From ec539184f388649163013ef62d4ec49835933fcc Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 22 Nov 2017 21:26:56 +0100 Subject: [PATCH 1/2] Fix type inference with scala.Singleton When a type variable is upper bounded by scala.Singleton, its instantiation should not be widened --- .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 8 +++++--- tests/pos/singletontrait.scala | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 tests/pos/singletontrait.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 6c75ee82a851..9cd6902f03e7 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -271,9 +271,11 @@ trait ConstraintHandling { var inst = approximation(param, fromBelow).simplified // Then, approximate by (1.) - (3.) and simplify as follows. - // 1. If instance is from below and is a singleton type, yet - // upper bound is not a singleton type, widen the instance. - if (fromBelow && isSingleton(inst) && !isSingleton(upperBound)) + // 1. If instance is from below and is a singleton type, yet upper bound is + // not a singleton type or a reference to `scala.Singleton`, widen the + // instance. + if (fromBelow && isSingleton(inst) && !isSingleton(upperBound) + && !upperBound.isRef(defn.SingletonClass)) inst = inst.widen // 2. If instance is from below and is a fully-defined union type, yet upper bound diff --git a/tests/pos/singletontrait.scala b/tests/pos/singletontrait.scala new file mode 100644 index 000000000000..6b282a610cee --- /dev/null +++ b/tests/pos/singletontrait.scala @@ -0,0 +1,5 @@ +object Test { + def foo[T <: Singleton](x: T): T = x + + val a: 1 = foo(1) +} From 3f61cb594eda9428978ec0f830c12124e0add47d Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Mon, 27 Nov 2017 14:35:41 +0100 Subject: [PATCH 2/2] Add support for scala.Symbol literal singleton types This brings us closer to fully supporting SIP-23. Bump the version of TASTY to 1.1, adding support for symbol literals is a backwards-compatible change. The sip23-* tests are adapted from https://github.com/scala/scala/pull/5310, the only difference is that we do not currently support the `T {}` trick to avoid widening when inferring types. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 4 +- .../src/dotty/tools/dotc/core/Constants.scala | 58 ++++++++++--------- .../dotty/tools/dotc/core/Definitions.scala | 8 ++- .../tools/dotc/core/tasty/TastyFormat.scala | 5 +- .../tools/dotc/core/tasty/TreePickler.scala | 3 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 + .../src/dotty/tools/dotc/parsing/Tokens.scala | 4 +- .../tools/dotc/printing/PlainPrinter.scala | 1 + .../dotty/tools/dotc/transform/Erasure.scala | 15 +++-- .../src/dotty/tools/dotc/typer/ReTyper.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- docs/docs/internals/syntax.md | 2 +- tests/neg/singletons.scala | 2 - tests/neg/sip23-symbols.scala | 25 ++++++++ tests/pickling/literals.scala | 9 +++ tests/pos/sip23-symbols.scala | 25 ++++++++ 16 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 tests/neg/sip23-symbols.scala create mode 100644 tests/pickling/literals.scala create mode 100644 tests/pos/sip23-symbols.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3a393a354fe6..621a98a50a17 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1029,9 +1029,7 @@ object desugar { val desugared = tree match { case SymbolLit(str) => - Apply( - ref(defn.SymbolClass.companionModule.termRef), - Literal(Constant(str)) :: Nil) + Literal(Constant(scala.Symbol(str))) case InterpolatedString(id, segments) => val strs = segments map { case ts: Thicket => ts.trees.head diff --git a/compiler/src/dotty/tools/dotc/core/Constants.scala b/compiler/src/dotty/tools/dotc/core/Constants.scala index ed388b7ecaea..a40ef09a894d 100644 --- a/compiler/src/dotty/tools/dotc/core/Constants.scala +++ b/compiler/src/dotty/tools/dotc/core/Constants.scala @@ -21,26 +21,28 @@ object Constants { final val ClazzTag = 12 // For supporting java enumerations inside java annotations (see ClassfileParser) final val EnumTag = 13 + final val ScalaSymbolTag = 14 case class Constant(value: Any) extends printing.Showable { import java.lang.Double.doubleToRawLongBits import java.lang.Float.floatToRawIntBits val tag: Int = value match { - case null => NullTag - case x: Unit => UnitTag - case x: Boolean => BooleanTag - case x: Byte => ByteTag - case x: Short => ShortTag - case x: Int => IntTag - case x: Long => LongTag - case x: Float => FloatTag - case x: Double => DoubleTag - case x: String => StringTag - case x: Char => CharTag - case x: Type => ClazzTag - case x: Symbol => EnumTag - case _ => throw new Error("bad constant value: " + value + " of class " + value.getClass) + case null => NullTag + case x: Unit => UnitTag + case x: Boolean => BooleanTag + case x: Byte => ByteTag + case x: Short => ShortTag + case x: Int => IntTag + case x: Long => LongTag + case x: Float => FloatTag + case x: Double => DoubleTag + case x: String => StringTag + case x: Char => CharTag + case x: Type => ClazzTag + case x: Symbol => EnumTag + case x: scala.Symbol => ScalaSymbolTag + case _ => throw new Error("bad constant value: " + value + " of class " + value.getClass) } def isByteRange: Boolean = isIntRange && Byte.MinValue <= intValue && intValue <= Byte.MaxValue @@ -54,19 +56,20 @@ object Constants { def isAnyVal = UnitTag <= tag && tag <= DoubleTag def tpe(implicit ctx: Context): Type = tag match { - case UnitTag => defn.UnitType - case BooleanTag => defn.BooleanType - case ByteTag => defn.ByteType - case ShortTag => defn.ShortType - case CharTag => defn.CharType - case IntTag => defn.IntType - case LongTag => defn.LongType - case FloatTag => defn.FloatType - case DoubleTag => defn.DoubleType - case StringTag => defn.StringType - case NullTag => defn.NullType - case ClazzTag => defn.ClassType(typeValue) - case EnumTag => defn.EnumType(symbolValue) + case UnitTag => defn.UnitType + case BooleanTag => defn.BooleanType + case ByteTag => defn.ByteType + case ShortTag => defn.ShortType + case CharTag => defn.CharType + case IntTag => defn.IntType + case LongTag => defn.LongType + case FloatTag => defn.FloatType + case DoubleTag => defn.DoubleType + case StringTag => defn.StringType + case NullTag => defn.NullType + case ClazzTag => defn.ClassType(typeValue) + case EnumTag => defn.EnumType(symbolValue) + case ScalaSymbolTag => defn.ScalaSymbolType } /** We need the equals method to take account of tags as well as values. @@ -206,6 +209,7 @@ object Constants { def typeValue: Type = value.asInstanceOf[Type] def symbolValue: Symbol = value.asInstanceOf[Symbol] + def scalaSymbolValue: scala.Symbol = value.asInstanceOf[scala.Symbol] /** * Consider two `NaN`s to be identical, despite non-equality diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 026f3ceae6cf..318a4ccee25a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -545,8 +545,12 @@ class Definitions { lazy val FunctionXXLType: TypeRef = ctx.requiredClassRef("scala.FunctionXXL") def FunctionXXLClass(implicit ctx: Context) = FunctionXXLType.symbol.asClass - lazy val SymbolType: TypeRef = ctx.requiredClassRef("scala.Symbol") - def SymbolClass(implicit ctx: Context) = SymbolType.symbol.asClass + lazy val ScalaSymbolType: TypeRef = ctx.requiredClassRef("scala.Symbol") + def ScalaSymbolClass(implicit ctx: Context) = ScalaSymbolType.symbol.asClass + def ScalaSymbolModule(implicit ctx: Context) = ScalaSymbolClass.companionModule + lazy val ScalaSymbolModule_applyR = ScalaSymbolModule.requiredMethodRef(nme.apply, List(StringType)) + def ScalaSymbolModule_apply(implicit ctx: Context) = ScalaSymbolModule_applyR.symbol + lazy val DynamicType: TypeRef = ctx.requiredClassRef("scala.Dynamic") def DynamicClass(implicit ctx: Context) = DynamicType.symbol.asClass lazy val OptionType: TypeRef = ctx.requiredClassRef("scala.Option") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 21f78d633e7d..1bb0163ffe0e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -147,6 +147,7 @@ Standard-Section: "ASTs" TopLevelStat* NULLconst CLASSconst Type ENUMconst Path + SYMBOLconst NameRef Type = Path TYPEREFdirect sym_ASTRef @@ -231,7 +232,7 @@ object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) val MajorVersion = 1 - val MinorVersion = 0 + val MinorVersion = 1 // Name tags @@ -315,6 +316,7 @@ object TastyFormat { final val STRINGconst = 77 final val IMPORTED = 78 final val RENAMED = 79 + final val SYMBOLconst = 80 // Cat. 3: tag AST @@ -575,6 +577,7 @@ object TastyFormat { case SUPER => "SUPER" case CLASSconst => "CLASSconst" case ENUMconst => "ENUMconst" + case SYMBOLconst => "SYMBOLconst" case SINGLETONtpt => "SINGLETONtpt" case SUPERtype => "SUPERtype" case TYPEARGtype => "TYPEARGtype" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0f727cf8f101..7006efc20a33 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -115,6 +115,9 @@ class TreePickler(pickler: TastyPickler) { case EnumTag => writeByte(ENUMconst) pickleType(c.symbolValue.termRef) + case ScalaSymbolTag => + writeByte(SYMBOLconst) + pickleName(c.scalaSymbolValue.name.toTermName) } def pickleType(tpe0: Type, richTypes: Boolean = false)(implicit ctx: Context): Unit = { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 09677d9490ec..038282a0e8a0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -331,6 +331,8 @@ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpi ConstantType(Constant(readType())) case ENUMconst => ConstantType(Constant(readTermRef().termSymbol)) + case SYMBOLconst => + ConstantType(Constant(scala.Symbol(readName().toString))) case BYNAMEtype => ExprType(readType()) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index ae447a22c7b1..3a0655e72f75 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -201,8 +201,8 @@ object Tokens extends TokensCommon { final val allTokens = tokenRange(minToken, maxToken) - final val simpleLiteralTokens = tokenRange(CHARLIT, STRINGLIT) | BitSet(TRUE, FALSE) - final val literalTokens = simpleLiteralTokens | BitSet(INTERPOLATIONID, SYMBOLLIT, NULL) + final val simpleLiteralTokens = tokenRange(CHARLIT, STRINGLIT) | BitSet(TRUE, FALSE, SYMBOLLIT) + final val literalTokens = simpleLiteralTokens | BitSet(INTERPOLATIONID, NULL) final val atomicExprTokens = literalTokens | identifierTokens | BitSet( USCORE, NULL, THIS, SUPER, TRUE, FALSE, RETURN, XMLSTART) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 447021e491f6..7b1c58667f8b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -463,6 +463,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case CharTag => literalText(s"'${escapedChar(const.charValue)}'") case LongTag => literalText(const.longValue.toString + "L") case EnumTag => literalText(const.symbolValue.name.toString) + case ScalaSymbolTag => literalText("'" + const.scalaSymbolValue.name.toString) case _ => literalText(String.valueOf(const.value)) } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index ecb40b52a910..e33e347dbfac 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -342,10 +342,17 @@ object Erasure { assignType(untpd.cpy.Typed(tree)(expr1, tpt1), tpt1) } - override def typedLiteral(tree: untpd.Literal)(implicit ctx: Context): Literal = - if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) - else if (tree.const.tag == Constants.ClazzTag) Literal(Constant(erasure(tree.const.typeValue))) - else super.typedLiteral(tree) + override def typedLiteral(tree: untpd.Literal)(implicit ctx: Context): Tree = + if (tree.typeOpt.isRef(defn.UnitClass)) + tree.withType(tree.typeOpt) + else if (tree.const.tag == Constants.ClazzTag) + Literal(Constant(erasure(tree.const.typeValue))) + else if (tree.const.tag == Constants.ScalaSymbolTag) + ref(defn.ScalaSymbolModule) + .select(defn.ScalaSymbolModule_apply) + .appliedTo(Literal(Constant(tree.const.scalaSymbolValue.name))) + else + super.typedLiteral(tree) /** Type check select nodes, applying the following rewritings exhaustively * on selections `e.m`, where `OT` is the type of the owner of `m` and `ET` diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 8298889a70f9..7031b7920f2a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -39,7 +39,7 @@ class ReTyper extends Typer { untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) } - override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal = + override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Tree = promote(tree) override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3c256edc513e..72f72bcb833a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -449,7 +449,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def typedLiteral(tree: untpd.Literal)(implicit ctx: Context) = track("typedLiteral") { + def typedLiteral(tree: untpd.Literal)(implicit ctx: Context): Tree = track("typedLiteral") { assignType(tree) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 4f4552cbf3e9..0afe98abdc58 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -99,9 +99,9 @@ SimpleLiteral ::= [‘-’] integerLiteral | booleanLiteral | characterLiteral | stringLiteral + | symbolLiteral Literal ::= SimpleLiteral | processedStringLiteral - | symbolLiteral | ‘null’ QualId ::= id {‘.’ id} diff --git a/tests/neg/singletons.scala b/tests/neg/singletons.scala index 502933cc48a9..c7ab2f20dc2d 100644 --- a/tests/neg/singletons.scala +++ b/tests/neg/singletons.scala @@ -5,7 +5,5 @@ object Test { val n: null = null // error: Null is not a legal singleton type // error: only classes can have declared but undefined members - val sym: 'sym = 'sym // error: Symbol is not a legal singleton type // error: only classes can have declared but undefined members - val foo: s"abc" = "abc" // error: not a legal singleton type // error: only classes can have declared but undefined members } diff --git a/tests/neg/sip23-symbols.scala b/tests/neg/sip23-symbols.scala new file mode 100644 index 000000000000..430cced5ffc7 --- /dev/null +++ b/tests/neg/sip23-symbols.scala @@ -0,0 +1,25 @@ +object Test { + val sym0 = 's + //sym0: Symbol + sym0: 's // error + + //val sym1: 's = 's + //sym1: Symbol + //sym1: 's + + //final val sym2 = 's + //sym2: Symbol + //sym2: 's + + def id[T](t: T): T = t + type Identity[T] = T + def narrow[T <: Singleton](t: T): Identity[T] = t + + final val sym3 = id('s) + //sym3: Symbol + sym3: 's // error + + //val sym4 = narrow('s) + //sym4: Symbol + //sym4: 's +} diff --git a/tests/pickling/literals.scala b/tests/pickling/literals.scala new file mode 100644 index 000000000000..003789eff1b9 --- /dev/null +++ b/tests/pickling/literals.scala @@ -0,0 +1,9 @@ +object Test { + val a: 1 = 1 + val b: 1L = 1L + val c: 1F = 1F + val d: 1.0 = 1.0 + val e: true = true + val f: '*' = '*' + val g: 'a = 'a +} diff --git a/tests/pos/sip23-symbols.scala b/tests/pos/sip23-symbols.scala new file mode 100644 index 000000000000..613029fa33d2 --- /dev/null +++ b/tests/pos/sip23-symbols.scala @@ -0,0 +1,25 @@ +object Test { + val sym0 = 's + sym0: Symbol + //sym0: 's + + val sym1: 's = 's + sym1: Symbol + sym1: 's + + final val sym2 = 's + sym2: Symbol + sym2: 's + + def id[T](t: T): T = t + type Identity[T] = T + def narrow[T <: Singleton](t: T): Identity[T] = t + + final val sym3 = id('s) + sym3: Symbol + //sym3: 's + + val sym4 = narrow('s) + sym4: Symbol + sym4: 's +}