From e4d50a77532a256956d4c29b8a805d4d4664f690 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 23 Oct 2014 17:34:10 +0200 Subject: [PATCH 01/21] bump module versions in eclipse projects --- src/eclipse/partest/.classpath | 2 +- src/eclipse/scaladoc/.classpath | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/eclipse/partest/.classpath b/src/eclipse/partest/.classpath index 7f28868d95ee..d9294625b0f6 100644 --- a/src/eclipse/partest/.classpath +++ b/src/eclipse/partest/.classpath @@ -5,7 +5,7 @@ - + diff --git a/src/eclipse/scaladoc/.classpath b/src/eclipse/scaladoc/.classpath index c8f0e89b8a8c..fad852db5a35 100644 --- a/src/eclipse/scaladoc/.classpath +++ b/src/eclipse/scaladoc/.classpath @@ -6,8 +6,8 @@ - - - + + + From e119ee52f3bb524a69f65d8f56e1abcc568a2969 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 24 Oct 2014 14:32:41 +0200 Subject: [PATCH 02/21] [WIP] SIP-23 Parse `Literal` as `SimpleType` This requires -Xexperimental in 2.11.x. Update spec. TODO: - test! - include SIP as spec addendum - don't predicate on -Xexperimental -- introduce -Xsip:23? Notes: - `()` is not a literal, neither are `Symbol`s --- spec/03-types.md | 33 ++++++++++++++++--- spec/06-expressions.md | 4 +-- spec/08-pattern-matching.md | 8 +++-- .../scala/tools/nsc/ast/parser/Parsers.scala | 18 ++++++++-- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/spec/03-types.md b/spec/03-types.md index d067d45ab2fd..c3fa08091b8e 100644 --- a/spec/03-types.md +++ b/spec/03-types.md @@ -23,6 +23,7 @@ chapter: 3 | SimpleType ‘#’ id | StableId | Path ‘.’ ‘type’ + | Literal | ‘(’ Types ‘)’ TypeArgs ::= ‘[’ Types ‘]’ Types ::= Type {‘,’ Type} @@ -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`. + + +### 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`. + + + + + + +### 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 diff --git a/spec/06-expressions.md b/spec/06-expressions.md index afd149274413..68f41a6faa8b 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -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 diff --git a/spec/08-pattern-matching.md b/spec/08-pattern-matching.md index 3538457b5c36..a0e40be8b0c3 100644 --- a/spec/08-pattern-matching.md +++ b/spec/08-pattern-matching.md @@ -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$). + * 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$. diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 466381000376..c5c742f8e984 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -339,6 +339,9 @@ self => } private def inScalaRootPackage = inScalaPackage && currentPackage == "scala" + // 42.type aka SIP-23 + private[this] lazy val parseLiteralSingletonTypes = settings.Xexperimental.value + def parseStartRule: () => Tree def parseRule[T](rule: this.type => T): T = { @@ -674,11 +677,11 @@ self => def isExprIntro: Boolean = isExprIntroToken(in.token) - def isTypeIntroToken(token: Token): Boolean = token match { + def isTypeIntroToken(token: Token): Boolean = (parseLiteralSingletonTypes && isLiteralToken(token)) || (token match { case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER | USCORE | LPAREN | AT => true case _ => false - } + }) def isStatSeqEnd = in.token == RBRACE || in.token == EOF @@ -930,6 +933,7 @@ self => * | SimpleType `#' Id * | StableId * | Path `.' type + * | Literal [`.' type] * | `(' Types `)' * | WildcardType * }}} @@ -939,7 +943,15 @@ self => simpleTypeRest(in.token match { case LPAREN => atPos(start)(makeTupleType(inParens(types()))) case USCORE => wildcardType(in.skipToken()) - case _ => + case tok if parseLiteralSingletonTypes && isLiteralToken(tok) => // SIP-23 + // // TODO: if we go back to allowing an optional `.type` suffix: + // val lit = literal() + // if (in.token == DOT && lookingAhead { in.token == TYPE }) { + // accept(DOT) + // accept(TYPE) + // } + atPos(start)(SingletonTypeTree(literal())) + case _ => path(thisOK = false, typeOK = true) match { case r @ SingletonTypeTree(_) => r case r => convertToTypeId(r) From 067e0a211f78e681c2fe0feefa7e27b69bb6edc9 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 27 Oct 2014 13:43:04 +0100 Subject: [PATCH 03/21] [WIP] SIP-23 LiteralType: 42 and friends `LiteralType` is a `SingleType` with a literal for its path. With an example: `1` is to `p.type`, as `1` is to `p`. `lit` is the most precise type for an expression known to evaluate to `lit`. Note that we cannot constant fold the expression to `lit`, since it may have a side effect. `ConstantType(c)` is the type of an expression that can be constant folded to `c`. When an expressions should not be constant folded, the corresponding `LiteralType` is created: `ConstantType(c).deconst == LiteralType(c)` (this is where all `LiteralType`s originate, except for unpickling). `typedLiteral` always infers a `ConstantType`, which is subsequently `deconst`ed or `widen`ed where necessary (see `widenIfNecessary`). In short, only `final val`s may have `ConstantType`s, and `widen`ing is required to ensure that a path is stable and accessible (roughly speaking). When checking subtyping, this invariant is useful: `LiteralType(c).widen == c.tpe`. TODO: - test! (subtyping, type inference, type tests, patmat, dependent method types) - fully predicate implementation on -Xsip:23 - is it safe to reduce LITERAL in pickler? --- .../tools/nsc/backend/icode/TypeKinds.scala | 1 + .../tools/nsc/backend/jvm/BCodeHelpers.scala | 1 + .../tools/nsc/symtab/classfile/Pickler.scala | 3 ++ .../scala/tools/nsc/transform/Erasure.scala | 2 +- .../transform/patmat/MatchTreeMaking.scala | 6 +++ .../tools/nsc/typechecker/Implicits.scala | 1 + .../scala/tools/nsc/typechecker/Typers.scala | 8 +++- src/reflect/scala/reflect/api/Types.scala | 43 +++++++++++++++++-- .../scala/reflect/internal/TreeGen.scala | 2 + .../scala/reflect/internal/Types.scala | 34 +++++++++++++-- .../scala/reflect/internal/Variances.scala | 3 +- .../internal/pickling/PickleFormat.scala | 1 + .../internal/pickling/Translations.scala | 1 + .../reflect/internal/pickling/UnPickler.scala | 1 + .../reflect/internal/tpe/TypeComparers.scala | 2 + .../scala/reflect/internal/tpe/TypeMaps.scala | 1 + 16 files changed, 98 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala index a6d0d3b9fa15..9fc87d6f8ea2 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala @@ -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!") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 806d4b277ce7..e3ad474edddc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -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)) } diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala index 25e13a131485..510427c6bb80 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala @@ -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) @@ -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) diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index b6af19250ecb..b3f0c52aeedc 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -904,7 +904,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))) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index 3abec521dfe2..30ae0d9872ec 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -448,6 +448,12 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { if (extractorArgTypeTest) mkDefault else expectedTp match { case SingleType(_, sym) => mkEqTest(gen.mkAttributedQualifier(expectedTp)) // SI-4577, SI-4897 + // 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))) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 74c28122a1a5..74dd2be5807e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -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]] diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 4d9a6a47ef56..83b47475ae92 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5178,15 +5178,19 @@ 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 (settings.Xexperimental) WildcardType else AnyRefTpe val refTyped = context.withImplicitsDisabled { - typed(tree.ref, MonoQualifierModes | mode.onlyTypePat, AnyRefTpe) + typed(tree.ref, MonoQualifierModes | mode.onlyTypePat, pt) } if (refTyped.isErrorTyped) { setError(tree) } else { - tree setType refTyped.tpe.resultType + // .resultType unwraps NullaryMethodType (accessor of a path) + // .deconst unwraps the ConstantType to a LiteralType (for literal-based singleton types) + tree setType refTyped.tpe.resultType.deconst if (refTyped.isErrorTyped || treeInfo.admitsTypeSelection(refTyped)) tree else UnstableTreeError(tree) } diff --git a/src/reflect/scala/reflect/api/Types.scala b/src/reflect/scala/reflect/api/Types.scala index f6995dd5dea7..53c0e27c336d 100644 --- a/src/reflect/scala/reflect/api/Types.scala +++ b/src/reflect/scala/reflect/api/Types.scala @@ -528,12 +528,17 @@ trait Types { */ def supertpe: Type } - /** The `ConstantType` type is not directly written in user programs, but arises as the type of a constant. - * The REPL expresses constant types like `Int(11)`. Here are some constants with their types: + + /** A `ConstantType` type cannot be expressed in user programs; it is inferred as the type of a constant. + * Here are some constants with their types and the internal string representation: * {{{ - * 1 ConstantType(Constant(1)) - * "abc" ConstantType(Constant("abc")) + * 1 ConstantType(Constant(1)) Int(1) + * "abc" ConstantType(Constant("abc")) String("abc") * }}} + * + * ConstantTypes denote values that may safely be constant folded during type checking. + * The `deconst` operation returns the equivalent type that will not be constant folded. + * * @template * @group Types */ @@ -565,6 +570,36 @@ trait Types { def value: Constant } + /** The `LiteralType` type represent a singleton type with a literal for its path. + * Examples: "a".type or 42.type, which may be abbreviated to "a" or 42. + * + * @template + * @group Types + */ + type LiteralType >: Null <: LiteralTypeApi with SingletonType + + /** The constructor/extractor for `ConstantType` instances. + * @group Extractors + */ + val LiteralType: LiteralTypeExtractor + + /** An extractor class to create and pattern match with syntax `LiteralType(constant)` + * Here, `constant` is the constant value represented by the type. + * @group Extractors + */ + abstract class LiteralTypeExtractor { + def unapply(tpe: LiteralType): Option[Constant] + } + + /** The API that all literal types support. + * The main source of information about types is the [[scala.reflect.api.Types]] page. + * @group API + */ + trait LiteralTypeApi extends TypeApi { this: LiteralType => + /** The compile-time constant underlying this type. */ + def value: Constant + } + /** The `TypeRef` type describes types of any of the forms on the left, * with their TypeRef representations to the right. * {{{ diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 6e8e992d1656..8f8de60c1758 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -77,6 +77,8 @@ abstract class TreeGen { else mkAttributedThis(clazz) case SingleType(pre, sym) => mkApplyIfNeeded(mkAttributedStableRef(pre, sym)) + case LiteralType(value) => + Literal(value) setType tpe case TypeRef(pre, sym, args) => if (sym.isRoot) { mkAttributedThis(sym) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index a95f626a0b5e..ab1904d7aec3 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -34,6 +34,8 @@ import TypeConstants._ // super references case SingleType(pre, sym) => // pre.sym.type + case LiteralType(value) => + // value.type case ConstantType(value) => // Int(2) case TypeRef(pre, sym, args) => @@ -1263,6 +1265,28 @@ trait Types } } + /** A class representing a literal type. Conceptually, a SingleType where the path is a literal. + * + * Along with ConstantType (which triggers constant folding), the most precise type for a literal. + * + * This type is created by `deconst`ing the corresponding `ConstantType` (see typedLiteral and widenIfNecessary); + * it `widen`s to the type of `value`. + * + * NOTE: `()` is not a literal, and neither are `Symbol`s (`'a`) + */ + abstract case class LiteralType(value: Constant) extends SingletonType with LiteralTypeApi { + override def underlying: Type = value.tpe + override def isTrivial: Boolean = true + override def safeToString: String = value.escapedStringValue + override def kind = "LiteralType" + } + + final class UniqueLiteralType(value: Constant) extends LiteralType(value) + + object LiteralType extends LiteralTypeExtractor { + def apply(value: Constant) = unique(new UniqueLiteralType(value)) + } + abstract case class SuperType(thistpe: Type, supertpe: Type) extends SingletonType with SuperTypeApi { private var trivial: ThreeValue = UNKNOWN override def isTrivial: Boolean = { @@ -1811,15 +1835,17 @@ trait Types class PackageClassInfoType(decls: Scope, clazz: Symbol) extends ClassInfoType(List(), decls, clazz) - /** A class representing a constant type. + /** A class representing a constant type. Constant types are constant-folded during type checking. + * To avoid constant folding, use the type returned by `deconst` instead. */ abstract case class ConstantType(value: Constant) extends SingletonType with ConstantTypeApi { - override def underlying: Type = value.tpe assert(underlying.typeSymbol != UnitClass) + + override def underlying: Type = if (settings.Xexperimental) LiteralType(value) else value.tpe // SIP-23 override def isTrivial: Boolean = true override def deconst: Type = underlying.deconst override def safeToString: String = - underlying.toString + "(" + value.escapedStringValue + ")" + underlying.widen.toString + "(" + value.escapedStringValue + ")" override def kind = "ConstantType" } @@ -3988,7 +4014,7 @@ trait Types * except it excludes ConstantTypes. */ def isSingleType(tp: Type) = tp match { - case ThisType(_) | SuperType(_, _) | SingleType(_, _) => true + case ThisType(_) | SuperType(_, _) | SingleType(_, _) | LiteralType(_) => true case _ => false } diff --git a/src/reflect/scala/reflect/internal/Variances.scala b/src/reflect/scala/reflect/internal/Variances.scala index 12b765b7a60b..9c3754a52e32 100644 --- a/src/reflect/scala/reflect/internal/Variances.scala +++ b/src/reflect/scala/reflect/internal/Variances.scala @@ -198,7 +198,8 @@ trait Variances { def inSym(sym: Symbol): Variance = if (sym.isAliasType) inType(sym.info).cut else inType(sym.info) def inType(tp: Type): Variance = tp match { case ErrorType | WildcardType | NoType | NoPrefix => Bivariant - case ThisType(_) | ConstantType(_) => Bivariant + case ThisType(_) | ConstantType(_) + | LiteralType(_) => Bivariant case TypeRef(_, `tparam`, _) => Covariant case BoundedWildcardType(bounds) => inType(bounds) case NullaryMethodType(restpe) => inType(restpe) diff --git a/src/reflect/scala/reflect/internal/pickling/PickleFormat.scala b/src/reflect/scala/reflect/internal/pickling/PickleFormat.scala index ce0ceec688dc..7078e637a354 100644 --- a/src/reflect/scala/reflect/internal/pickling/PickleFormat.scala +++ b/src/reflect/scala/reflect/internal/pickling/PickleFormat.scala @@ -131,6 +131,7 @@ object PickleFormat { final val NOPREFIXtpe = 12 final val THIStpe = 13 final val SINGLEtpe = 14 + final val LITERALtpe = LITERAL // TODO SIP-23 final val CONSTANTtpe = 15 final val TYPEREFtpe = 16 final val TYPEBOUNDStpe = 17 diff --git a/src/reflect/scala/reflect/internal/pickling/Translations.scala b/src/reflect/scala/reflect/internal/pickling/Translations.scala index d924cb3a0c7c..4ca42466714c 100644 --- a/src/reflect/scala/reflect/internal/pickling/Translations.scala +++ b/src/reflect/scala/reflect/internal/pickling/Translations.scala @@ -66,6 +66,7 @@ trait Translations { case NoPrefix => NOPREFIXtpe case _: ThisType => THIStpe case _: SingleType => SINGLEtpe + case _: LiteralType => LITERALtpe case _: SuperType => SUPERtpe case _: ConstantType => CONSTANTtpe case _: TypeBounds => TYPEBOUNDStpe diff --git a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala index 5433bfad6031..bbf9df593f0b 100644 --- a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala +++ b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala @@ -399,6 +399,7 @@ abstract class UnPickler { case SINGLEtpe => SingleType(readTypeRef(), readSymbolRef()) case SUPERtpe => SuperType(readTypeRef(), readTypeRef()) case CONSTANTtpe => ConstantType(readConstantRef()) + case LITERALtpe => LiteralType(readConstantRef()) case TYPEREFtpe => TypeRef(readTypeRef(), readSymbolRef(), readTypes()) case TYPEBOUNDStpe => TypeBounds(readTypeRef(), readTypeRef()) case REFINEDtpe | CLASSINFOtpe => CompoundType(readSymbolRef(), readTypes()) diff --git a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala index f9b10c90beb5..2ebd3bf3825c 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala @@ -224,6 +224,7 @@ trait TypeComparers { case ExistentialType(qs1, res1) => tp2 match { case ExistentialType(qs2, res2) => equalTypeParamsAndResult(qs1, res1, qs2, res2) ; case _ => false } case ThisType(sym1) => tp2 match { case ThisType(sym2) => sym1 == sym2 ; case _ => false } case ConstantType(c1) => tp2 match { case ConstantType(c2) => c1 == c2 ; case _ => false } + case LiteralType(c1) => tp2 match { case LiteralType(c2) => c1 == c2 ; case _ => false } case NullaryMethodType(res1) => tp2 match { case NullaryMethodType(res2) => res1 =:= res2 ; case _ => false } case TypeBounds(lo1, hi1) => tp2 match { case TypeBounds(lo2, hi2) => lo1 =:= lo2 && hi1 =:= hi2 ; case _ => false } case _ => false @@ -371,6 +372,7 @@ trait TypeComparers { private def isSubType2(tp1: Type, tp2: Type, depth: Depth): Boolean = { def retry(lhs: Type, rhs: Type) = ((lhs ne tp1) || (rhs ne tp2)) && isSubType(lhs, rhs, depth) + // TODO SIP-23: should this just be tp1.isInstanceOf[SingletonType] && tp2.isInstanceOf[SingletonType] if (isSingleType(tp1) && isSingleType(tp2) || isConstantType(tp1) && isConstantType(tp2)) return (tp1 =:= tp2) || isThisAndSuperSubtype(tp1, tp2) || retry(tp1.underlying, tp2) diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index c705ca70694d..8ffbab4525a6 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -124,6 +124,7 @@ private[internal] trait TypeMaps { if (pre1 eq pre) tp else singleType(pre1, sym) } + case LiteralType(_) => tp case MethodType(params, result) => val params1 = flipped(mapOver(params)) val result1 = this(result) From 21c7500a7031ac045e9ffba4cca88d829500559c Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 27 Oct 2014 22:35:45 +0100 Subject: [PATCH 04/21] [WIP] SIP-23 equality test for singleton type <:< AnyVal test case: ``` val y: 5 = 5 def g(x: Int) = x match { case _: y.type => 0 } ``` --- .../scala/tools/nsc/transform/patmat/MatchTreeMaking.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index 30ae0d9872ec..634024ba881a 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -447,7 +447,10 @@ 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`. From 48b6e5e1fe62651774efd85b9851d58d9ea751d3 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 28 Oct 2014 11:23:08 +0100 Subject: [PATCH 05/21] [WIP] SIP-23 fix doc comment in parsers --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index c5c742f8e984..9364dbd42f15 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -933,7 +933,7 @@ self => * | SimpleType `#' Id * | StableId * | Path `.' type - * | Literal [`.' type] + * | Literal * | `(' Types `)' * | WildcardType * }}} From 70e51f1ea898eff1b5ad5f6b6965aa82b4cff815 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 28 Oct 2014 22:00:45 +1000 Subject: [PATCH 06/21] SIP-23 Account for literal types in varargs translation Before: sandbox/test.scala:2: error: type mismatch; found : Array[1] required: Array[Int] --- src/compiler/scala/tools/nsc/transform/UnCurry.scala | 2 +- test/files/run/literal-type-varargs.flags | 1 + test/files/run/literal-type-varargs.scala | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/files/run/literal-type-varargs.flags create mode 100644 test/files/run/literal-type-varargs.scala diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 3544dc9966d9..19b2d4a66d2b 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -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)) diff --git a/test/files/run/literal-type-varargs.flags b/test/files/run/literal-type-varargs.flags new file mode 100644 index 000000000000..48fd867160ba --- /dev/null +++ b/test/files/run/literal-type-varargs.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/run/literal-type-varargs.scala b/test/files/run/literal-type-varargs.scala new file mode 100644 index 000000000000..3b78af727dc1 --- /dev/null +++ b/test/files/run/literal-type-varargs.scala @@ -0,0 +1,4 @@ +object Test extends App { + val x = List.apply[1](1) + assert(x == List(1)) +} From f887a0e87bbdedf8251cab02c25d9ec8c2a056dc Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 28 Oct 2014 15:11:17 +0100 Subject: [PATCH 07/21] SI-8564 --- src/reflect/scala/reflect/internal/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index ab1904d7aec3..464d1d897c00 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -2447,7 +2447,7 @@ trait Types if (isTrivial || phase.erasedTypes) resultType else if (/*isDependentMethodType &&*/ sameLength(actuals, params)) { val idm = new InstantiateDependentMap(params, actuals) - val res = idm(resultType) + val res = idm(resultType).deconst existentialAbstraction(idm.existentialsNeeded, res) } else existentialAbstraction(params, resultType) From 1588a261712f6af0fcc9e117a249579743b0c521 Mon Sep 17 00:00:00 2001 From: George Leontiev Date: Tue, 28 Oct 2014 18:06:19 +0100 Subject: [PATCH 08/21] Add some basic smoke tests for SIP-23. --- test/files/run/sip-23-implicit-resolution.flags | 1 + test/files/run/sip-23-implicit-resolution.scala | 17 +++++++++++++++++ test/files/run/sip-23-no-constant-folding.check | 2 ++ test/files/run/sip-23-no-constant-folding.flags | 1 + test/files/run/sip-23-no-constant-folding.scala | 15 +++++++++++++++ test/files/run/sip-23-type-equality.flags | 1 + test/files/run/sip-23-type-equality.scala | 8 ++++++++ 7 files changed, 45 insertions(+) create mode 100644 test/files/run/sip-23-implicit-resolution.flags create mode 100644 test/files/run/sip-23-implicit-resolution.scala create mode 100644 test/files/run/sip-23-no-constant-folding.check create mode 100644 test/files/run/sip-23-no-constant-folding.flags create mode 100644 test/files/run/sip-23-no-constant-folding.scala create mode 100644 test/files/run/sip-23-type-equality.flags create mode 100644 test/files/run/sip-23-type-equality.scala diff --git a/test/files/run/sip-23-implicit-resolution.flags b/test/files/run/sip-23-implicit-resolution.flags new file mode 100644 index 000000000000..48fd867160ba --- /dev/null +++ b/test/files/run/sip-23-implicit-resolution.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/run/sip-23-implicit-resolution.scala b/test/files/run/sip-23-implicit-resolution.scala new file mode 100644 index 000000000000..5d83f89c43b1 --- /dev/null +++ b/test/files/run/sip-23-implicit-resolution.scala @@ -0,0 +1,17 @@ +object Test extends App { + trait Assoc[K] { type V ; val v: V } + + def mkAssoc[K, V0](k: K, v0: V0): Assoc[k.type] { type V = V0 } = new Assoc[k.type] {type V = V0 ; val v = v0} + def lookup[K](k: K)(implicit a: Assoc[k.type]): a.V = a.v + + implicit def firstAssoc = mkAssoc(1, "Panda!") + implicit def secondAssoc = mkAssoc(2, "Kitty!") + + implicit def ageAssoc = mkAssoc("Age", 3) + implicit def nmAssoc = mkAssoc("Name", "Jane") + + assert(lookup(1) == "Panda!") + assert(lookup(2) == "Kitty!") + assert(lookup("Age") == 3) + assert(lookup("Name") == "Jane") +} diff --git a/test/files/run/sip-23-no-constant-folding.check b/test/files/run/sip-23-no-constant-folding.check new file mode 100644 index 000000000000..c6a6bd717378 --- /dev/null +++ b/test/files/run/sip-23-no-constant-folding.check @@ -0,0 +1,2 @@ +got 42 +panda diff --git a/test/files/run/sip-23-no-constant-folding.flags b/test/files/run/sip-23-no-constant-folding.flags new file mode 100644 index 000000000000..48fd867160ba --- /dev/null +++ b/test/files/run/sip-23-no-constant-folding.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/run/sip-23-no-constant-folding.scala b/test/files/run/sip-23-no-constant-folding.scala new file mode 100644 index 000000000000..17cc9f1d8913 --- /dev/null +++ b/test/files/run/sip-23-no-constant-folding.scala @@ -0,0 +1,15 @@ +object Test extends App { + + def noisyId[A](x: A): x.type = { + println(s"got $x") + x + } + + def testNoConstantFolding(): 23 = { + println("panda") + 23 + } + + assert(noisyId(42) == 42) + assert(testNoConstantFolding == 23) +} diff --git a/test/files/run/sip-23-type-equality.flags b/test/files/run/sip-23-type-equality.flags new file mode 100644 index 000000000000..48fd867160ba --- /dev/null +++ b/test/files/run/sip-23-type-equality.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/run/sip-23-type-equality.scala b/test/files/run/sip-23-type-equality.scala new file mode 100644 index 000000000000..f9052323ea34 --- /dev/null +++ b/test/files/run/sip-23-type-equality.scala @@ -0,0 +1,8 @@ +object Test extends App { + val x = 1 + val y: x.type = 1 + + implicitly[x.type =:= y.type] + implicitly[1 =:= y.type] + implicitly[1 =:= x.type] +} From 5d92b7efda57fb1b6230ab4734d65b858f20d577 Mon Sep 17 00:00:00 2001 From: George Leontiev Date: Wed, 29 Oct 2014 11:05:00 +0100 Subject: [PATCH 09/21] Add LiteralType to JavaUniverseForce, backward, and forward bincompat. --- bincompat-backward.whitelist.conf | 4 ++++ bincompat-forward.whitelist.conf | 12 ++++++++++++ .../scala/reflect/runtime/JavaUniverseForce.scala | 1 + 3 files changed, 17 insertions(+) diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index 076b9bb9aaeb..1d26dce74074 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -198,6 +198,10 @@ filter { { matchName="scala.collection.immutable.Stream.scala$collection$immutable$Stream$$loop$4" problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.api.Types.LiteralType" + problemName=MissingMethodProblem } ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 53401eefad19..a4dee7261dda 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -292,6 +292,18 @@ filter { { matchName="scala.collection.immutable.Stream.scala$collection$immutable$Stream$$loop$2" 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 } ] } diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index c87b810bdd31..a8f42fdaf043 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -137,6 +137,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.NoPrefix this.ThisType this.SingleType + this.LiteralType this.SuperType this.TypeBounds this.CompoundType From d25b5cc7f2108d5a7376e302089c2d70d0bb569e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 29 Oct 2014 14:24:24 +0100 Subject: [PATCH 10/21] [WIP] SIP-23: accept negated literal in infixTypeRest e.g., val x : -1 => -1 = ??? --- .../scala/tools/nsc/ast/parser/Parsers.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 9364dbd42f15..4a3b896016d9 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -943,14 +943,7 @@ self => simpleTypeRest(in.token match { case LPAREN => atPos(start)(makeTupleType(inParens(types()))) case USCORE => wildcardType(in.skipToken()) - case tok if parseLiteralSingletonTypes && isLiteralToken(tok) => // SIP-23 - // // TODO: if we go back to allowing an optional `.type` suffix: - // val lit = literal() - // if (in.token == DOT && lookingAhead { in.token == TYPE }) { - // accept(DOT) - // accept(TYPE) - // } - atPos(start)(SingletonTypeTree(literal())) + case tok if parseLiteralSingletonTypes && isLiteralToken(tok) => SingletonTypeTree(literal(start = start)) // SIP-23 case _ => path(thisOK = false, typeOK = true) match { case r @ SingletonTypeTree(_) => r @@ -1032,7 +1025,15 @@ self => else mkOp(infixType(InfixMode.RightOp)) } + // SIP-23 + def isNegatedLiteralType = parseLiteralSingletonTypes && ( + 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) SingletonTypeTree(literal(isNegated = true, start = t.pos.start)) else t } From ca24e7c0f58e3f45c5664d65fdaa871d9710452f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 3 Nov 2014 23:11:08 +0100 Subject: [PATCH 11/21] WIP: quickfix for nopos in parser -- proper fix? --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 4a3b896016d9..1f4072c51483 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -943,7 +943,7 @@ self => simpleTypeRest(in.token match { case LPAREN => atPos(start)(makeTupleType(inParens(types()))) case USCORE => wildcardType(in.skipToken()) - case tok if parseLiteralSingletonTypes && isLiteralToken(tok) => SingletonTypeTree(literal(start = start)) // SIP-23 + case tok if parseLiteralSingletonTypes && isLiteralToken(tok) => atPos(start){SingletonTypeTree(literal())} // SIP-23 case _ => path(thisOK = false, typeOK = true) match { case r @ SingletonTypeTree(_) => r @@ -1033,7 +1033,7 @@ self => } ) if (isIdent) checkRepeatedParam orElse asInfix - else if (isNegatedLiteralType) SingletonTypeTree(literal(isNegated = true, start = t.pos.start)) + else if (isNegatedLiteralType) atPos(t.pos.start){SingletonTypeTree(literal(isNegated = true, start = t.pos.start))} else t } From 063e2dba2fcca900e3c9243a94de2dc4ed4d4f8f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 13 May 2014 15:14:32 +0200 Subject: [PATCH 12/21] WIP SI-5103 avoid widening TypeVar <: Singleton TODO: deriving this from type param bounds is not the best way, should consider all constraints that we encounter scala> def stable[T <: 1](x: T): T = x stable: [T <: 1](x: T)T scala> stable(1) res0: 1 = 1 scala> stable(2) :9: error: inferred type arguments [2] do not conform to method stable's type parameter bounds [T <: 1] stable(2) ^ :9: error: type mismatch; found : Int(2) required: T stable(2) ^ scala> def stable[T <: Singleton](x: T): T = x stable: [T <: Singleton](x: T)T scala> val x: Int = 2 x: Int = 2 scala> val y: x.type = x y: x.type = 2 scala> stable(y) res3: x.type = 2 --- .../scala/reflect/internal/Types.scala | 21 +++++++++++++------ .../internal/tpe/TypeConstraints.scala | 5 +++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 464d1d897c00..c2b7088d2654 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -2807,18 +2807,27 @@ trait Types * See SI-5359. */ val bounds = tparam.info.bounds + /* We can seed the type constraint with the type parameter * bounds as long as the types are concrete. This should lower * the complexity of the search even if it doesn't improve * any results. */ - if (propagateParameterBoundsToTypeVars) { - val exclude = bounds.isEmptyBounds || (bounds exists typeIsNonClassType) + val constr = + if (propagateParameterBoundsToTypeVars) { + val exclude = bounds.isEmptyBounds || (bounds exists typeIsNonClassType) - if (exclude) new TypeConstraint - else TypeVar.trace("constraint", "For " + tparam.fullLocationString)(new TypeConstraint(bounds)) - } - else new TypeConstraint + if (exclude) new TypeConstraint + else TypeVar.trace("constraint", "For " + tparam.fullLocationString)(new TypeConstraint(bounds)) + } + else new TypeConstraint + + // TODO: this assumes covariance.. we should look at bounds.lo for contravariance + // can we plug this in where variance flipping is dealt with for us already? + def precludesWidening(tp: Type) = settings.Xexperimental && (tp.isStable || tp.contains(SingletonClass)) + if (precludesWidening(bounds.hi)) constr.stopWidening() + + constr } def untouchable(tparam: Symbol): TypeVar = createTypeVar(tparam, untouchable = true) def apply(tparam: Symbol): TypeVar = createTypeVar(tparam, untouchable = false) diff --git a/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala b/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala index c1c43178e519..9071d6ce5cb5 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala @@ -93,6 +93,7 @@ private[internal] trait TypeConstraints { def loBounds: List[Type] = if (numlo == NoType) lobounds else numlo :: lobounds def hiBounds: List[Type] = if (numhi == NoType) hibounds else numhi :: hibounds def avoidWiden: Boolean = avoidWidening + def stopWidening(): Unit = avoidWidening = true def addLoBound(tp: Type, isNumericBound: Boolean = false) { // For some reason which is still a bit fuzzy, we must let Nothing through as @@ -117,9 +118,9 @@ private[internal] trait TypeConstraints { } def checkWidening(tp: Type) { - if(tp.isStable) avoidWidening = true + if(tp.isStable) stopWidening() else tp match { - case HasTypeMember(_, _) => avoidWidening = true + case HasTypeMember(_, _) => stopWidening() case _ => } } From 4cade29c5fc8be7accce844fe504bfd7903355ca Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Nov 2014 10:55:22 +0100 Subject: [PATCH 13/21] isSuitableLiteralType --- src/reflect/scala/reflect/internal/Constants.scala | 1 + src/reflect/scala/reflect/internal/Types.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Constants.scala b/src/reflect/scala/reflect/internal/Constants.scala index 85d0efdcba79..904b30326b8f 100644 --- a/src/reflect/scala/reflect/internal/Constants.scala +++ b/src/reflect/scala/reflect/internal/Constants.scala @@ -60,6 +60,7 @@ trait Constants extends api.Constants { def isFloatRange: Boolean = ByteTag <= tag && tag <= FloatTag def isNumeric: Boolean = ByteTag <= tag && tag <= DoubleTag def isNonUnitAnyVal = BooleanTag <= tag && tag <= DoubleTag + def isSuitableLiteralType = BooleanTag <= tag && tag <= NullTag def isAnyVal = UnitTag <= tag && tag <= DoubleTag def tpe: Type = tag match { diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index c2b7088d2654..59a95b482abd 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1841,7 +1841,7 @@ trait Types abstract case class ConstantType(value: Constant) extends SingletonType with ConstantTypeApi { assert(underlying.typeSymbol != UnitClass) - override def underlying: Type = if (settings.Xexperimental) LiteralType(value) else value.tpe // SIP-23 + override def underlying: Type = if (settings.Xexperimental && value.isSuitableLiteralType) LiteralType(value) else value.tpe // SIP-23 override def isTrivial: Boolean = true override def deconst: Type = underlying.deconst override def safeToString: String = From 63c6b080106f32eb0284f8fe430d03ef035c2679 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Nov 2014 10:55:37 +0100 Subject: [PATCH 14/21] widen when checking for typeref --- src/reflect/scala/reflect/internal/Definitions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7e2d1244864d..3ea8a7c83b0d 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1357,7 +1357,7 @@ trait Definitions extends api.StandardDefinitions { else boxedClass.map(kvp => (kvp._2: Symbol, kvp._1)).getOrElse(sym, NoSymbol) /** Is type's symbol a numeric value class? */ - def isNumericValueType(tp: Type): Boolean = tp match { + def isNumericValueType(tp: Type): Boolean = tp.widen match { case TypeRef(_, sym, _) => isNumericValueClass(sym) case _ => false } From 0de90b561c1fbc5b34befe95acca8b7373f102ed Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Nov 2014 10:55:05 +0100 Subject: [PATCH 15/21] widenIfNecessary --- .../scala/tools/nsc/typechecker/Namers.scala | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index a1de5e303bc9..9da63035dcbe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -828,7 +828,30 @@ 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) + } + +// private[this] val sip23 = settings.Xexperimental.value + + /** + * 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 @@ -841,35 +864,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 (settings.Xexperimental) { // 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. */ @@ -879,7 +906,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 } From cb8007041f43d9c4af4e69f66865926cd05a825b Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Nov 2014 10:55:56 +0100 Subject: [PATCH 16/21] simplify subtyping of SingletonType? --- src/reflect/scala/reflect/internal/tpe/TypeComparers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala index 2ebd3bf3825c..3de1656988ae 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala @@ -372,8 +372,8 @@ trait TypeComparers { private def isSubType2(tp1: Type, tp2: Type, depth: Depth): Boolean = { def retry(lhs: Type, rhs: Type) = ((lhs ne tp1) || (rhs ne tp2)) && isSubType(lhs, rhs, depth) - // TODO SIP-23: should this just be tp1.isInstanceOf[SingletonType] && tp2.isInstanceOf[SingletonType] - if (isSingleType(tp1) && isSingleType(tp2) || isConstantType(tp1) && isConstantType(tp2)) + // TODO SIP-23 + if (tp1.isInstanceOf[SingletonType] && tp2.isInstanceOf[SingletonType]) return (tp1 =:= tp2) || isThisAndSuperSubtype(tp1, tp2) || retry(tp1.underlying, tp2) if (tp1.isHigherKinded || tp2.isHigherKinded) From 11d5730a2b980898c8b1e7c29c8ca2c3cfe3bd91 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Nov 2014 12:26:41 +0100 Subject: [PATCH 17/21] sip23 condition this bootstraps --- src/compiler/scala/tools/nsc/Global.scala | 3 +++ src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 8 +++----- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 4 +--- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- src/reflect/scala/reflect/internal/SymbolTable.scala | 1 + src/reflect/scala/reflect/internal/Types.scala | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 430424d0f86d..9d9db6f9791a 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -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 = true //settings.Xexperimental.value + /** Switch to turn on detailed type logs */ var printTypings = settings.Ytyperdebug.value diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 1f4072c51483..d71a55bc0ed3 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -339,8 +339,6 @@ self => } private def inScalaRootPackage = inScalaPackage && currentPackage == "scala" - // 42.type aka SIP-23 - private[this] lazy val parseLiteralSingletonTypes = settings.Xexperimental.value def parseStartRule: () => Tree @@ -677,7 +675,7 @@ self => def isExprIntro: Boolean = isExprIntroToken(in.token) - def isTypeIntroToken(token: Token): Boolean = (parseLiteralSingletonTypes && isLiteralToken(token)) || (token match { + def isTypeIntroToken(token: Token): Boolean = (sip23 && isLiteralToken(token)) || (token match { case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER | USCORE | LPAREN | AT => true case _ => false @@ -943,7 +941,7 @@ self => simpleTypeRest(in.token match { case LPAREN => atPos(start)(makeTupleType(inParens(types()))) case USCORE => wildcardType(in.skipToken()) - case tok if parseLiteralSingletonTypes && isLiteralToken(tok) => atPos(start){SingletonTypeTree(literal())} // SIP-23 + case tok if sip23 && isLiteralToken(tok) => atPos(start){SingletonTypeTree(literal())} // SIP-23 case _ => path(thisOK = false, typeOK = true) match { case r @ SingletonTypeTree(_) => r @@ -1026,7 +1024,7 @@ self => mkOp(infixType(InfixMode.RightOp)) } // SIP-23 - def isNegatedLiteralType = parseLiteralSingletonTypes && ( + 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 diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 9da63035dcbe..e490dd4afd95 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -848,8 +848,6 @@ trait Namers extends MethodSynthesis { loop(tp) } -// private[this] val sip23 = settings.Xexperimental.value - /** * This method has a big impact on the eventual compiled code. * At this point many values have the most specific possible @@ -865,7 +863,7 @@ trait Namers extends MethodSynthesis { * whether it is otherwise redundant (such as in a singleton.) */ private def widenIfNecessary(sym: Symbol, tpe: Type, pt: Type): Type = - if (settings.Xexperimental) { // SIP-23 + if (sip23) { // SIP-23 // TODO: spec -- this is a crucial part of type inference // NOTES: // - Can we widen less? (E.g., for local definitions.) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 83b47475ae92..f5553919b7f8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5179,7 +5179,7 @@ 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 (settings.Xexperimental) WildcardType else AnyRefTpe + val pt = if (sip23) WildcardType else AnyRefTpe val refTyped = context.withImplicitsDisabled { typed(tree.ref, MonoQualifierModes | mode.onlyTypePat, pt) diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index ed5c68fe82f7..0f85d63511b7 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -52,6 +52,7 @@ abstract class SymbolTable extends macros.Universe val gen = new InternalTreeGen { val global: SymbolTable.this.type = SymbolTable.this } def log(msg: => AnyRef): Unit + def sip23: Boolean = false // temporary, overriden in Global protected def elapsedMessage(msg: String, start: Long) = msg + " in " + (TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) - start) + "ms" diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 59a95b482abd..15e302fcbdf1 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1841,7 +1841,7 @@ trait Types abstract case class ConstantType(value: Constant) extends SingletonType with ConstantTypeApi { assert(underlying.typeSymbol != UnitClass) - override def underlying: Type = if (settings.Xexperimental && value.isSuitableLiteralType) LiteralType(value) else value.tpe // SIP-23 + override def underlying: Type = if (sip23 && value.isSuitableLiteralType) LiteralType(value) else value.tpe // SIP-23 override def isTrivial: Boolean = true override def deconst: Type = underlying.deconst override def safeToString: String = From b85931eca426184518c33e2ca33df016711f690f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 17 Nov 2014 18:05:20 +0100 Subject: [PATCH 18/21] sip23: numericLub --- src/reflect/scala/reflect/internal/tpe/GlbLubs.scala | 7 +++++-- test/files/pos/sip23_numericLub.scala | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 test/files/pos/sip23_numericLub.scala diff --git a/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala b/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala index 123b44aa05cc..1d174a6fa5c7 100644 --- a/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala +++ b/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala @@ -257,10 +257,13 @@ private[internal] trait GlbLubs { lub(tps) ) + // Need to widen result when using isNumericSubType to compare. + // isNumericSubType considers types after dealiasWiden, so should perform same transform on return. + // Example unit test: `numericLub(0, 1) == Int` (without the dealiasWiden one of the types would be returned as-is...) def numericLub(ts: List[Type]) = ts reduceLeft ((t1, t2) => - if (isNumericSubType(t1, t2)) t2 - else if (isNumericSubType(t2, t1)) t1 + if (isNumericSubType(t1, t2)) t2.dealiasWiden + else if (isNumericSubType(t2, t1)) t1.dealiasWiden else IntTpe) private val _lubResults = new mutable.HashMap[(Depth, List[Type]), Type] diff --git a/test/files/pos/sip23_numericLub.scala b/test/files/pos/sip23_numericLub.scala new file mode 100644 index 000000000000..b156a81ed083 --- /dev/null +++ b/test/files/pos/sip23_numericLub.scala @@ -0,0 +1,3 @@ +class C { + def foo(x: Boolean) = if (x) 1 else 0 +} \ No newline at end of file From 61a16f54b64d1bf020f36c2a6b4baff58c6ec70d Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 17 Nov 2014 20:47:19 +0100 Subject: [PATCH 19/21] asInstanceOf for singleton types ``` scala> val x = "a" x: "a" = a scala> x.asInstanceOf["a"] res1: "a" = a scala> "1".asInstanceOf["2"] x asInstanceOf Throwable java.lang.ClassCastException ... 33 elided scala> "1".asInstanceOf["1"] res3: "1" = 1 scala> 1.asInstanceOf[1] res4: 1 = 1 scala> 1.asInstanceOf[2] x asInstanceOf Throwable java.lang.ClassCastException ... 33 elided ``` --- .../scala/tools/nsc/transform/Erasure.scala | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index b3f0c52aeedc..0d8f0650c9eb 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -869,6 +869,18 @@ abstract class Erasure extends AddInterfaces * - Reset all other type attributes to null, thus enforcing a retyping. */ private val preTransformer = new TypingTransformer(unit) { + // TODO: since the spec defines instanceOf checks in terms of pattern matching, + // this extractor should share code with TypeTestTreeMaker + object SingletonInstanceCheck { + def unapply(pt: Type): Option[(TermSymbol, Tree)] = pt match { + case SingleType(_, _) | LiteralType(_) | ThisType(_) | SuperType(_, _) => + val cmpOp = if (pt <:< AnyValTpe) Any_equals else Object_eq + val cmpArg = gen.mkAttributedQualifier(pt) + Some((cmpOp, cmpArg)) + case _ => + None + } + } private def preEraseNormalApply(tree: Apply) = { val fn = tree.fun @@ -881,12 +893,22 @@ abstract class Erasure extends AddInterfaces def preEraseAsInstanceOf = { (fn: @unchecked) match { case TypeApply(Select(qual, _), List(targ)) => - if (qual.tpe <:< targ.tpe) - atPos(tree.pos) { Typed(qual, TypeTree(targ.tpe)) } - else if (isNumericValueClass(qual.tpe.typeSymbol) && isNumericValueClass(targ.tpe.typeSymbol)) - atPos(tree.pos)(numericConversion(qual, targ.tpe.typeSymbol)) - else - tree + targ.tpe match { + case argTp@SingletonInstanceCheck(cmpOp, cmpArg) => + atPos(tree.pos) { + gen.evalOnce(qual, currentOwner, currentUnit) { qual => + If(Apply(Select(qual(), cmpOp), List(cmpArg)), + Typed(qual(), TypeTree(argTp)), + Throw(ClassCastExceptionClass.tpe_*)) + } + } + case argTp if qual.tpe <:< argTp => + atPos(tree.pos) { Typed(qual, TypeTree(argTp)) } + case argTp if isNumericValueClass(qual.tpe.typeSymbol) && isNumericValueClass(argTp.typeSymbol) => + atPos(tree.pos)(numericConversion(qual, argTp.typeSymbol)) + case _ => + tree + } } // todo: also handle the case where the singleton type is buried in a compound } @@ -904,11 +926,8 @@ abstract class Erasure extends AddInterfaces List(TypeTree(tp) setPos targ.pos)) setPos fn.pos, List()) setPos tree.pos targ.tpe match { - 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))) - } + case SingletonInstanceCheck(cmpOp, cmpArg) => + atPos(tree.pos) { Apply(Select(qual, cmpOp), List(cmpArg)) } case RefinedType(parents, decls) if (parents.length >= 2) => gen.evalOnce(qual, currentOwner, unit) { q => // Optimization: don't generate isInstanceOf tests if the static type From 1724a619f90ac4bf94168f01d6ca0f7989a6efd5 Mon Sep 17 00:00:00 2001 From: George Leontiev Date: Sun, 23 Nov 2014 21:53:07 +0100 Subject: [PATCH 20/21] Unit-test LUBs. --- test/junit/scala/reflect/internal/TypesTest.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/junit/scala/reflect/internal/TypesTest.scala b/test/junit/scala/reflect/internal/TypesTest.scala index 95194ef0a453..da47ab89491c 100644 --- a/test/junit/scala/reflect/internal/TypesTest.scala +++ b/test/junit/scala/reflect/internal/TypesTest.scala @@ -32,4 +32,15 @@ class TypesTest { val uniquelyNarrowed2 = refinedType(boolWithString1narrow2 :: Nil, NoSymbol) assert(uniquelyNarrowed1 =:= uniquelyNarrowed2) } + + @Test + def testLub(): Unit = { + assert(lub(List()) =:= NothingTpe) + assert(lub(List(LiteralType(Constant(0)), LiteralType(Constant(1)))) =:= IntTpe) + assert(lub(List(LiteralType(Constant(1)), LiteralType(Constant(1)), LiteralType(Constant(1)))) =:= LiteralType(Constant(1))) + assert(lub(List(LiteralType(Constant("a")), LiteralType(Constant("b")))) =:= StringTpe) + assert(lub(List(LiteralType(Constant("a")), LiteralType(Constant("a")))) =:= LiteralType(Constant("a"))) + assert(lub(List(typeOf[Class[String]], typeOf[Class[String]])) =:= typeOf[Class[String]]) + assert(lub(List(typeOf[Class[String]], typeOf[Class[Object]])) =:= typeOf[Class[_ >: String <: Object]]) + } } From a5a59ad1df2628656dff3714e2d3dcf0c14c0832 Mon Sep 17 00:00:00 2001 From: George Leontiev Date: Fri, 28 Nov 2014 11:31:42 +0100 Subject: [PATCH 21/21] Provide some more interesting combinations. --- .../scala/reflect/internal/TypesTest.scala | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/test/junit/scala/reflect/internal/TypesTest.scala b/test/junit/scala/reflect/internal/TypesTest.scala index da47ab89491c..d27fe4178e6b 100644 --- a/test/junit/scala/reflect/internal/TypesTest.scala +++ b/test/junit/scala/reflect/internal/TypesTest.scala @@ -34,13 +34,43 @@ class TypesTest { } @Test - def testLub(): Unit = { - assert(lub(List()) =:= NothingTpe) - assert(lub(List(LiteralType(Constant(0)), LiteralType(Constant(1)))) =:= IntTpe) - assert(lub(List(LiteralType(Constant(1)), LiteralType(Constant(1)), LiteralType(Constant(1)))) =:= LiteralType(Constant(1))) - assert(lub(List(LiteralType(Constant("a")), LiteralType(Constant("b")))) =:= StringTpe) - assert(lub(List(LiteralType(Constant("a")), LiteralType(Constant("a")))) =:= LiteralType(Constant("a"))) - assert(lub(List(typeOf[Class[String]], typeOf[Class[String]])) =:= typeOf[Class[String]]) - assert(lub(List(typeOf[Class[String]], typeOf[Class[Object]])) =:= typeOf[Class[_ >: String <: Object]]) + def testSameTypesLub(): Unit = { + def testSameType(tpe: Type, num: Int = 5) = assert(lub(List.fill(num)(tpe)) =:= tpe) + + testSameType(IntTpe) + testSameType(StringTpe) + testSameType(typeOf[Class[String]]) + testSameType(LiteralType(Constant(1))) + testSameType(LiteralType(Constant("test"))) + } + + @Test + def testTypesLub(): Unit = { + val interestingCombos: Map[Type, List[List[Type]]] = Map( + IntTpe -> List(List(LiteralType(Constant(0)), LiteralType(Constant(1))), + List(LiteralType(Constant(0)), IntTpe), + List(ConstantType(Constant(0)), IntTpe), + List(ConstantType(Constant(0)), LiteralType(Constant(1))), + List(ConstantType(Constant(1)), LiteralType(Constant(1))), // Should this be LiteralType(Constant(1))? + + List(LiteralType(Constant(1)), ConstantType(Constant(1))), // Same here + List(LiteralType(Constant(0)), ConstantType(Constant(1)))), + StringTpe -> List(List(LiteralType(Constant("a")), LiteralType(Constant("b"))), + List(LiteralType(Constant("a")), StringTpe), + List(ConstantType(Constant("a")), LiteralType(Constant("a"))), // Should this be LiteralType(Constant("a"))? + List(LiteralType(Constant("a")), ConstantType(Constant("a"))), // Same here + List(ConstantType(Constant("a")), StringTpe), + List(ConstantType(Constant("a")), LiteralType(Constant("b"))), + List(ConstantType(Constant("a")), LiteralType(Constant("b")))), + LiteralType(Constant(1)) -> List(List(LiteralType(Constant(1)), LiteralType(Constant(1)))), + LiteralType(Constant("a")) -> List(List(LiteralType(Constant("a")), LiteralType(Constant("a")))), + AnyValTpe -> List(List(LiteralType(Constant(1)), IntTpe, DoubleTpe)), + typeOf[Class[String]] -> List(List(typeOf[Class[String]], typeOf[Class[String]])), + typeOf[Class[_ >: String <: Object]] -> List(List(typeOf[Class[String]], typeOf[Class[Object]])) + ) + + interestingCombos foreach { case (result, checks) => + checks.foreach(check => assert(lub(check) =:= result)) + } } }