Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

SIP-23 #86

Merged
merged 17 commits into from
Nov 15, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bincompat-backward.whitelist.conf
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ filter {
{
matchName="scala.collection.immutable.Stream.scala$collection$immutable$Stream$$loop$4"
problemName=MissingMethodProblem
},
{
matchName="scala.reflect.api.Types.LiteralType"
problemName=MissingMethodProblem
}
]
}
12 changes: 12 additions & 0 deletions bincompat-forward.whitelist.conf
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,18 @@ filter {
{
matchName="scala.reflect.runtime.Settings.ZirrefutableGeneratorPatterns"
problemName=MissingMethodProblem
},
{
matchName="scala.reflect.api.Types$LiteralTypeApi"
problemName=MissingClassProblem
},
{
matchName="scala.reflect.api.Types.LiteralType"
problemName=MissingMethodProblem
},
{
matchName="scala.reflect.api.Types$LiteralTypeExtractor"
problemName=MissingClassProblem
}
]
}
33 changes: 29 additions & 4 deletions spec/03-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ chapter: 3
| SimpleType ‘#’ id
| StableId
| Path ‘.’ ‘type’
| Literal
| ‘(’ Types ‘)’
TypeArgs ::= ‘[’ Types ‘]’
Types ::= Type {‘,’ Type}
Expand Down Expand Up @@ -102,16 +103,40 @@ forms.
### Singleton Types

```ebnf
SimpleType ::= Path ‘.’ type
SimpleType ::= Path ‘.’ type
```

