Skip to content

Progress towards full SIP-23 support: correct inference for scala.Singleton, support for symbol literals #3565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 30, 2017
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: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 31 additions & 27 deletions compiler/src/dotty/tools/dotc/core/Constants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ Standard-Section: "ASTs" TopLevelStat*
NULLconst
CLASSconst Type
ENUMconst Path
SYMBOLconst NameRef

Type = Path
TYPEREFdirect sym_ASTRef
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
15 changes: 11 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ SimpleLiteral ::= [‘-’] integerLiteral
| booleanLiteral
| characterLiteral
| stringLiteral
| symbolLiteral
Literal ::= SimpleLiteral
| processedStringLiteral
| symbolLiteral
| ‘null’

QualId ::= id {‘.’ id}
Expand Down
2 changes: 0 additions & 2 deletions tests/neg/singletons.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
25 changes: 25 additions & 0 deletions tests/neg/sip23-symbols.scala
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 9 additions & 0 deletions tests/pickling/literals.scala
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions tests/pos/singletontrait.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
def foo[T <: Singleton](x: T): T = x

val a: 1 = foo(1)
}
25 changes: 25 additions & 0 deletions tests/pos/sip23-symbols.scala
Original file line number Diff line number Diff line change
@@ -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
}