From 6706e275016f670bbfb51e6d6fe14ded8805f13f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 29 Sep 2020 10:58:22 +0200 Subject: [PATCH 1/4] enums are case classes and vals --- .../src/dotty/tools/dotc/ast/Desugar.scala | 20 ++------ .../dotty/tools/dotc/ast/DesugarEnums.scala | 46 ------------------- 2 files changed, 4 insertions(+), 62 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 0df97a71e4e8..a73826d70df9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -607,7 +607,7 @@ object desugar { cpy.ValDef(vparam)(rhs = copyDefault(vparam))) val copyRestParamss = derivedVparamss.tail.nestedMap(vparam => cpy.ValDef(vparam)(rhs = EmptyTree)) - DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) + DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, classTypeRef, creatorExpr) .withMods(Modifiers(Synthetic | constr1.mods.flags & copiedAccessFlags, constr1.mods.privateWithin)) :: Nil } } @@ -658,15 +658,6 @@ object desugar { // For all other classes, the parent is AnyRef. val companions = if (isCaseClass) { - // The return type of the `apply` method, and an (empty or singleton) list - // of widening coercions - val (applyResultTpt, widenDefs) = - if (!isEnumCase) - (TypeTree(), Nil) - else if (parents.isEmpty || enumClass.typeParams.isEmpty) - (enumClassTypeRef, Nil) - else - enumApplyResult(cdef, parents, derivedEnumParams, appliedRef(enumClassRef, derivedEnumParams)) // true if access to the apply method has to be restricted // i.e. if the case class constructor is either private or qualified private @@ -697,8 +688,6 @@ object desugar { then anyRef else constrVparamss.foldRight(classTypeRef)((vparams, restpe) => Function(vparams map (_.tpt), restpe)) - def widenedCreatorExpr = - widenDefs.foldLeft(creatorExpr)((rhs, meth) => Apply(Ident(meth.name), rhs :: Nil)) val applyMeths = if (mods.is(Abstract)) Nil else { @@ -711,9 +700,8 @@ object desugar { val appParamss = derivedVparamss.nestedZipWithConserve(constrVparamss)((ap, cp) => ap.withMods(ap.mods | (cp.mods.flags & HasDefault))) - val app = DefDef(nme.apply, derivedTparams, appParamss, applyResultTpt, widenedCreatorExpr) - .withMods(appMods) - app :: widenDefs + DefDef(nme.apply, derivedTparams, appParamss, classTypeRef, creatorExpr) + .withMods(appMods) :: Nil } val unapplyMeth = { val hasRepeatedParam = constrVparamss.head.exists { @@ -722,7 +710,7 @@ object desugar { val methName = if (hasRepeatedParam) nme.unapplySeq else nme.unapply val unapplyParam = makeSyntheticParameter(tpt = classTypeRef) val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name) - val unapplyResTp = if (arity == 0) Literal(Constant(true)) else TypeTree() + val unapplyResTp = if arity == 0 then Literal(Constant(true)) else classTypeRef DefDef(methName, derivedTparams, (unapplyParam :: Nil) :: Nil, unapplyResTp, unapplyRHS) .withMods(synthetic) } diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 74a169126eaa..56aa41c00cc7 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -201,52 +201,6 @@ object DesugarEnums { TypeTree(), creator).withFlags(Private | Synthetic) } - /** The return type of an enum case apply method and any widening methods in which - * the apply's right hand side will be wrapped. For parents of the form - * - * extends E(args) with T1(args1) with ... TN(argsN) - * - * and type parameters `tparams` the generated widen method is - * - * def C$to$E[tparams](x$1: E[tparams] with T1 with ... TN) = x$1 - * - * @param cdef The case definition - * @param parents The declared parents of the enum case - * @param tparams The type parameters of the enum case - * @param appliedEnumRef The enum class applied to `tparams`. - */ - def enumApplyResult( - cdef: TypeDef, - parents: List[Tree], - tparams: List[TypeDef], - appliedEnumRef: Tree)(using Context): (Tree, List[DefDef]) = { - - def extractType(t: Tree): Tree = t match { - case Apply(t1, _) => extractType(t1) - case TypeApply(t1, ts) => AppliedTypeTree(extractType(t1), ts) - case Select(t1, nme.CONSTRUCTOR) => extractType(t1) - case New(t1) => t1 - case t1 => t1 - } - - val parentTypes = parents.map(extractType) - parentTypes.head match { - case parent: RefTree if parent.name == enumClass.name => - // need a widen method to compute correct type parameters for enum base class - val widenParamType = parentTypes.tail.foldLeft(appliedEnumRef)(makeAndType) - val widenParam = makeSyntheticParameter(tpt = widenParamType) - val widenDef = DefDef( - name = s"${cdef.name}$$to$$${enumClass.name}".toTermName, - tparams = tparams, - vparamss = (widenParam :: Nil) :: Nil, - tpt = TypeTree(), - rhs = Ident(widenParam.name)) - (TypeTree(), widenDef :: Nil) - case _ => - (parentTypes.reduceLeft(makeAndType), Nil) - } - } - /** Is a type parameter in `enumTypeParams` referenced from an enum class case that has * given type parameters `caseTypeParams`, value parameters `vparamss` and parents `parents`? * Issues an error if that is the case but the reference is illegal. From 4b3652f0b086114b5e4a061cdabe6b949d41b4f5 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 29 Sep 2020 16:22:04 +0200 Subject: [PATCH 2/4] widen enum cases if not upper bound --- .../dotty/tools/dotc/core/ConstraintHandling.scala | 13 ++++++++++++- compiler/src/dotty/tools/dotc/core/Types.scala | 5 +++++ .../src/dotty/tools/dotc/parsing/Scanners.scala | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index a392b0ddb10b..4ee8122dc506 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -350,13 +350,24 @@ trait ConstraintHandling { val tpw = tp.widenSingletons if (tpw ne tp) && (tpw <:< bound) then tpw else tp + def widenEnum(tp: Type) = + val tpw = tp.widenEnumCase + 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) + def isEnumCase(tp: Type): Boolean = tp match + case WildcardType(optBounds) => optBounds.exists && isEnumCase(optBounds.bounds.hi) + case _ => tp.classSymbol.isAllOf(EnumCase, butNot=JavaDefined) + val wideInst = if isSingleton(bound) then inst - else dropSuperTraits(widenOr(widenSingle(inst))) + else + val lub = widenOr(widenSingle(inst)) + val asAdt = if isEnumCase(bound) then lub else widenEnum(lub) + dropSuperTraits(asAdt) 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/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c14942e12a67..e70d4f3a5bc1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1146,6 +1146,11 @@ object Types { case _ => this } + /** if this type is a reference to a class case of an enum, replace it by its first parent */ + final def widenEnumCase(using Context): Type = this match + case tp: (TypeRef | AppliedType) if tp.classSymbol.isAllOf(EnumCase) => tp.parents.head + case _ => this + /** Widen this type and if the result contains embedded union types, replace * them by their joins. * "Embedded" means: inside type lambdas, intersections or recursive types, or in prefixes of refined types. diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 241319ea6ff7..672d76c3fafb 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -1399,8 +1399,8 @@ object Scanners { object IndentWidth { private inline val MaxCached = 40 - private val spaces = Array.tabulate(MaxCached + 1)(new Run(' ', _)) - private val tabs = Array.tabulate(MaxCached + 1)(new Run('\t', _)) + private val spaces = Array.tabulate[Run](MaxCached + 1)(new Run(' ', _)) // TODO: remove new after bootstrap + private val tabs = Array.tabulate[Run](MaxCached + 1)(new Run('\t', _)) // TODO: remove new after bootstrap def Run(ch: Char, n: Int): Run = if (n <= MaxCached && ch == ' ') spaces(n) From ef0fbdc3934eec087e268c9278ec339b7c66c980 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 29 Sep 2020 17:17:25 +0200 Subject: [PATCH 3/4] fix #3935: add regression tests --- .../test-resources/type-printer/enum-precise | 12 +++++++ tests/patmat/i7186.scala | 12 +++---- tests/pos/i3935.scala | 10 ++++++ .../run-macros/enum-nat-macro/Macros_2.scala | 30 ++++++++++++++++++ tests/run-macros/enum-nat-macro/Nat_1.scala | 3 ++ tests/run-macros/enum-nat-macro/Test_3.scala | 9 ++++++ tests/run-macros/i8007.check | 2 ++ tests/run-macros/i8007/Macro_3.scala | 4 +-- tests/run-macros/i8007/Test_4.scala | 12 ++++++- tests/run/enums-precise.scala | 31 +++++++++++++++++++ 10 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 compiler/test-resources/type-printer/enum-precise create mode 100644 tests/pos/i3935.scala create mode 100644 tests/run-macros/enum-nat-macro/Macros_2.scala create mode 100644 tests/run-macros/enum-nat-macro/Nat_1.scala create mode 100644 tests/run-macros/enum-nat-macro/Test_3.scala create mode 100644 tests/run/enums-precise.scala diff --git a/compiler/test-resources/type-printer/enum-precise b/compiler/test-resources/type-printer/enum-precise new file mode 100644 index 000000000000..362c6a6e969f --- /dev/null +++ b/compiler/test-resources/type-printer/enum-precise @@ -0,0 +1,12 @@ +scala> enum Maybe[+T] { case Something(value: T); case EmptyValue }; import Maybe._ +// defined class Maybe + +scala> List(Something(1)) +val res0: List[Maybe[Int]] = List(Something(1)) + +scala> def listOfSomething[O <: Maybe.Something[_]](listOfSomething: List[O]): listOfSomething.type = listOfSomething +def listOfSomething + [O <: Maybe.Something[?]](listOfSomething: List[O]): listOfSomething.type + +scala> listOfSomething(List(Something(1))) +val res1: List[Maybe.Something[Int]] = List(Something(1)) diff --git a/tests/patmat/i7186.scala b/tests/patmat/i7186.scala index 95c7d4db02c4..d5fa281b4188 100644 --- a/tests/patmat/i7186.scala +++ b/tests/patmat/i7186.scala @@ -172,7 +172,7 @@ object printMips { def oneAddr[O] ( a: O, indent: String) - ( r: O => Dest, + ( r: a.type => Dest, ): String = { val name = a.getClass.getSimpleName.toLowerCase s"${indent}$name ${rsrc(r(a))}$endl" @@ -180,8 +180,8 @@ object printMips { def twoAddr[O] ( a: O, indent: String) - ( d: O => Register, - r: O => Dest | Constant + ( d: a.type => Register, + r: a.type => Dest | Constant ): String = { val name = a.getClass.getSimpleName.toLowerCase s"${indent}$name ${registers(d(a))}, ${rsrc(r(a))}$endl" @@ -190,9 +190,9 @@ object printMips { def threeAddr[O] ( a: O, indent: String ) - ( d: O => Register, - l: O => Register, - r: O => Src + ( d: a.type => Register, + l: a.type => Register, + r: a.type => Src ): String = { val name = a.getClass.getSimpleName.toLowerCase s"${indent}$name ${registers(d(a))}, ${registers(l(a))}, ${rsrc(r(a))}$endl" diff --git a/tests/pos/i3935.scala b/tests/pos/i3935.scala new file mode 100644 index 000000000000..4a3af9e3b6ec --- /dev/null +++ b/tests/pos/i3935.scala @@ -0,0 +1,10 @@ +enum Foo3[T](x: T) { + case Bar[S, T](y: T) extends Foo3[y.type](y) +} + +val foo: Foo3.Bar[Nothing, 3] = Foo3.Bar(3) +val bar = foo + +def baz[T](f: Foo3[T]): f.type = f + +val qux = baz(bar) // existentials are back in Dotty? diff --git a/tests/run-macros/enum-nat-macro/Macros_2.scala b/tests/run-macros/enum-nat-macro/Macros_2.scala new file mode 100644 index 000000000000..e7a8c13489ef --- /dev/null +++ b/tests/run-macros/enum-nat-macro/Macros_2.scala @@ -0,0 +1,30 @@ +import Nat._ + + inline def toIntMacro(inline nat: Nat): Int = ${ Macros.toIntImpl('nat) } + inline def ZeroMacro: Zero.type = ${ Macros.natZero } + transparent inline def toNatMacro(inline int: Int): Nat = ${ Macros.toNatImpl('int) } + + object Macros: + import quoted._ + + def toIntImpl(nat: Expr[Nat])(using QuoteContext): Expr[Int] = + + def inner(nat: Expr[Nat], acc: Int): Int = nat match + case '{ Succ($nat) } => inner(nat, acc + 1) + case '{ Zero } => acc + + Expr(inner(nat, 0)) + + def natZero(using QuoteContext): Expr[Nat.Zero.type] = '{Zero} + + def toNatImpl(int: Expr[Int])(using QuoteContext): Expr[Nat] = + + // it seems even with the bound that the arg will always widen to Expr[Nat] unless explicit + + def inner[N <: Nat: Type](int: Int, acc: Expr[N]): Expr[Nat] = int match + case 0 => acc + case n => inner[Succ[N]](n - 1, '{Succ($acc)}) + + val Const(i) = int + require(i >= 0) + inner[Zero.type](i, '{Zero}) diff --git a/tests/run-macros/enum-nat-macro/Nat_1.scala b/tests/run-macros/enum-nat-macro/Nat_1.scala new file mode 100644 index 000000000000..fbb209698469 --- /dev/null +++ b/tests/run-macros/enum-nat-macro/Nat_1.scala @@ -0,0 +1,3 @@ +enum Nat: + case Zero + case Succ[N <: Nat](n: N) diff --git a/tests/run-macros/enum-nat-macro/Test_3.scala b/tests/run-macros/enum-nat-macro/Test_3.scala new file mode 100644 index 000000000000..da686d4cb3f4 --- /dev/null +++ b/tests/run-macros/enum-nat-macro/Test_3.scala @@ -0,0 +1,9 @@ +import Nat._ + +@main def Test: Unit = + assert(toIntMacro(Succ(Succ(Succ(Zero)))) == 3) + assert(toNatMacro(3) == Succ(Succ(Succ(Zero)))) + val zero: Zero.type = ZeroMacro + assert(zero == Zero) + assert(toIntMacro(toNatMacro(3)) == 3) + val n: Succ[Succ[Succ[Zero.type]]] = toNatMacro(3) diff --git a/tests/run-macros/i8007.check b/tests/run-macros/i8007.check index 0ccbe496ef31..68c91aea9c3c 100644 --- a/tests/run-macros/i8007.check +++ b/tests/run-macros/i8007.check @@ -11,5 +11,7 @@ true true +true + false diff --git a/tests/run-macros/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala index a6e54a2bfc03..4140dcd8f902 100644 --- a/tests/run-macros/i8007/Macro_3.scala +++ b/tests/run-macros/i8007/Macro_3.scala @@ -73,7 +73,7 @@ object Eq { } object Macro3 { - extension [T](x: =>T) inline def === (y: =>T)(using eq: Eq[T]): Boolean = eq.eqv(x, y) + extension [T](inline x: T) inline def === (inline y: T)(using eq: Eq[T]): Boolean = eq.eqv(x, y) implicit inline def eqGen[T]: Eq[T] = ${ Eq.derived[T] } -} \ No newline at end of file +} diff --git a/tests/run-macros/i8007/Test_4.scala b/tests/run-macros/i8007/Test_4.scala index 3bce338f2aec..ba0b20f00323 100644 --- a/tests/run-macros/i8007/Test_4.scala +++ b/tests/run-macros/i8007/Test_4.scala @@ -6,7 +6,12 @@ import Macro3.eqGen case class Person(name: String, age: Int) enum Opt[+T] { - case Sm[T](t: T) extends Opt[T] + case Sm(t: T) + case Nn +} + +enum OptInv[+T] { + case Sm[T](t: T) extends OptInv[T] case Nn } @@ -34,6 +39,11 @@ enum Opt[+T] { println(t5) // true println + // Here invariant case without explicit type parameter will instantiate T to OptInv[Any] + val t5_2 = OptInv.Sm[Int](23) === OptInv.Sm(23) + println(t5) // true + println + val t6 = Sm(Person("Test", 23)) === Sm(Person("Test", 23)) println(t6) // true println diff --git a/tests/run/enums-precise.scala b/tests/run/enums-precise.scala new file mode 100644 index 000000000000..1ae98ca6664f --- /dev/null +++ b/tests/run/enums-precise.scala @@ -0,0 +1,31 @@ +enum NonEmptyList[+T]: + case Many[+U](head: U, tail: NonEmptyList[U]) extends NonEmptyList[U] + case One [+U](value: U) extends NonEmptyList[U] + +enum Ast: + case Binding(name: String, tpe: String) + case Lambda(args: NonEmptyList[Binding], rhs: Ast) // reference to another case of the enum + case Ident(name: String) + case Apply(fn: Ast, args: NonEmptyList[Ast]) + +import NonEmptyList._ +import Ast._ + +// This example showcases the widening when inferring enum case types. +// With scala 2 case class hierarchies, if One.apply(1) returns One[Int] and Many.apply(2, One(3)) returns Many[Int] +// then the `foldRight` expression below would complain that Many[Binding] is not One[Binding]. With Scala 3 enums, +// .apply on the companion returns the precise class, but type inference will widen to NonEmptyList[Binding] unless +// the precise class is expected. +def Bindings(arg: (String, String), args: (String, String)*): NonEmptyList[Binding] = + def Bind(arg: (String, String)): Binding = + val (name, tpe) = arg + Binding(name, tpe) + + args.foldRight(One[Binding](Bind(arg)))((arg, acc) => Many(Bind(arg), acc)) + +@main def Test: Unit = + val OneOfOne: One[1] = One[1](1) + val True = Lambda(Bindings("x" -> "T", "y" -> "T"), Ident("x")) + val Const = Lambda(One(Binding("x", "T")), Lambda(One(Binding("y", "U")), Ident("x"))) // precise type is forwarded + + assert(OneOfOne.value == 1) From 5b864e159b7b1ef62c600c93d3fd8f48ee28a8fb Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 30 Sep 2020 15:14:07 +0200 Subject: [PATCH 4/4] update scodec --- community-build/community-projects/scodec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scodec b/community-build/community-projects/scodec index e04ba7c17f84..8500c6ecf519 160000 --- a/community-build/community-projects/scodec +++ b/community-build/community-projects/scodec @@ -1 +1 @@ -Subproject commit e04ba7c17f848a57425a7e0342c00c0314c9559d +Subproject commit 8500c6ecf5198fc7d7f9bac9a15212683cf68c38