A singleton type is of the form $p.$`type`, where $p$ is a
path pointing to a value expected to [conform](06-expressions.html#expression-typing)
to `scala.AnyRef`. The type denotes the set of values
consisting of `null` and the value denoted by $p$.
consisting of `null` and the value denoted by $p$
(i.e., the value $v$ for which `v eq p`).

A _stable type_ is either a singleton type or a type which is
declared to be a subtype of trait `scala.Singleton`.
<!-- a pattern match/type test against a singleton type `p.type` desugars to `_ eq p` -->

### Literal Types

```ebnf
SimpleType ::= Literal
```

A literal type `lit` is a special kind of singleton type which denotes the single literal value `lit`.
Thus, the type ascription `1: 1` gives the most precise type to the literal value `1`: the literal type `1`.

At run time, an expression `e` is considered to have literal type `lit` if `e == lit`.
Concretely, the result of `e.isInstanceOf[lit]` and `e match { case _ : lit => }` is determined by evaluating `e == lit`.

<!-- TODO: use eq when we lift it up to Any -->

<!-- TODO: relate to constant types, which trigger constant folding
ConstantType(1).deconst =:= LiteralType(1)
LiteralType(1).widen =:= IntClass.tpe
-->


### Stable Types
A _stable type_ is a singleton type, a literal type,
or a type that is declared to be a subtype of trait `scala.Singleton`.

### Type Projection

Expand Down
4 changes: 2 additions & 2 deletions spec/06-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ $T$ forSome { type $t_1[\mathit{tps}\_1] >: L_1 <: U_1$; $\ldots$; type $t_n[\ma
SimpleExpr ::= Literal
```

Typing of literals is as described [here](01-lexical-syntax.html#literals); their
evaluation is immediate.
Typing of literals is described along with their [lexical syntax](01-lexical-syntax.html#literals);
their evaluation is immediate.

## The _Null_ Value

Expand Down
8 changes: 5 additions & 3 deletions spec/08-pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,11 @@ A type pattern $T$ is of one of the following forms:
be used as type patterns, because they would match nothing in any case.

* A singleton type `$p$.type`. This type pattern matches only the value
denoted by the path $p$ (that is, a pattern match involved a
comparison of the matched value with $p$ using method `eq` in class
`AnyRef`).
denoted by the path $p$ (the `eq` method is used to compare the matched value to $p$).

* A literal type `$lit$`. This type pattern matches only the value
denoted by the literal $lit$ (the `==` method is used to compare the matched value to $lit$). <!-- SIP-23 -->

* A compound type pattern `$T_1$ with $\ldots$ with $T_n$` where each $T_i$ is a
type pattern. This type pattern matches all values that are matched by each of
the type patterns $T_i$.
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter)

override def settings = currentSettings

// TODO: temporary flag to easily enable/disable SIP-23 (aka the type formerly known as 42.type)
override def sip23: Boolean = settings.Xexperimental.value

/** Switch to turn on detailed type logs */
var printTypings = settings.Ytyperdebug.value

Expand Down
35 changes: 15 additions & 20 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ self =>
}
private def inScalaRootPackage = inScalaPackage && currentPackage == "scala"


def parseStartRule: () => Tree

def parseRule[T](rule: this.type => T): T = {
Expand Down Expand Up @@ -675,11 +676,11 @@ self =>

def isExprIntro: Boolean = isExprIntroToken(in.token)

def isTypeIntroToken(token: Token): Boolean = token match {
def isTypeIntroToken(token: Token): Boolean = (sip23 && isLiteralToken(token)) || (token match {
case IDENTIFIER | BACKQUOTED_IDENT | THIS |
SUPER | USCORE | LPAREN | AT => true
case _ => false
}
})

def isStatSeqEnd = in.token == RBRACE || in.token == EOF

Expand Down Expand Up @@ -932,31 +933,17 @@ self =>
* | SimpleType `#' Id
* | StableId
* | Path `.' type
* | Literal
* | `(' Types `)'
* | WildcardType
* }}}
*/
def simpleType(): Tree = {
val start = in.offset
simpleTypeRest(in.token match {
case LPAREN =>
atPos(start)(makeTupleType(inParens(types())))
case LBRACKET =>
atPos(start) {
val ts = typeParamClauseOpt(freshTypeName("typelambda"), null)
if (ts.isEmpty) {
syntaxError("missing type parameters", skipIt = false)
errorTypeTree
} else if (in.token == ARROW) {
in.skipToken()
makeTypeLambdaTypeTree(ts, typ())
} else {
syntaxError("`=>' expected", skipIt = false)
errorTypeTree
}
}
case USCORE =>
wildcardType(in.skipToken())
case LPAREN => atPos(start)(makeTupleType(inParens(types())))
case USCORE => wildcardType(in.skipToken())
case tok if sip23 && isLiteralToken(tok) => atPos(start){SingletonTypeTree(literal())} // SIP-23
case _ =>
path(thisOK = false, typeOK = true) match {
case r @ SingletonTypeTree(_) => r
Expand Down Expand Up @@ -1038,7 +1025,15 @@ self =>
else
mkOp(infixType(InfixMode.RightOp))
}
// SIP-23
def isNegatedLiteralType = sip23 && (
t match { // the token for `t` (Ident("-")) has already been read, thus `isLiteral` below is looking at next token (must be a literal)
case Ident(name) if isLiteral => name == nme.MINUS.toTypeName // TODO: OPT? lift out nme.MINUS.toTypeName?
case _ => false
}
)
if (isIdent) checkRepeatedParam orElse asInfix
else if (isNegatedLiteralType) atPos(t.pos.start){SingletonTypeTree(literal(isNegated = true, start = t.pos.start))}
else t
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ trait TypeKinds { self: ICodes =>
case ThisType(ArrayClass) => ObjectReference
case ThisType(sym) => REFERENCE(sym)
case SingleType(_, sym) => primitiveOrRefType(sym)
case LiteralType(_) => toTypeKind(t.underlying)
case ConstantType(_) => toTypeKind(t.underlying)
case TypeRef(_, sym, args) => primitiveOrClassType(sym, args)
case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
case ThisType(sym) => getClassBTypeAndRegisterInnerClass(sym)
case SingleType(_, sym) => primitiveOrClassToBType(sym)
case LiteralType(_) => toTypeKind(t.underlying)
case ConstantType(_) => toTypeKind(t.underlying)
case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))
}
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ abstract class Pickler extends SubComponent {
case SingleType(pre, sym) =>
putType(pre)
putSymbol(sym)
case LiteralType(value) =>
putConstant(value)
case SuperType(thistpe, supertpe) =>
putType(thistpe)
putType(supertpe)
Expand Down Expand Up @@ -452,6 +454,7 @@ abstract class Pickler extends SubComponent {
case NoType | NoPrefix =>
case ThisType(sym) => writeRef(sym)
case SingleType(pre, sym) => writeRef(pre) ; writeRef(sym)
case LiteralType(value) => writeRef(value)
case SuperType(thistpe, supertpe) => writeRef(thistpe) ; writeRef(supertpe)
case ConstantType(value) => writeRef(value)
case TypeBounds(lo, hi) => writeRef(lo) ; writeRef(hi)
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ abstract class Erasure extends AddInterfaces
List(TypeTree(tp) setPos targ.pos)) setPos fn.pos,
List()) setPos tree.pos
targ.tpe match {
case SingleType(_, _) | ThisType(_) | SuperType(_, _) =>
case SingleType(_, _) | LiteralType(_) | ThisType(_) | SuperType(_, _) =>
val cmpOp = if (targ.tpe <:< AnyValTpe) Any_equals else Object_eq
atPos(tree.pos) {
Apply(Select(qual, cmpOp), List(gen.mkAttributedQualifier(targ.tpe)))
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/transform/UnCurry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ abstract class UnCurry extends InfoTransform
args.take(formals.length - 1) :+ (suffix setType formals.last)
}

val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head) else args
val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head.widen) else args

map2(formals, args1) { (formal, arg) =>
if (!isByNameParamType(formal))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,16 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
// - Scala's arrays are invariant (so we don't drop type tests unsoundly)
if (extractorArgTypeTest) mkDefault
else expectedTp match {
case SingleType(_, sym) => mkEqTest(gen.mkAttributedQualifier(expectedTp)) // SI-4577, SI-4897
case SingleType(_, sym) =>
val expected = gen.mkAttributedQualifier(expectedTp)
if (expectedTp <:< AnyRefTpe) mkEqTest(expected) // SI-4577, SI-4897
else mkEqualsTest(expected)
// TODO SIP-23: should we test equality for literal types with eq?
// Conceptually cleaner, as SingleType is tested using eq.
// In practice it doesn't really matter, since `equals` does the same thing as `eq` in the `AnyVal` subclasses of `Any`.
// Should revisit if we end up lifting `eq`'s definition to `Any`, as discussed here:
// https://groups.google.com/d/msg/scala-internals/jsVlJI4H5OQ/8emZWRmgzcoJ
case LiteralType(const) => mkEqualsTest(expTp(Literal(const)))
case ThisType(sym) if sym.isModule => and(mkEqualsTest(CODE.REF(sym)), mkTypeTest) // must use == to support e.g. List() == Nil
case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(CODE.NULL))
case ConstantType(const) => mkEqualsTest(expTp(Literal(const)))
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,7 @@ trait Implicits {
// necessary only to compile typetags used inside the Universe cake
case ThisType(thisSym) =>
gen.mkAttributedThis(thisSym)
// TODO SIP-23: TypeTag for LiteralType
case _ =>
// if `pre` is not a PDT, e.g. if someone wrote
// implicitly[scala.reflect.macros.blackbox.Context#TypeTag[Int]]
Expand Down
81 changes: 53 additions & 28 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,28 @@ trait Namers extends MethodSynthesis {
}
}

/** This method has a big impact on the eventual compiled code.
private def refersToSymbolLessAccessibleThan(tp: Type, sym: Symbol): Boolean = {
val accessibilityReference =
if (sym.isValue && sym.owner.isClass && sym.isPrivate)
sym.getterIn(sym.owner)
else sym

@tailrec def loop(tp: Type): Boolean = tp match {
case SingleType(pre, sym) =>
(sym isLessAccessibleThan accessibilityReference) || loop(pre)
case ThisType(sym) =>
sym isLessAccessibleThan accessibilityReference
case p: SimpleTypeProxy =>
loop(p.underlying)
case _ =>
false
}

loop(tp)
}

/**
* This method has a big impact on the eventual compiled code.
* At this point many values have the most specific possible
* type (e.g. in val x = 42, x's type is Int(42), not Int) but
* most need to be widened to avoid undesirable propagation of
Expand All @@ -838,35 +859,39 @@ trait Namers extends MethodSynthesis {
* value should not be widened, so it has a use even in situations
* whether it is otherwise redundant (such as in a singleton.)
*/
private def widenIfNecessary(sym: Symbol, tpe: Type, pt: Type): Type = {
val getter =
if (sym.isValue && sym.owner.isClass && sym.isPrivate)
sym.getter(sym.owner)
else sym
def isHidden(tp: Type): Boolean = tp match {
case SingleType(pre, sym) =>
(sym isLessAccessibleThan getter) || isHidden(pre)
case ThisType(sym) =>
sym isLessAccessibleThan getter
case p: SimpleTypeProxy =>
isHidden(p.underlying)
case _ =>
false
}
val shouldWiden = (
!tpe.typeSymbolDirect.isModuleClass // Infer Foo.type instead of "object Foo"
&& (tpe.widen <:< pt) // Don't widen our way out of conforming to pt
&& ( sym.isVariable
|| sym.isMethod && !sym.hasAccessorFlag
|| isHidden(tpe)
)
)
dropIllegalStarTypes(
private def widenIfNecessary(sym: Symbol, tpe: Type, pt: Type): Type =
if (sip23) { // SIP-23
// TODO: spec -- this is a crucial part of type inference
// NOTES:
// - Can we widen less? (E.g., for local definitions.)
// - Do we need to check tpe.deconst <:< pt?
// - We don't need to call dropIllegalStarTypes on a ref to a module class, do we? Where would the stars be? In the prefix?

// We're inferring the result type of a stable symbol, and the type doesn't refer to a hidden symbol
val mayKeepSingletonType = sym.isStable && !refersToSymbolLessAccessibleThan(tpe, sym)

// (OPT: 99.99% of the time, pt will be WildcardType)
@inline def cannotWiden = (pt ne WildcardType) && !(tpe.widen <:< pt)

// If the definition can keep its inferred singleton type,
// or widening would mean no longer conforming to the expected type,
// we must still deconst unless it's a final val. Otherwise, widen.
if (mayKeepSingletonType || cannotWiden) { if (sym.isFinal) tpe else tpe.deconst }
else tpe.widen
} else {
val shouldWiden = (
!tpe.typeSymbolDirect.isModuleClass // Infer Foo.type instead of "object Foo"
&& (tpe.widen <:< pt) // Don't widen our way out of conforming to pt
&& ( sym.isVariable
|| sym.isMethod && !sym.hasAccessorFlag
|| refersToSymbolLessAccessibleThan(tpe, sym)
)
)
if (shouldWiden) tpe.widen
else if (sym.isFinal) tpe // "final val" allowed to retain constant type
else tpe.deconst
)
}
}

/** Computes the type of the body in a ValDef or DefDef, and
* assigns the type to the tpt's node. Returns the type.
*/
Expand All @@ -876,7 +901,7 @@ trait Namers extends MethodSynthesis {
case _ => defnTyper.computeType(tree.rhs, pt)
}

val defnTpe = widenIfNecessary(tree.symbol, rhsTpe, pt)
val defnTpe = dropIllegalStarTypes(widenIfNecessary(tree.symbol, rhsTpe, pt))
tree.tpt defineType defnTpe setPos tree.pos.focus
tree.tpt.tpe
}
Expand Down
8 changes: 6 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5093,13 +5093,17 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}

def typedSingletonTypeTree(tree: SingletonTypeTree) = {
// SIP-23: don't require AnyRef for 1.type etc
val pt = if (sip23) WildcardType else AnyRefTpe
val refTyped =
context.withImplicitsDisabled {
typed(tree.ref, MonoQualifierModes | mode.onlyTypePat, AnyRefTpe)
typed(tree.ref, MonoQualifierModes | mode.onlyTypePat, pt)
}

// .resultType unwraps NullaryMethodType (accessor of a path)
// .deconst unwraps the ConstantType to a LiteralType (for literal-based singleton types)
if (!refTyped.isErrorTyped)
tree setType refTyped.tpe.resultType
tree setType refTyped.tpe.resultType.deconst

if (treeInfo.admitsTypeSelection(refTyped)) tree
else UnstableTreeError(refTyped)
Expand Down
Loading