From 1e13a92336979f633ccd10a0c2bb082ae4e6b75b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 May 2020 14:25:09 +0200 Subject: [PATCH 01/12] Fix #9011: Make single enum values inherit from Product --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 8 ++- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + library/src/scala/EnumValue.scala | 10 ++++ tests/run/i9011.scala | 57 +++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 library/src/scala/EnumValue.scala create mode 100644 tests/run/i9011.scala diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 50e32c20e5e9..89ed46baa379 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -124,7 +124,7 @@ object DesugarEnums { /** A creation method for a value of enum type `E`, which is defined as follows: * - * private def $new(_$ordinal: Int, $name: String) = new E { + * private def $new(_$ordinal: Int, $name: String) = new E with EnumValue { * def $ordinal = $tag * override def toString = $name * $values.register(this) @@ -135,7 +135,7 @@ object DesugarEnums { val toStringDef = toStringMeth(Ident(nme.nameDollar)) val creator = New(Template( constr = emptyConstructor, - parents = enumClassRef :: Nil, + parents = enumClassRef :: scalaDot(str.EnumValue.toTypeName) :: Nil, derived = Nil, self = EmptyValDef, body = List(ordinalDef, toStringDef) ++ registerCall @@ -286,7 +286,9 @@ object DesugarEnums { val (tag, scaffolding) = nextOrdinal(CaseKind.Object) val ordinalDef = ordinalMethLit(tag) val toStringDef = toStringMethLit(name.toString) - val impl1 = cpy.Template(impl)(body = List(ordinalDef, toStringDef) ++ registerCall) + val impl1 = cpy.Template(impl)( + parents = impl.parents :+ scalaDot(str.EnumValue.toTypeName), + body = List(ordinalDef, toStringDef) ++ registerCall) .withAttachment(ExtendsSingletonMirror, ()) val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods.withAddedFlags(EnumValue, span)) flatTree(scaffolding ::: vdef :: Nil).withSpan(span) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 45e23e9d4512..648e74a797d8 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -33,6 +33,7 @@ object StdNames { final val MODULE_INSTANCE_FIELD = "MODULE$" + final val EnumValue = "EnumValue" final val Function = "Function" final val ErasedFunction = "ErasedFunction" final val ContextFunction = "ContextFunction" diff --git a/library/src/scala/EnumValue.scala b/library/src/scala/EnumValue.scala new file mode 100644 index 000000000000..87436dbd786d --- /dev/null +++ b/library/src/scala/EnumValue.scala @@ -0,0 +1,10 @@ +package scala + +trait EnumValue extends Product: + override def canEqual(that: Any) = true + override def productArity: Int = 0 + override def productPrefix: String = toString + override def productElement(n: Int): Any = + throw IndexOutOfBoundsException(n.toString()) + override def productElementName(n: Int): String = + throw IndexOutOfBoundsException(n.toString()) diff --git a/tests/run/i9011.scala b/tests/run/i9011.scala new file mode 100644 index 000000000000..e5b488cb1b5d --- /dev/null +++ b/tests/run/i9011.scala @@ -0,0 +1,57 @@ +enum Opt[+T] derives Eq: + case Sm(t: T) + case Nn + +import scala.deriving._ +import scala.compiletime.{erasedValue, summonInline} + +trait Eq[T] { + def eqv(x: T, y: T): Boolean +} + +object Eq { + given Eq[Int] { + def eqv(x: Int, y: Int) = x == y + } + + inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match { + case _: Unit => Nil + case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts] + } + + def check(elem: Eq[_])(x: Any, y: Any): Boolean = + elem.asInstanceOf[Eq[Any]].eqv(x, y) + + def iterator[T](p: T) = p.asInstanceOf[Product].productIterator + + def eqSum[T](s: Mirror.SumOf[T], elems: List[Eq[_]]): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = { + val ordx = s.ordinal(x) + (s.ordinal(y) == ordx) && check(elems(ordx))(x, y) + } + } + + def eqProduct[T](p: Mirror.ProductOf[T], elems: List[Eq[_]]): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = + iterator(x).zip(iterator(y)).zip(elems.iterator).forall { + case ((x, y), elem) => check(elem)(x, y) + } + } + + inline given derived[T](using m: Mirror.Of[T]) as Eq[T] = { + val elemInstances = summonAll[m.MirroredElemTypes] + inline m match { + case s: Mirror.SumOf[T] => eqSum(s, elemInstances) + case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances) + } + } +} + +object Test extends App { + import Opt._ + val eqoi = summon[Eq[Opt[Int]]] + assert(eqoi.eqv(Sm(23), Sm(23))) + assert(eqoi.eqv(Nn, Nn)) +} \ No newline at end of file From ba466e90154a3c3890b8e18a2c6f0f356ea7b2b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 May 2020 14:21:19 +0200 Subject: [PATCH 02/12] Rename main objects in tests to _main This was done just because compiling with a non-empty classpaths leads to conflicts when main is used as an annotation. --- tests/fuzzy/b82054893e0db44e31ae82d696c19c1fbc7be55c.scala | 2 +- tests/pos/localmodules.scala | 2 +- tests/pos/t0002.scala | 2 +- tests/pos/t789.scala | 2 +- tests/pos/typealiases.scala | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/fuzzy/b82054893e0db44e31ae82d696c19c1fbc7be55c.scala b/tests/fuzzy/b82054893e0db44e31ae82d696c19c1fbc7be55c.scala index 1e57d871d58e..9247be34b235 100644 --- a/tests/fuzzy/b82054893e0db44e31ae82d696c19c1fbc7be55c.scala +++ b/tests/fuzzy/b82054893e0db44e31ae82d696c19c1fbc7be55c.scala @@ -1,4 +1,4 @@ -object main { +object _main { def i0 = { class i1 { private[i0] var i2: _ > 0 private def i3: List[Int] diff --git a/tests/pos/localmodules.scala b/tests/pos/localmodules.scala index 3e1600842c20..f681d34f3123 100644 --- a/tests/pos/localmodules.scala +++ b/tests/pos/localmodules.scala @@ -1,6 +1,6 @@ package test; -object main { +object _main { class a { diff --git a/tests/pos/t0002.scala b/tests/pos/t0002.scala index 4c58ed3f4f6d..b6caffb784d4 100644 --- a/tests/pos/t0002.scala +++ b/tests/pos/t0002.scala @@ -1,4 +1,4 @@ -object main { +object _main { def main(args: Array[String]) = { val b = true; while (b == true) { } diff --git a/tests/pos/t789.scala b/tests/pos/t789.scala index c453e229ac7e..f086f65633ca 100644 --- a/tests/pos/t789.scala +++ b/tests/pos/t789.scala @@ -1,4 +1,4 @@ -object main { // don't do this at home +object _main { // don't do this at home trait Impl diff --git a/tests/pos/typealiases.scala b/tests/pos/typealiases.scala index 93d1dce4dc31..c5bde6e2dbdd 100644 --- a/tests/pos/typealiases.scala +++ b/tests/pos/typealiases.scala @@ -11,7 +11,7 @@ trait Test[T] { def check2[S](xs: Array[S], c: Check[S]) = c(xs) } -object main extends Test[Int] { +object _main extends Test[Int] { val pair1 = (1,1) implicit def topair(x: Int): Tuple2[Int, Int] = (x,x) From 67f07869c2da60fa5e93f0d5ae146756ca8164ed Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 May 2020 15:38:26 +0200 Subject: [PATCH 03/12] Make all enum cases instances of EnumValue statically --- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 89ed46baa379..fafa62e2851e 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -306,7 +306,7 @@ object DesugarEnums { else { val (tag, scaffolding) = nextOrdinal(CaseKind.Simple) val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) - val vdef = ValDef(name, enumClassRef, creator).withMods(mods.withAddedFlags(EnumValue, span)) + val vdef = ValDef(name, TypeTree(), creator).withMods(mods.withAddedFlags(EnumValue, span)) flatTree(scaffolding ::: vdef :: Nil).withSpan(span) } } From 323d2287463e71f8a5f59e60985bec5db13b698a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 May 2020 15:58:46 +0200 Subject: [PATCH 04/12] Make EnumValue inherit Serializable --- library/src/scala/EnumValue.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/EnumValue.scala b/library/src/scala/EnumValue.scala index 87436dbd786d..70ed31b0f0ac 100644 --- a/library/src/scala/EnumValue.scala +++ b/library/src/scala/EnumValue.scala @@ -1,6 +1,6 @@ package scala -trait EnumValue extends Product: +trait EnumValue extends Product, Serializable: override def canEqual(that: Any) = true override def productArity: Int = 0 override def productPrefix: String = toString From 29ac3a6846b829b16cb862a8c2c7cbc8119742a0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 May 2020 18:26:16 +0200 Subject: [PATCH 05/12] Strip EnumValue parent from inferred types We now strip EnumValue parents from inferred types, unless they are required by the bound. This is analogous to widen unions and singletons. It should be generalized to more types, not just EnumValue. --- .../tools/dotc/core/ConstraintHandling.scala | 33 +++++++++++++++---- .../dotty/tools/dotc/core/Definitions.scala | 1 + tests/neg/enumvalues.scala | 11 +++++++ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 tests/neg/enumvalues.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 1c575b1c5f68..eedb89b57acc 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -300,6 +300,8 @@ trait ConstraintHandling[AbstractContext] { * (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint) * 2. If `inst` is a union type, approximate the union type from above by an intersection * of all common base types, provided the result is a subtype of `bound`. + * 3. If `inst` is an intersection with some protected base types, drop + * the protected base types from the intersection, provided the result is a subtype of `bound`. * * Don't do these widenings if `bound` is a subtype of `scala.Singleton`. * Also, if the result of these widenings is a TypeRef to a module class, @@ -309,26 +311,43 @@ trait ConstraintHandling[AbstractContext] { * At this point we also drop the @Repeated annotation to avoid inferring type arguments with it, * as those could leak the annotation to users (see run/inferred-repeated-result). */ - def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type = { - def widenOr(tp: Type) = { + def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type = + + def isProtected(tp: Type) = tp.typeSymbol == defn.EnumValueClass // for now, to be generalized later + + def dropProtected(tp: Type): Type = tp.dealias match + case tp @ AndType(tp1, tp2) => + if isProtected(tp1) then tp2 + else if isProtected(tp2) then tp1 + else tp.derivedAndType(dropProtected(tp1), dropProtected(tp2)) + case _ => + tp + + def widenProtected(tp: Type) = + val tpw = dropProtected(tp) + if (tpw ne tp) && (tpw <:< bound) then tpw else tp + + def widenOr(tp: Type) = val tpw = tp.widenUnion if (tpw ne tp) && (tpw <:< bound) then tpw else tp - } - def widenSingle(tp: Type) = { + + def widenSingle(tp: Type) = val tpw = tp.widenSingletons if (tpw ne tp) && (tpw <:< bound) then tpw else tp - } + def isSingleton(tp: Type): Boolean = tp match case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi) case _ => isSubTypeWhenFrozen(tp, defn.SingletonType) + val wideInst = - if isSingleton(bound) then inst else widenOr(widenSingle(inst)) + if isSingleton(bound) then inst + else widenProtected(widenOr(widenSingle(inst))) wideInst match case wideInst: TypeRef if wideInst.symbol.is(Module) => TermRef(wideInst.prefix, wideInst.symbol.sourceModule) case _ => wideInst.dropRepeatedAnnot - } + end widenInferred /** The instance type of `param` in the current constraint (which contains `param`). * If `fromBelow` is true, the instance type is the lub of the parameter's diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 379b18b77a02..5a2247739d97 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -639,6 +639,7 @@ class Definitions { @tu lazy val EnumClass: ClassSymbol = ctx.requiredClass("scala.Enum") @tu lazy val Enum_ordinal: Symbol = EnumClass.requiredMethod(nme.ordinal) + @tu lazy val EnumValueClass: ClassSymbol = ctx.requiredClass("scala.EnumValue") @tu lazy val EnumValuesClass: ClassSymbol = ctx.requiredClass("scala.runtime.EnumValues") @tu lazy val ProductClass: ClassSymbol = ctx.requiredClass("scala.Product") @tu lazy val Product_canEqual : Symbol = ProductClass.requiredMethod(nme.canEqual_) diff --git a/tests/neg/enumvalues.scala b/tests/neg/enumvalues.scala new file mode 100644 index 000000000000..835d82a1fe89 --- /dev/null +++ b/tests/neg/enumvalues.scala @@ -0,0 +1,11 @@ +enum Color: + case Red, Green, Blue + +@main def Test(c: Boolean) = + // These currently give errors. But maybe we should make the actual + // enum values carry the `EnumValue` trait, and only strip it from + // user-defined vals and defs? + val x: EnumValue = if c then Color.Red else Color.Blue // error // error + val y: EnumValue = Color.Green // error + + From 1003adb3592e69d5da12639e3d9c8869d2895974 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 May 2020 22:51:49 +0200 Subject: [PATCH 06/12] Update semanticDB checks --- tests/semanticdb/expect/Enums.expect.scala | 32 +++++++++++----------- tests/semanticdb/metac.expect | 18 +++++++++++- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/tests/semanticdb/expect/Enums.expect.scala b/tests/semanticdb/expect/Enums.expect.scala index 34a4a406425d..064269ed88ae 100644 --- a/tests/semanticdb/expect/Enums.expect.scala +++ b/tests/semanticdb/expect/Enums.expect.scala @@ -29,19 +29,19 @@ object Enums/*<-_empty_::Enums.*/: case Sunday/*<-_empty_::Enums.WeekDays.Sunday.*/ enum Coin/*<-_empty_::Enums.Coin#*/(value/*<-_empty_::Enums.Coin#value.*/: Int/*->scala::Int#*/): - case Penny/*<-_empty_::Enums.Coin.Penny.*/ extends Coin/*->_empty_::Enums.Coin#*/(1) - case Nickel/*<-_empty_::Enums.Coin.Nickel.*/ extends Coin/*->_empty_::Enums.Coin#*/(5) - case Dime/*<-_empty_::Enums.Coin.Dime.*/ extends Coin/*->_empty_::Enums.Coin#*/(10) - case Quarter/*<-_empty_::Enums.Coin.Quarter.*/ extends Coin/*->_empty_::Enums.Coin#*/(25) - case Dollar/*<-_empty_::Enums.Coin.Dollar.*/ extends Coin/*->_empty_::Enums.Coin#*/(100) + case Penny/*<-_empty_::Enums.Coin.Penny.*/ extends Coin/*->_empty_::Enums.Coin#*/(1)/*->scala::EnumValue#*/ + case Nickel/*<-_empty_::Enums.Coin.Nickel.*/ extends Coin/*->_empty_::Enums.Coin#*/(5)/*->scala::EnumValue#*/ + case Dime/*<-_empty_::Enums.Coin.Dime.*/ extends Coin/*->_empty_::Enums.Coin#*/(10)/*->scala::EnumValue#*/ + case Quarter/*<-_empty_::Enums.Coin.Quarter.*/ extends Coin/*->_empty_::Enums.Coin#*/(25)/*->scala::EnumValue#*/ + case Dollar/*<-_empty_::Enums.Coin.Dollar.*/ extends Coin/*->_empty_::Enums.Coin#*/(100)/*->scala::EnumValue#*/ enum Maybe/*<-_empty_::Enums.Maybe#*/[+A/*<-_empty_::Enums.Maybe#[A]*/]: case Just/*<-_empty_::Enums.Maybe.Just#*/(value/*<-_empty_::Enums.Maybe.Just#value.*/: A/*->_empty_::Enums.Maybe.Just#[A]*/) - case None/*<-_empty_::Enums.Maybe.None.*/ + case None/*<-_empty_::Enums.Maybe.None.*//*->scala::EnumValue#*/ enum Tag/*<-_empty_::Enums.Tag#*/[A/*<-_empty_::Enums.Tag#[A]*/]: - case IntTag/*<-_empty_::Enums.Tag.IntTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Int/*->scala::Int#*/] - case BooleanTag/*<-_empty_::Enums.Tag.BooleanTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Boolean/*->scala::Boolean#*/] + case IntTag/*<-_empty_::Enums.Tag.IntTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Int/*->scala::Int#*/]/*->scala::EnumValue#*/ + case BooleanTag/*<-_empty_::Enums.Tag.BooleanTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Boolean/*->scala::Boolean#*/]/*->scala::EnumValue#*/ enum <:_empty_::Enums.`<:<`.Refl#[C]*/ <:_empty_::Enums.`<:<`#*/ C/*->_empty_::Enums.`<:<`.Refl#[C]*/) @@ -59,11 +59,11 @@ object Enums/*<-_empty_::Enums.*/: def surfaceGravity/*<-_empty_::Enums.Planet#surfaceGravity().*/ = G/*->_empty_::Enums.Planet#G.*/ */*->scala::Double#`*`(+6).*/ mass/*->_empty_::Enums.Planet#mass.*/ //*->scala::Double#`::`(+6).*/ (radius/*->_empty_::Enums.Planet#radius.*/ */*->scala::Double#`*`(+6).*/ radius/*->_empty_::Enums.Planet#radius.*/) def surfaceWeight/*<-_empty_::Enums.Planet#surfaceWeight().*/(otherMass/*<-_empty_::Enums.Planet#surfaceWeight().(otherMass)*/: Double/*->scala::Double#*/) = otherMass/*->_empty_::Enums.Planet#surfaceWeight().(otherMass)*/ */*->scala::Double#`*`(+6).*/ surfaceGravity/*->_empty_::Enums.Planet#surfaceGravity().*/ - case Mercury/*<-_empty_::Enums.Planet.Mercury.*/ extends Planet/*->_empty_::Enums.Planet#*/(3.303e+23, 2.4397e6) - case Venus/*<-_empty_::Enums.Planet.Venus.*/ extends Planet/*->_empty_::Enums.Planet#*/(4.869e+24, 6.0518e6) - case Earth/*<-_empty_::Enums.Planet.Earth.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.976e+24, 6.37814e6) - case Mars/*<-_empty_::Enums.Planet.Mars.*/ extends Planet/*->_empty_::Enums.Planet#*/(6.421e+23, 3.3972e6) - case Jupiter/*<-_empty_::Enums.Planet.Jupiter.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.9e+27, 7.1492e7) - case Saturn/*<-_empty_::Enums.Planet.Saturn.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.688e+26, 6.0268e7) - case Uranus/*<-_empty_::Enums.Planet.Uranus.*/ extends Planet/*->_empty_::Enums.Planet#*/(8.686e+25, 2.5559e7) - case Neptune/*<-_empty_::Enums.Planet.Neptune.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.024e+26, 2.4746e7) + case Mercury/*<-_empty_::Enums.Planet.Mercury.*/ extends Planet/*->_empty_::Enums.Planet#*/(3.303e+23, 2.4397e6)/*->scala::EnumValue#*/ + case Venus/*<-_empty_::Enums.Planet.Venus.*/ extends Planet/*->_empty_::Enums.Planet#*/(4.869e+24, 6.0518e6)/*->scala::EnumValue#*/ + case Earth/*<-_empty_::Enums.Planet.Earth.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.976e+24, 6.37814e6)/*->scala::EnumValue#*/ + case Mars/*<-_empty_::Enums.Planet.Mars.*/ extends Planet/*->_empty_::Enums.Planet#*/(6.421e+23, 3.3972e6)/*->scala::EnumValue#*/ + case Jupiter/*<-_empty_::Enums.Planet.Jupiter.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.9e+27, 7.1492e7)/*->scala::EnumValue#*/ + case Saturn/*<-_empty_::Enums.Planet.Saturn.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.688e+26, 6.0268e7)/*->scala::EnumValue#*/ + case Uranus/*<-_empty_::Enums.Planet.Uranus.*/ extends Planet/*->_empty_::Enums.Planet#*/(8.686e+25, 2.5559e7)/*->scala::EnumValue#*/ + case Neptune/*<-_empty_::Enums.Planet.Neptune.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.024e+26, 2.4746e7)/*->scala::EnumValue#*/ diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 09ac1176b4af..319b8da7c44f 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -636,7 +636,7 @@ Uri => Enums.scala Text => empty Language => Scala Symbols => 157 entries -Occurrences => 187 entries +Occurrences => 203 entries Symbols: _empty_/Enums. => final object Enums @@ -855,18 +855,23 @@ Occurrences: [31:9..31:14): Penny <- _empty_/Enums.Coin.Penny. [31:26..31:30): Coin -> _empty_/Enums.Coin# [31:30..31:30): -> _empty_/Enums.Coin#``(). +[31:33..31:33): -> scala/EnumValue# [32:9..32:15): Nickel <- _empty_/Enums.Coin.Nickel. [32:26..32:30): Coin -> _empty_/Enums.Coin# [32:30..32:30): -> _empty_/Enums.Coin#``(). +[32:33..32:33): -> scala/EnumValue# [33:9..33:13): Dime <- _empty_/Enums.Coin.Dime. [33:26..33:30): Coin -> _empty_/Enums.Coin# [33:30..33:30): -> _empty_/Enums.Coin#``(). +[33:34..33:34): -> scala/EnumValue# [34:9..34:16): Quarter <- _empty_/Enums.Coin.Quarter. [34:26..34:30): Coin -> _empty_/Enums.Coin# [34:30..34:30): -> _empty_/Enums.Coin#``(). +[34:34..34:34): -> scala/EnumValue# [35:9..35:15): Dollar <- _empty_/Enums.Coin.Dollar. [35:26..35:30): Coin -> _empty_/Enums.Coin# [35:30..35:30): -> _empty_/Enums.Coin#``(). +[35:35..35:35): -> scala/EnumValue# [37:7..37:12): Maybe <- _empty_/Enums.Maybe# [37:12..37:16): <- _empty_/Enums.Maybe#``(). [37:14..37:15): A <- _empty_/Enums.Maybe#[A] @@ -876,6 +881,7 @@ Occurrences: [38:21..38:22): A -> _empty_/Enums.Maybe.Just#[A] [39:4..39:4): -> _empty_/Enums.Maybe#``(). [39:9..39:13): None <- _empty_/Enums.Maybe.None. +[39:13..39:13): -> scala/EnumValue# [41:7..41:10): Tag <- _empty_/Enums.Tag# [41:10..41:13): <- _empty_/Enums.Tag#``(). [41:11..41:12): A <- _empty_/Enums.Tag#[A] @@ -883,10 +889,12 @@ Occurrences: [42:24..42:27): Tag -> _empty_/Enums.Tag# [42:28..42:31): Int -> scala/Int# [42:32..42:32): -> _empty_/Enums.Tag#``(). +[42:32..42:32): -> scala/EnumValue# [43:9..43:19): BooleanTag <- _empty_/Enums.Tag.BooleanTag. [43:28..43:31): Tag -> _empty_/Enums.Tag# [43:32..43:39): Boolean -> scala/Boolean# [43:40..43:40): -> _empty_/Enums.Tag#``(). +[43:40..43:40): -> scala/EnumValue# [45:7..45:10): <:< <- _empty_/Enums.`<:<`# [45:10..45:17): <- _empty_/Enums.`<:<`#``(). [45:12..45:13): A <- _empty_/Enums.`<:<`#[A] @@ -964,27 +972,35 @@ Occurrences: [61:9..61:16): Mercury <- _empty_/Enums.Planet.Mercury. [61:25..61:31): Planet -> _empty_/Enums.Planet# [61:31..61:31): -> _empty_/Enums.Planet#``(). +[61:52..61:52): -> scala/EnumValue# [62:9..62:14): Venus <- _empty_/Enums.Planet.Venus. [62:25..62:31): Planet -> _empty_/Enums.Planet# [62:31..62:31): -> _empty_/Enums.Planet#``(). +[62:52..62:52): -> scala/EnumValue# [63:9..63:14): Earth <- _empty_/Enums.Planet.Earth. [63:25..63:31): Planet -> _empty_/Enums.Planet# [63:31..63:31): -> _empty_/Enums.Planet#``(). +[63:53..63:53): -> scala/EnumValue# [64:9..64:13): Mars <- _empty_/Enums.Planet.Mars. [64:25..64:31): Planet -> _empty_/Enums.Planet# [64:31..64:31): -> _empty_/Enums.Planet#``(). +[64:52..64:52): -> scala/EnumValue# [65:9..65:16): Jupiter <- _empty_/Enums.Planet.Jupiter. [65:25..65:31): Planet -> _empty_/Enums.Planet# [65:31..65:31): -> _empty_/Enums.Planet#``(). +[65:52..65:52): -> scala/EnumValue# [66:9..66:15): Saturn <- _empty_/Enums.Planet.Saturn. [66:25..66:31): Planet -> _empty_/Enums.Planet# [66:31..66:31): -> _empty_/Enums.Planet#``(). +[66:52..66:52): -> scala/EnumValue# [67:9..67:15): Uranus <- _empty_/Enums.Planet.Uranus. [67:25..67:31): Planet -> _empty_/Enums.Planet# [67:31..67:31): -> _empty_/Enums.Planet#``(). +[67:52..67:52): -> scala/EnumValue# [68:9..68:16): Neptune <- _empty_/Enums.Planet.Neptune. [68:25..68:31): Planet -> _empty_/Enums.Planet# [68:31..68:31): -> _empty_/Enums.Planet#``(). +[68:52..68:52): -> scala/EnumValue# expect/EtaExpansion.scala ------------------------- From 9a887fe83f492a07f59699dd1da5d4c1e4603b12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 May 2020 10:14:03 +0200 Subject: [PATCH 07/12] Make Enum extend Product and Serializable so that these traits do not leak into inferred types of unions of cases. --- library/src-bootstrapped/scala/Enum.scala | 9 +++++++++ library/{src/scala => src-non-bootstrapped}/Enum.scala | 3 +-- library/src/scala/EnumValue.scala | 6 +++--- tests/pos/enum-List-control.scala | 7 ++++++- 4 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 library/src-bootstrapped/scala/Enum.scala rename library/{src/scala => src-non-bootstrapped}/Enum.scala (91%) diff --git a/library/src-bootstrapped/scala/Enum.scala b/library/src-bootstrapped/scala/Enum.scala new file mode 100644 index 000000000000..d1e72cb06ff1 --- /dev/null +++ b/library/src-bootstrapped/scala/Enum.scala @@ -0,0 +1,9 @@ +package scala + +/** A base trait of all enum classes */ +trait Enum extends Product, Serializable: + + /** A number uniquely identifying a case of an enum */ + def ordinal: Int + protected def $ordinal: Int + diff --git a/library/src/scala/Enum.scala b/library/src-non-bootstrapped/Enum.scala similarity index 91% rename from library/src/scala/Enum.scala rename to library/src-non-bootstrapped/Enum.scala index 69f1daa27ab8..4f8fe897d41c 100644 --- a/library/src/scala/Enum.scala +++ b/library/src-non-bootstrapped/Enum.scala @@ -1,9 +1,8 @@ package scala /** A base trait of all enum classes */ -trait Enum { +trait Enum: /** A number uniquely identifying a case of an enum */ def ordinal: Int protected def $ordinal: Int -} diff --git a/library/src/scala/EnumValue.scala b/library/src/scala/EnumValue.scala index 70ed31b0f0ac..49a2e73ba737 100644 --- a/library/src/scala/EnumValue.scala +++ b/library/src/scala/EnumValue.scala @@ -1,10 +1,10 @@ package scala trait EnumValue extends Product, Serializable: - override def canEqual(that: Any) = true + override def canEqual(that: Any) = this eq that.asInstanceOf[AnyRef] override def productArity: Int = 0 override def productPrefix: String = toString override def productElement(n: Int): Any = - throw IndexOutOfBoundsException(n.toString()) + throw IndexOutOfBoundsException(n.toString) override def productElementName(n: Int): String = - throw IndexOutOfBoundsException(n.toString()) + throw IndexOutOfBoundsException(n.toString) diff --git a/tests/pos/enum-List-control.scala b/tests/pos/enum-List-control.scala index 931406214122..86ac138e1e7c 100644 --- a/tests/pos/enum-List-control.scala +++ b/tests/pos/enum-List-control.scala @@ -2,11 +2,16 @@ abstract sealed class List[T] extends Enum object List { final class Cons[T](x: T, xs: List[T]) extends List[T] { def $ordinal = 0 + def canEqual(that: Any): Boolean = that.isInstanceOf[Cons[_]] + def productArity: Int = 2 + def productElement(n: Int): Any = n match + case 0 => x + case 1 => xs } object Cons { def apply[T](x: T, xs: List[T]): List[T] = new Cons(x, xs) } - final class Nil[T]() extends List[T] { + final class Nil[T]() extends List[T], EnumValue { def $ordinal = 1 } object Nil { From 6bd846a56508d5e6a4753335f7c03d213a1b4d24 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 May 2020 10:36:19 +0200 Subject: [PATCH 08/12] Special case dropping EnumValue in Namer We now drop EnumValue just when inferring types of enum cases. This can be done unconditionally since in any case every enum case extends Enum, which extends Product and Serializable. EnumValue is just an invisible implementation bundle. This means we disable for now the more general handling in widenInferred. --- .../src/dotty/tools/dotc/ast/DesugarEnums.scala | 2 +- .../tools/dotc/core/ConstraintHandling.scala | 5 ++++- compiler/src/dotty/tools/dotc/core/Flags.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 15 ++++++++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index fafa62e2851e..89ed46baa379 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -306,7 +306,7 @@ object DesugarEnums { else { val (tag, scaffolding) = nextOrdinal(CaseKind.Simple) val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) - val vdef = ValDef(name, TypeTree(), creator).withMods(mods.withAddedFlags(EnumValue, span)) + val vdef = ValDef(name, enumClassRef, creator).withMods(mods.withAddedFlags(EnumValue, span)) flatTree(scaffolding ::: vdef :: Nil).withSpan(span) } } diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index eedb89b57acc..2afe8d0ef559 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -341,7 +341,10 @@ trait ConstraintHandling[AbstractContext] { val wideInst = if isSingleton(bound) then inst - else widenProtected(widenOr(widenSingle(inst))) + else /*widenProtected*/(widenOr(widenSingle(inst))) + // widenProtected is currently not called since it's special cased in `dropEnumValue` + // in `Namer`. It's left in here in case we want to generalize the scheme to other + // "protected inheritance" classes. wideInst match case wideInst: TypeRef if wideInst.symbol.is(Module) => TermRef(wideInst.prefix, wideInst.symbol.sourceModule) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 3ffafc0e48fb..0b2aa4133983 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -438,7 +438,7 @@ object Flags { * TODO: Should check that FromStartFlags do not change in completion */ val FromStartFlags: FlagSet = commonFlags( - Module, Package, Deferred, Method, Case, + Module, Package, Deferred, Method, Case, Enum, HigherKinded, Param, ParamAccessor, Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic, OuterOrCovariant, LabelOrContravariant, CaseAccessor, diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 329135a02b32..8b52ab997d39 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1439,6 +1439,17 @@ class Namer { typer: Typer => // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable) + def isEnumValue(tp: Type) = tp.typeSymbol == defn.EnumValueClass + + // Drop EnumValue parents from inferred types of enum constants + def dropEnumValue(tp: Type): Type = tp.dealias match + case tp @ AndType(tp1, tp2) => + if isEnumValue(tp1) then tp2 + else if isEnumValue(tp2) then tp1 + else tp.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2)) + case _ => + tp + // Widen rhs type and eliminate `|' but keep ConstantTypes if // definition is inline (i.e. final in Scala2) and keep module singleton types // instead of widening to the underlying module class types. @@ -1447,7 +1458,9 @@ class Namer { typer: Typer => def widenRhs(tp: Type): Type = tp.widenTermRefExpr.simplified match case ctp: ConstantType if isInlineVal => ctp - case tp => ctx.typeComparer.widenInferred(tp, rhsProto) + case tp => + val tp1 = ctx.typeComparer.widenInferred(tp, rhsProto) + if sym.is(Enum) then dropEnumValue(tp1) else tp1 // Replace aliases to Unit by Unit itself. If we leave the alias in // it would be erased to BoxedUnit. From 8832569bc7e9b65e95e7dae928814ce076394d9d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 May 2020 13:14:26 +0200 Subject: [PATCH 09/12] Move EnumValue to scala.runtime It is no longer visible in the types of enum constants, so no need to keep it in Scala. Also, update doc pages to match the new scheme. --- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 6 +++--- compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 2 +- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/src/dotty/tools/dotc/core/StdNames.scala | 6 +++--- docs/docs/reference/enums/desugarEnums.md | 5 ++++- docs/docs/reference/enums/enums.md | 4 ++-- library/src/scala/{ => runtime}/EnumValue.scala | 2 +- tests/neg/enumvalues.scala | 10 +++++++--- tests/pos/enum-List-control.scala | 2 +- 10 files changed, 24 insertions(+), 16 deletions(-) rename library/src/scala/{ => runtime}/EnumValue.scala (94%) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 89ed46baa379..b130e7cb9aea 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -124,7 +124,7 @@ object DesugarEnums { /** A creation method for a value of enum type `E`, which is defined as follows: * - * private def $new(_$ordinal: Int, $name: String) = new E with EnumValue { + * private def $new(_$ordinal: Int, $name: String) = new E with scala.runtime.EnumValue { * def $ordinal = $tag * override def toString = $name * $values.register(this) @@ -135,7 +135,7 @@ object DesugarEnums { val toStringDef = toStringMeth(Ident(nme.nameDollar)) val creator = New(Template( constr = emptyConstructor, - parents = enumClassRef :: scalaDot(str.EnumValue.toTypeName) :: Nil, + parents = enumClassRef :: scalaRuntimeDot(tpnme.EnumValue) :: Nil, derived = Nil, self = EmptyValDef, body = List(ordinalDef, toStringDef) ++ registerCall @@ -287,7 +287,7 @@ object DesugarEnums { val ordinalDef = ordinalMethLit(tag) val toStringDef = toStringMethLit(name.toString) val impl1 = cpy.Template(impl)( - parents = impl.parents :+ scalaDot(str.EnumValue.toTypeName), + parents = impl.parents :+ scalaRuntimeDot(tpnme.EnumValue), body = List(ordinalDef, toStringDef) ++ registerCall) .withAttachment(ExtendsSingletonMirror, ()) val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods.withAddedFlags(EnumValue, span)) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 3b4a52f176c7..cbbe96075ac0 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -450,6 +450,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def rootDot(name: Name)(implicit src: SourceFile): Select = Select(Ident(nme.ROOTPKG), name) def scalaDot(name: Name)(implicit src: SourceFile): Select = Select(rootDot(nme.scala), name) def scalaAnnotationDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.annotation), name) + def scalaRuntimeDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.runtime), name) def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit) def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any) def javaDotLangDot(name: Name)(implicit src: SourceFile): Select = Select(Select(Ident(nme.java), nme.lang), name) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 2afe8d0ef559..daa490292faf 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -300,7 +300,7 @@ trait ConstraintHandling[AbstractContext] { * (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint) * 2. If `inst` is a union type, approximate the union type from above by an intersection * of all common base types, provided the result is a subtype of `bound`. - * 3. If `inst` is an intersection with some protected base types, drop + * 3. (currently not enabled) If `inst` is an intersection with some protected base types, drop * the protected base types from the intersection, provided the result is a subtype of `bound`. * * Don't do these widenings if `bound` is a subtype of `scala.Singleton`. diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5a2247739d97..ee06ad3579a5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -639,7 +639,7 @@ class Definitions { @tu lazy val EnumClass: ClassSymbol = ctx.requiredClass("scala.Enum") @tu lazy val Enum_ordinal: Symbol = EnumClass.requiredMethod(nme.ordinal) - @tu lazy val EnumValueClass: ClassSymbol = ctx.requiredClass("scala.EnumValue") + @tu lazy val EnumValueClass: ClassSymbol = ctx.requiredClass("scala.runtime.EnumValue") @tu lazy val EnumValuesClass: ClassSymbol = ctx.requiredClass("scala.runtime.EnumValues") @tu lazy val ProductClass: ClassSymbol = ctx.requiredClass("scala.Product") @tu lazy val Product_canEqual : Symbol = ProductClass.requiredMethod(nme.canEqual_) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 648e74a797d8..ec1887c8eff3 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -33,7 +33,6 @@ object StdNames { final val MODULE_INSTANCE_FIELD = "MODULE$" - final val EnumValue = "EnumValue" final val Function = "Function" final val ErasedFunction = "ErasedFunction" final val ContextFunction = "ContextFunction" @@ -358,7 +357,7 @@ object StdNames { val CAP: N = "CAP" val Constant: N = "Constant" val ConstantType: N = "ConstantType" - val doubleHash: N = "doubleHash" + val EnumValue: N = "EnumValue" val ExistentialTypeTree: N = "ExistentialTypeTree" val Flag : N = "Flag" val floatHash: N = "floatHash" @@ -366,7 +365,6 @@ object StdNames { val Import: N = "Import" val Literal: N = "Literal" val LiteralAnnotArg: N = "LiteralAnnotArg" - val longHash: N = "longHash" val MatchCase: N = "MatchCase" val MirroredElemTypes: N = "MirroredElemTypes" val MirroredElemLabels: N = "MirroredElemLabels" @@ -444,6 +442,7 @@ object StdNames { val delayedInitArg: N = "delayedInit$body" val derived: N = "derived" val derives: N = "derives" + val doubleHash: N = "doubleHash" val drop: N = "drop" val dynamics: N = "dynamics" val elem: N = "elem" @@ -506,6 +505,7 @@ object StdNames { val language: N = "language" val length: N = "length" val lengthCompare: N = "lengthCompare" + val longHash: N = "longHash" val macroThis : N = "_this" val macroContext : N = "c" val main: N = "main" diff --git a/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index 7ab1460c44ef..3070602c6c11 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -126,7 +126,9 @@ map into `case class`es or `val`s. where `n` is the ordinal number of the case in the companion object, starting from 0. The statement `$values.register(this)` registers the value as one of the `values` of the enumeration (see below). `$values` is a - compiler-defined private value in the companion object. + compiler-defined private value in the companion object. The anonymous class also + implements the abstract `Product` methods that it inherits from `Enum`. + It is an error if a value case refers to a type parameter of the enclosing `enum` in a type argument of ``. @@ -178,6 +180,7 @@ Companion objects of enumerations that contain at least one simple case define i } ``` +The anonymous class also implements the abstract `Product` methods that it inherits from `Enum`. The `$ordinal` method above is used to generate the `ordinal` method if the enum does not extend a `java.lang.Enum` (as Scala enums do not extend `java.lang.Enum`s unless explicitly specified). In case it does, there is no need to generate `ordinal` as `java.lang.Enum` defines it. ### Scopes for Enum Cases diff --git a/docs/docs/reference/enums/enums.md b/docs/docs/reference/enums/enums.md index c56f733ab5b9..90e52bdb2414 100644 --- a/docs/docs/reference/enums/enums.md +++ b/docs/docs/reference/enums/enums.md @@ -95,7 +95,7 @@ If you want to use the Scala-defined enums as Java enums, you can do so by exten enum Color extends java.lang.Enum[Color] { case Red, Green, Blue } ``` -The type parameter comes from the Java enum [definition](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Enum.html) and should be the same as the type of the enum. +The type parameter comes from the Java enum [definition](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Enum.html) and should be the same as the type of the enum. There is no need to provide constructor arguments (as defined in the Java API docs) to `java.lang.Enum` when extending it – the compiler will generate them automatically. After defining `Color` like that, you can use it like you would a Java enum: @@ -116,7 +116,7 @@ This trait defines a single public method, `ordinal`: package scala /** A base trait of all enum classes */ -trait Enum { +trait Enum extends Product with Serializable { /** A number uniquely identifying a case of an enum */ def ordinal: Int diff --git a/library/src/scala/EnumValue.scala b/library/src/scala/runtime/EnumValue.scala similarity index 94% rename from library/src/scala/EnumValue.scala rename to library/src/scala/runtime/EnumValue.scala index 49a2e73ba737..f07b756190e8 100644 --- a/library/src/scala/EnumValue.scala +++ b/library/src/scala/runtime/EnumValue.scala @@ -1,4 +1,4 @@ -package scala +package scala.runtime trait EnumValue extends Product, Serializable: override def canEqual(that: Any) = this eq that.asInstanceOf[AnyRef] diff --git a/tests/neg/enumvalues.scala b/tests/neg/enumvalues.scala index 835d82a1fe89..f9d847f5fb63 100644 --- a/tests/neg/enumvalues.scala +++ b/tests/neg/enumvalues.scala @@ -1,11 +1,15 @@ enum Color: case Red, Green, Blue +enum Option[+T]: + case None extends Option[Nothing] + +import scala.runtime.EnumValue + @main def Test(c: Boolean) = - // These currently give errors. But maybe we should make the actual - // enum values carry the `EnumValue` trait, and only strip it from - // user-defined vals and defs? + // Verify that enum constants don't leak the scala.runtime.EnumValue trait val x: EnumValue = if c then Color.Red else Color.Blue // error // error val y: EnumValue = Color.Green // error + val z: EnumValue = Option.None // error diff --git a/tests/pos/enum-List-control.scala b/tests/pos/enum-List-control.scala index 86ac138e1e7c..2a957a2c4ab2 100644 --- a/tests/pos/enum-List-control.scala +++ b/tests/pos/enum-List-control.scala @@ -11,7 +11,7 @@ object List { object Cons { def apply[T](x: T, xs: List[T]): List[T] = new Cons(x, xs) } - final class Nil[T]() extends List[T], EnumValue { + final class Nil[T]() extends List[T], runtime.EnumValue { def $ordinal = 1 } object Nil { From 3a352e6bf289c5a36a9062ddef6965a1c0558bba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 May 2020 14:50:15 +0200 Subject: [PATCH 10/12] Update semanticDB expects again --- tests/semanticdb/expect/Enums.expect.scala | 32 +++++++++++----------- tests/semanticdb/metac.expect | 32 +++++++++++----------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/semanticdb/expect/Enums.expect.scala b/tests/semanticdb/expect/Enums.expect.scala index 064269ed88ae..3baff23aa49c 100644 --- a/tests/semanticdb/expect/Enums.expect.scala +++ b/tests/semanticdb/expect/Enums.expect.scala @@ -29,19 +29,19 @@ object Enums/*<-_empty_::Enums.*/: case Sunday/*<-_empty_::Enums.WeekDays.Sunday.*/ enum Coin/*<-_empty_::Enums.Coin#*/(value/*<-_empty_::Enums.Coin#value.*/: Int/*->scala::Int#*/): - case Penny/*<-_empty_::Enums.Coin.Penny.*/ extends Coin/*->_empty_::Enums.Coin#*/(1)/*->scala::EnumValue#*/ - case Nickel/*<-_empty_::Enums.Coin.Nickel.*/ extends Coin/*->_empty_::Enums.Coin#*/(5)/*->scala::EnumValue#*/ - case Dime/*<-_empty_::Enums.Coin.Dime.*/ extends Coin/*->_empty_::Enums.Coin#*/(10)/*->scala::EnumValue#*/ - case Quarter/*<-_empty_::Enums.Coin.Quarter.*/ extends Coin/*->_empty_::Enums.Coin#*/(25)/*->scala::EnumValue#*/ - case Dollar/*<-_empty_::Enums.Coin.Dollar.*/ extends Coin/*->_empty_::Enums.Coin#*/(100)/*->scala::EnumValue#*/ + case Penny/*<-_empty_::Enums.Coin.Penny.*/ extends Coin/*->_empty_::Enums.Coin#*/(1)/*->scala::runtime::EnumValue#*/ + case Nickel/*<-_empty_::Enums.Coin.Nickel.*/ extends Coin/*->_empty_::Enums.Coin#*/(5)/*->scala::runtime::EnumValue#*/ + case Dime/*<-_empty_::Enums.Coin.Dime.*/ extends Coin/*->_empty_::Enums.Coin#*/(10)/*->scala::runtime::EnumValue#*/ + case Quarter/*<-_empty_::Enums.Coin.Quarter.*/ extends Coin/*->_empty_::Enums.Coin#*/(25)/*->scala::runtime::EnumValue#*/ + case Dollar/*<-_empty_::Enums.Coin.Dollar.*/ extends Coin/*->_empty_::Enums.Coin#*/(100)/*->scala::runtime::EnumValue#*/ enum Maybe/*<-_empty_::Enums.Maybe#*/[+A/*<-_empty_::Enums.Maybe#[A]*/]: case Just/*<-_empty_::Enums.Maybe.Just#*/(value/*<-_empty_::Enums.Maybe.Just#value.*/: A/*->_empty_::Enums.Maybe.Just#[A]*/) - case None/*<-_empty_::Enums.Maybe.None.*//*->scala::EnumValue#*/ + case None/*<-_empty_::Enums.Maybe.None.*//*->scala::runtime::EnumValue#*/ enum Tag/*<-_empty_::Enums.Tag#*/[A/*<-_empty_::Enums.Tag#[A]*/]: - case IntTag/*<-_empty_::Enums.Tag.IntTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Int/*->scala::Int#*/]/*->scala::EnumValue#*/ - case BooleanTag/*<-_empty_::Enums.Tag.BooleanTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Boolean/*->scala::Boolean#*/]/*->scala::EnumValue#*/ + case IntTag/*<-_empty_::Enums.Tag.IntTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Int/*->scala::Int#*/]/*->scala::runtime::EnumValue#*/ + case BooleanTag/*<-_empty_::Enums.Tag.BooleanTag.*/ extends Tag/*->_empty_::Enums.Tag#*/[Boolean/*->scala::Boolean#*/]/*->scala::runtime::EnumValue#*/ enum <:_empty_::Enums.`<:<`.Refl#[C]*/ <:_empty_::Enums.`<:<`#*/ C/*->_empty_::Enums.`<:<`.Refl#[C]*/) @@ -59,11 +59,11 @@ object Enums/*<-_empty_::Enums.*/: def surfaceGravity/*<-_empty_::Enums.Planet#surfaceGravity().*/ = G/*->_empty_::Enums.Planet#G.*/ */*->scala::Double#`*`(+6).*/ mass/*->_empty_::Enums.Planet#mass.*/ //*->scala::Double#`::`(+6).*/ (radius/*->_empty_::Enums.Planet#radius.*/ */*->scala::Double#`*`(+6).*/ radius/*->_empty_::Enums.Planet#radius.*/) def surfaceWeight/*<-_empty_::Enums.Planet#surfaceWeight().*/(otherMass/*<-_empty_::Enums.Planet#surfaceWeight().(otherMass)*/: Double/*->scala::Double#*/) = otherMass/*->_empty_::Enums.Planet#surfaceWeight().(otherMass)*/ */*->scala::Double#`*`(+6).*/ surfaceGravity/*->_empty_::Enums.Planet#surfaceGravity().*/ - case Mercury/*<-_empty_::Enums.Planet.Mercury.*/ extends Planet/*->_empty_::Enums.Planet#*/(3.303e+23, 2.4397e6)/*->scala::EnumValue#*/ - case Venus/*<-_empty_::Enums.Planet.Venus.*/ extends Planet/*->_empty_::Enums.Planet#*/(4.869e+24, 6.0518e6)/*->scala::EnumValue#*/ - case Earth/*<-_empty_::Enums.Planet.Earth.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.976e+24, 6.37814e6)/*->scala::EnumValue#*/ - case Mars/*<-_empty_::Enums.Planet.Mars.*/ extends Planet/*->_empty_::Enums.Planet#*/(6.421e+23, 3.3972e6)/*->scala::EnumValue#*/ - case Jupiter/*<-_empty_::Enums.Planet.Jupiter.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.9e+27, 7.1492e7)/*->scala::EnumValue#*/ - case Saturn/*<-_empty_::Enums.Planet.Saturn.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.688e+26, 6.0268e7)/*->scala::EnumValue#*/ - case Uranus/*<-_empty_::Enums.Planet.Uranus.*/ extends Planet/*->_empty_::Enums.Planet#*/(8.686e+25, 2.5559e7)/*->scala::EnumValue#*/ - case Neptune/*<-_empty_::Enums.Planet.Neptune.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.024e+26, 2.4746e7)/*->scala::EnumValue#*/ + case Mercury/*<-_empty_::Enums.Planet.Mercury.*/ extends Planet/*->_empty_::Enums.Planet#*/(3.303e+23, 2.4397e6)/*->scala::runtime::EnumValue#*/ + case Venus/*<-_empty_::Enums.Planet.Venus.*/ extends Planet/*->_empty_::Enums.Planet#*/(4.869e+24, 6.0518e6)/*->scala::runtime::EnumValue#*/ + case Earth/*<-_empty_::Enums.Planet.Earth.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.976e+24, 6.37814e6)/*->scala::runtime::EnumValue#*/ + case Mars/*<-_empty_::Enums.Planet.Mars.*/ extends Planet/*->_empty_::Enums.Planet#*/(6.421e+23, 3.3972e6)/*->scala::runtime::EnumValue#*/ + case Jupiter/*<-_empty_::Enums.Planet.Jupiter.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.9e+27, 7.1492e7)/*->scala::runtime::EnumValue#*/ + case Saturn/*<-_empty_::Enums.Planet.Saturn.*/ extends Planet/*->_empty_::Enums.Planet#*/(5.688e+26, 6.0268e7)/*->scala::runtime::EnumValue#*/ + case Uranus/*<-_empty_::Enums.Planet.Uranus.*/ extends Planet/*->_empty_::Enums.Planet#*/(8.686e+25, 2.5559e7)/*->scala::runtime::EnumValue#*/ + case Neptune/*<-_empty_::Enums.Planet.Neptune.*/ extends Planet/*->_empty_::Enums.Planet#*/(1.024e+26, 2.4746e7)/*->scala::runtime::EnumValue#*/ diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 319b8da7c44f..6bf2e8fe4e3f 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -855,23 +855,23 @@ Occurrences: [31:9..31:14): Penny <- _empty_/Enums.Coin.Penny. [31:26..31:30): Coin -> _empty_/Enums.Coin# [31:30..31:30): -> _empty_/Enums.Coin#``(). -[31:33..31:33): -> scala/EnumValue# +[31:33..31:33): -> scala/runtime/EnumValue# [32:9..32:15): Nickel <- _empty_/Enums.Coin.Nickel. [32:26..32:30): Coin -> _empty_/Enums.Coin# [32:30..32:30): -> _empty_/Enums.Coin#``(). -[32:33..32:33): -> scala/EnumValue# +[32:33..32:33): -> scala/runtime/EnumValue# [33:9..33:13): Dime <- _empty_/Enums.Coin.Dime. [33:26..33:30): Coin -> _empty_/Enums.Coin# [33:30..33:30): -> _empty_/Enums.Coin#``(). -[33:34..33:34): -> scala/EnumValue# +[33:34..33:34): -> scala/runtime/EnumValue# [34:9..34:16): Quarter <- _empty_/Enums.Coin.Quarter. [34:26..34:30): Coin -> _empty_/Enums.Coin# [34:30..34:30): -> _empty_/Enums.Coin#``(). -[34:34..34:34): -> scala/EnumValue# +[34:34..34:34): -> scala/runtime/EnumValue# [35:9..35:15): Dollar <- _empty_/Enums.Coin.Dollar. [35:26..35:30): Coin -> _empty_/Enums.Coin# [35:30..35:30): -> _empty_/Enums.Coin#``(). -[35:35..35:35): -> scala/EnumValue# +[35:35..35:35): -> scala/runtime/EnumValue# [37:7..37:12): Maybe <- _empty_/Enums.Maybe# [37:12..37:16): <- _empty_/Enums.Maybe#``(). [37:14..37:15): A <- _empty_/Enums.Maybe#[A] @@ -881,7 +881,7 @@ Occurrences: [38:21..38:22): A -> _empty_/Enums.Maybe.Just#[A] [39:4..39:4): -> _empty_/Enums.Maybe#``(). [39:9..39:13): None <- _empty_/Enums.Maybe.None. -[39:13..39:13): -> scala/EnumValue# +[39:13..39:13): -> scala/runtime/EnumValue# [41:7..41:10): Tag <- _empty_/Enums.Tag# [41:10..41:13): <- _empty_/Enums.Tag#``(). [41:11..41:12): A <- _empty_/Enums.Tag#[A] @@ -889,12 +889,12 @@ Occurrences: [42:24..42:27): Tag -> _empty_/Enums.Tag# [42:28..42:31): Int -> scala/Int# [42:32..42:32): -> _empty_/Enums.Tag#``(). -[42:32..42:32): -> scala/EnumValue# +[42:32..42:32): -> scala/runtime/EnumValue# [43:9..43:19): BooleanTag <- _empty_/Enums.Tag.BooleanTag. [43:28..43:31): Tag -> _empty_/Enums.Tag# [43:32..43:39): Boolean -> scala/Boolean# [43:40..43:40): -> _empty_/Enums.Tag#``(). -[43:40..43:40): -> scala/EnumValue# +[43:40..43:40): -> scala/runtime/EnumValue# [45:7..45:10): <:< <- _empty_/Enums.`<:<`# [45:10..45:17): <- _empty_/Enums.`<:<`#``(). [45:12..45:13): A <- _empty_/Enums.`<:<`#[A] @@ -972,35 +972,35 @@ Occurrences: [61:9..61:16): Mercury <- _empty_/Enums.Planet.Mercury. [61:25..61:31): Planet -> _empty_/Enums.Planet# [61:31..61:31): -> _empty_/Enums.Planet#``(). -[61:52..61:52): -> scala/EnumValue# +[61:52..61:52): -> scala/runtime/EnumValue# [62:9..62:14): Venus <- _empty_/Enums.Planet.Venus. [62:25..62:31): Planet -> _empty_/Enums.Planet# [62:31..62:31): -> _empty_/Enums.Planet#``(). -[62:52..62:52): -> scala/EnumValue# +[62:52..62:52): -> scala/runtime/EnumValue# [63:9..63:14): Earth <- _empty_/Enums.Planet.Earth. [63:25..63:31): Planet -> _empty_/Enums.Planet# [63:31..63:31): -> _empty_/Enums.Planet#``(). -[63:53..63:53): -> scala/EnumValue# +[63:53..63:53): -> scala/runtime/EnumValue# [64:9..64:13): Mars <- _empty_/Enums.Planet.Mars. [64:25..64:31): Planet -> _empty_/Enums.Planet# [64:31..64:31): -> _empty_/Enums.Planet#``(). -[64:52..64:52): -> scala/EnumValue# +[64:52..64:52): -> scala/runtime/EnumValue# [65:9..65:16): Jupiter <- _empty_/Enums.Planet.Jupiter. [65:25..65:31): Planet -> _empty_/Enums.Planet# [65:31..65:31): -> _empty_/Enums.Planet#``(). -[65:52..65:52): -> scala/EnumValue# +[65:52..65:52): -> scala/runtime/EnumValue# [66:9..66:15): Saturn <- _empty_/Enums.Planet.Saturn. [66:25..66:31): Planet -> _empty_/Enums.Planet# [66:31..66:31): -> _empty_/Enums.Planet#``(). -[66:52..66:52): -> scala/EnumValue# +[66:52..66:52): -> scala/runtime/EnumValue# [67:9..67:15): Uranus <- _empty_/Enums.Planet.Uranus. [67:25..67:31): Planet -> _empty_/Enums.Planet# [67:31..67:31): -> _empty_/Enums.Planet#``(). -[67:52..67:52): -> scala/EnumValue# +[67:52..67:52): -> scala/runtime/EnumValue# [68:9..68:16): Neptune <- _empty_/Enums.Planet.Neptune. [68:25..68:31): Planet -> _empty_/Enums.Planet# [68:31..68:31): -> _empty_/Enums.Planet#``(). -[68:52..68:52): -> scala/EnumValue# +[68:52..68:52): -> scala/runtime/EnumValue# expect/EtaExpansion.scala ------------------------- From 1cd85bc72bbe1efa8a3407df8a03c79ed32f94c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 May 2020 18:12:46 +0200 Subject: [PATCH 11/12] Address review comments --- .../tools/dotc/core/ConstraintHandling.scala | 26 ++++++++++--------- .../src/dotty/tools/dotc/typer/Namer.scala | 6 +++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index daa490292faf..4b37dde5bf2b 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -300,8 +300,8 @@ trait ConstraintHandling[AbstractContext] { * (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint) * 2. If `inst` is a union type, approximate the union type from above by an intersection * of all common base types, provided the result is a subtype of `bound`. - * 3. (currently not enabled) If `inst` is an intersection with some protected base types, drop - * the protected base types from the intersection, provided the result is a subtype of `bound`. + * 3. (currently not enabled) If `inst` is an intersection with some restricted base types, drop + * the restricted base types from the intersection, provided the result is a subtype of `bound`. * * Don't do these widenings if `bound` is a subtype of `scala.Singleton`. * Also, if the result of these widenings is a TypeRef to a module class, @@ -313,18 +313,20 @@ trait ConstraintHandling[AbstractContext] { */ def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type = - def isProtected(tp: Type) = tp.typeSymbol == defn.EnumValueClass // for now, to be generalized later + def isRestricted(tp: Type) = tp.typeSymbol == defn.EnumValueClass // for now, to be generalized later - def dropProtected(tp: Type): Type = tp.dealias match - case tp @ AndType(tp1, tp2) => - if isProtected(tp1) then tp2 - else if isProtected(tp2) then tp1 - else tp.derivedAndType(dropProtected(tp1), dropProtected(tp2)) + def dropRestricted(tp: Type): Type = tp.dealias match + case tpd @ AndType(tp1, tp2) => + if isRestricted(tp1) then tp2 + else if isRestricted(tp2) then tp1 + else + val tpw = tpd.derivedAndType(dropRestricted(tp1), dropRestricted(tp2)) + if tpw ne tpd then tpw else tp case _ => tp - def widenProtected(tp: Type) = - val tpw = dropProtected(tp) + def widenRestricted(tp: Type) = + val tpw = dropRestricted(tp) if (tpw ne tp) && (tpw <:< bound) then tpw else tp def widenOr(tp: Type) = @@ -341,8 +343,8 @@ trait ConstraintHandling[AbstractContext] { val wideInst = if isSingleton(bound) then inst - else /*widenProtected*/(widenOr(widenSingle(inst))) - // widenProtected is currently not called since it's special cased in `dropEnumValue` + else /*widenRestricted*/(widenOr(widenSingle(inst))) + // widenRestricted is currently not called since it's special cased in `dropEnumValue` // in `Namer`. It's left in here in case we want to generalize the scheme to other // "protected inheritance" classes. wideInst match diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 8b52ab997d39..b11fef3ee26d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1443,10 +1443,12 @@ class Namer { typer: Typer => // Drop EnumValue parents from inferred types of enum constants def dropEnumValue(tp: Type): Type = tp.dealias match - case tp @ AndType(tp1, tp2) => + case tpd @ AndType(tp1, tp2) => if isEnumValue(tp1) then tp2 else if isEnumValue(tp2) then tp1 - else tp.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2)) + else + val tpw = tpd.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2)) + if tpw ne tpd then tpw else tp case _ => tp From 74cc1b1f2441cbbf319b273ea75ed20c7304b4f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 May 2020 19:25:13 +0200 Subject: [PATCH 12/12] Add link to issue --- compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 4b37dde5bf2b..0d6bcadaf331 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -300,7 +300,7 @@ trait ConstraintHandling[AbstractContext] { * (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint) * 2. If `inst` is a union type, approximate the union type from above by an intersection * of all common base types, provided the result is a subtype of `bound`. - * 3. (currently not enabled) If `inst` is an intersection with some restricted base types, drop + * 3. (currently not enabled, see #9028) If `inst` is an intersection with some restricted base types, drop * the restricted base types from the intersection, provided the result is a subtype of `bound`. * * Don't do these widenings if `bound` is a subtype of `scala.Singleton`.