From 4ec5446ef83b0e1f21e1dcd15fc1d8fae528314f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 3 Sep 2020 15:59:47 +0200 Subject: [PATCH 01/10] fix #3935: widen inferred enum types, precise factory method --- .../src/dotty/tools/dotc/ast/Desugar.scala | 26 ++++++---------- .../src/dotty/tools/dotc/core/Types.scala | 2 ++ .../dotty/tools/dotc/parsing/Scanners.scala | 4 +-- tests/pos/i3935.scala | 10 ++++++ tests/run-macros/i8007/Macro_3.scala | 3 +- tests/run-macros/i8007/Test_4.scala | 30 +++++++++++++++--- tests/run/enum-precise.scala | 31 +++++++++++++++++++ 7 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 tests/pos/i3935.scala create mode 100644 tests/run/enum-precise.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 19f8cc586279..47df622d5a5f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -527,6 +527,12 @@ object desugar { // a reference to the class type bound by `cdef`, with type parameters coming from the constructor val classTypeRef = appliedRef(classTycon) + def applyResultTpt = + if isEnumCase then + classTypeRef + else + TypeTree() + // a reference to `enumClass`, with type parameters coming from the case constructor lazy val enumClassTypeRef = if (enumClass.typeParams.isEmpty) @@ -605,7 +611,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, applyResultTpt, creatorExpr) .withMods(Modifiers(Synthetic | constr1.mods.flags & copiedAccessFlags, constr1.mods.privateWithin)) :: Nil } } @@ -656,15 +662,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 @@ -695,8 +692,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 { @@ -709,9 +704,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, applyResultTpt, creatorExpr) + .withMods(appMods) :: Nil } val unapplyMeth = { val hasRepeatedParam = constrVparamss.head.exists { @@ -720,7 +714,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) Literal(Constant(true)) else applyResultTpt DefDef(methName, derivedTparams, (unapplyParam :: Nil) :: Nil, unapplyResTp, unapplyRHS) .withMods(synthetic) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8e328c0c882a..aed18057a587 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1180,6 +1180,8 @@ object Types { def widenSingletons(using Context): Type = dealias match { case tp: SingletonType => tp.widen + case tp: (TypeRef | AppliedType) if tp.typeSymbol.isAllOf(EnumCase) => + tp.parents.head case tp: OrType => val tp1w = tp.widenSingletons if (tp1w eq tp) this else tp1w 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) 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/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala index a6e54a2bfc03..338f651f0dd5 100644 --- a/tests/run-macros/i8007/Macro_3.scala +++ b/tests/run-macros/i8007/Macro_3.scala @@ -64,7 +64,6 @@ object Eq { $ordx == $ordy && $elements($ordx).asInstanceOf[Eq[Any]].eqv($x, $y) } } - '{ eqSum((x: T, y: T) => ${eqSumBody('x, 'y)}) } @@ -76,4 +75,4 @@ object Macro3 { extension [T](x: =>T) inline def === (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 f9cc926c4819..25f54bf2eb79 100644 --- a/tests/run-macros/i8007/Test_4.scala +++ b/tests/run-macros/i8007/Test_4.scala @@ -6,10 +6,22 @@ import Macro3.eqGen case class Person(name: String, age: Int) enum Opt[+T] { - case Sm(t: T) + case Sm[U](t: U) extends Opt[U] case Nn } +enum OptInfer[+T] { + case Sm[+U](t: U) extends OptInfer[U] + case Nn +} + +// simulation of Opt using case class hierarchy +sealed abstract class OptCase[+T] +object OptCase { + final case class Sm[T](t: T) extends OptCase[T] + case object Nn extends OptCase[Nothing] +} + @main def Test() = { import Opt._ import Eq.{given _, _} @@ -30,15 +42,23 @@ enum Opt[+T] { println(t4) // false println - val t5 = Sm(23) === Sm(23) + val t5 = Opt.Sm[Int](23) === Opt.Sm(23) // same behaviour as case class when using apply println(t5) // true println - val t6 = Sm(Person("Test", 23)) === Sm(Person("Test", 23)) + val t5_2 = OptCase.Sm[Int](23) === OptCase.Sm(23) + println(t5_2) // true + println + + val t5_3 = OptInfer.Sm(23) === OptInfer.Sm(23) // covariant `Sm` case means we can avoid explicit type parameter + println(t5_3) // true + println + + val t6 = Sm[Person](Person("Test", 23)) === Sm(Person("Test", 23)) println(t6) // true println - val t7 = Sm(Person("Test", 23)) === Sm(Person("Test", 24)) + val t7 = Sm[Person](Person("Test", 23)) === Sm(Person("Test", 24)) println(t7) // false println -} \ No newline at end of file +} diff --git a/tests/run/enum-precise.scala b/tests/run/enum-precise.scala new file mode 100644 index 000000000000..1ae98ca6664f --- /dev/null +++ b/tests/run/enum-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 db22123ba23a394688ddeaf605276ef6b6c05487 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 4 Sep 2020 15:18:50 +0200 Subject: [PATCH 02/10] add version of Nat to int using enums --- .../tools/dotc/core/ConstraintHandling.scala | 5 +++- tests/neg/enum-widen.scala | 19 ++++++++++++++ tests/run-macros/i8007.check | 4 +++ tests/run/enum-nat.scala | 25 +++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/neg/enum-widen.scala create mode 100644 tests/run/enum-nat.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index a392b0ddb10b..d1ca5f981f73 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -354,8 +354,11 @@ trait ConstraintHandling { case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi) case _ => isSubTypeWhenFrozen(tp, defn.SingletonType) + def isEnumBound(tp: Type): Boolean = + bound.isValueType && bound.typeSymbol.is(Enum, butNot=JavaDefined) && tp <:< bound + val wideInst = - if isSingleton(bound) then inst + if isSingleton(bound) || isEnumBound(bound) then inst else dropSuperTraits(widenOr(widenSingle(inst))) wideInst match case wideInst: TypeRef if wideInst.symbol.is(Module) => diff --git a/tests/neg/enum-widen.scala b/tests/neg/enum-widen.scala new file mode 100644 index 000000000000..73f41fadede4 --- /dev/null +++ b/tests/neg/enum-widen.scala @@ -0,0 +1,19 @@ +import Nat._ + +enum Nat: + case Zero + case Succ[N <: Nat](n: N) + +enum MyEnum: + case A[E <: Enum](e: E) + +final case class Foo[T](t: T) + +inline def willNotReduce1 = inline Foo(Zero) match // assert that enums are widened when the bound is not a parent enum type + case Foo(Zero) => () + +inline def willNotReduce2 = inline MyEnum.A(Zero) match // assert that enums are only narrowed when bound is own enum type + case MyEnum.A(Zero) => () + +val foo = willNotReduce1 // error: cannot reduce inline match with scrutinee: Foo.apply[Nat](Nat$#Zero) : Foo[Nat] +val bar = willNotReduce2 // error: cannot reduce inline match with scrutinee: MyEnum.A.apply[Nat](Nat$#Zero): MyEnum.A[Nat] diff --git a/tests/run-macros/i8007.check b/tests/run-macros/i8007.check index 0ccbe496ef31..01586017aee7 100644 --- a/tests/run-macros/i8007.check +++ b/tests/run-macros/i8007.check @@ -11,5 +11,9 @@ true true +true + +true + false diff --git a/tests/run/enum-nat.scala b/tests/run/enum-nat.scala new file mode 100644 index 000000000000..ab1944837de3 --- /dev/null +++ b/tests/run/enum-nat.scala @@ -0,0 +1,25 @@ +import Nat._ +import compiletime._ + +enum Nat: + case Zero + case Succ[N <: Nat](n: N) + +inline def toIntTypeLevel[N <: Nat]: Int = inline erasedValue[N] match + case _: Zero.type => 0 + case _: Succ[n] => toIntTypeLevel[n] + 1 + +inline def toInt(inline nat: Nat): Int = inline nat match + case nat: Zero.type => 0 + case nat: Succ[n] => toInt(nat.n) + 1 + +inline def toIntTypeTailRec[N <: Nat, Acc <: Int]: Int = inline erasedValue[N] match + case _: Zero.type => constValue[Acc] + case _: Succ[n] => toIntTypeTailRec[n, S[Acc]] + +inline def toIntErased[N <: Nat](inline nat: N): Int = toIntTypeTailRec[N, 0] + +@main def Test: Unit = + assert(toIntTypeLevel[Succ[Succ[Zero.type]]] == 2) + assert(toInt(Succ(Succ(Zero))) == 2) + assert(toIntErased(Succ(Succ(Zero))) == 2) From 3110a0035713cb14ce00bc48ac5e0675b0db60fa Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 9 Sep 2020 14:32:28 +0200 Subject: [PATCH 03/10] separate widenEnumClass logic to own method --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 12 +++--------- .../dotty/tools/dotc/core/ConstraintHandling.scala | 13 +++++++++---- compiler/src/dotty/tools/dotc/core/Types.scala | 7 +++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 47df622d5a5f..e23483f05b21 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -527,12 +527,6 @@ object desugar { // a reference to the class type bound by `cdef`, with type parameters coming from the constructor val classTypeRef = appliedRef(classTycon) - def applyResultTpt = - if isEnumCase then - classTypeRef - else - TypeTree() - // a reference to `enumClass`, with type parameters coming from the case constructor lazy val enumClassTypeRef = if (enumClass.typeParams.isEmpty) @@ -611,7 +605,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, applyResultTpt, creatorExpr) + DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, classTypeRef, creatorExpr) .withMods(Modifiers(Synthetic | constr1.mods.flags & copiedAccessFlags, constr1.mods.privateWithin)) :: Nil } } @@ -704,7 +698,7 @@ object desugar { val appParamss = derivedVparamss.nestedZipWithConserve(constrVparamss)((ap, cp) => ap.withMods(ap.mods | (cp.mods.flags & HasDefault))) - DefDef(nme.apply, derivedTparams, appParamss, applyResultTpt, creatorExpr) + DefDef(nme.apply, derivedTparams, appParamss, classTypeRef, creatorExpr) .withMods(appMods) :: Nil } val unapplyMeth = { @@ -714,7 +708,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 applyResultTpt + 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/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index d1ca5f981f73..1d5c2863a285 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -346,6 +346,10 @@ trait ConstraintHandling { val tpw = tp.widenUnion if (tpw ne tp) && (tpw <:< bound) then tpw else tp + def widenEnum(tp: Type) = + val tpw = tp.widenEnumClass + if (tpw ne tp) && (tpw <:< bound) then tpw else tp + def widenSingle(tp: Type) = val tpw = tp.widenSingletons if (tpw ne tp) && (tpw <:< bound) then tpw else tp @@ -354,12 +358,13 @@ trait ConstraintHandling { case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi) case _ => isSubTypeWhenFrozen(tp, defn.SingletonType) - def isEnumBound(tp: Type): Boolean = - bound.isValueType && bound.typeSymbol.is(Enum, butNot=JavaDefined) && tp <:< bound + def isEnum(tp: Type): Boolean = tp match + case WildcardType(optBounds) => optBounds.exists && isEnum(optBounds.bounds.hi) + case _ => tp.typeSymbol.is(Enum, butNot=JavaDefined) val wideInst = - if isSingleton(bound) || isEnumBound(bound) then inst - else dropSuperTraits(widenOr(widenSingle(inst))) + if isSingleton(bound) || isEnum(bound) then inst + else dropSuperTraits(widenOr(widenEnum(widenSingle(inst)))) 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 aed18057a587..ce943aea8d70 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1173,6 +1173,13 @@ object Types { tp } + def widenEnumClass(using Context): Type = dealias match { + case tp: (TypeRef | AppliedType) if tp.typeSymbol.isAllOf(EnumCase) => + tp.parents.head + case _ => + this + } + /** Widen all top-level singletons reachable by dealiasing * and going to the operands of & and |. * Overridden and cached in OrType. From a89998e5a3fad02764c0a6962e224be0a9e0c0f8 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 9 Sep 2020 16:54:16 +0200 Subject: [PATCH 04/10] preserve module and enumValue in widenTermRefExpr --- .../src/dotty/tools/dotc/core/Types.scala | 4 ++-- .../tools/dotc/printing/PlainPrinter.scala | 3 +-- tests/neg/enum-widen.scala | 19 ------------------- tests/run/enum-nat.scala | 15 ++++++++++++--- 4 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 tests/neg/enum-widen.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ce943aea8d70..dbc0cd8e06d6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1101,8 +1101,10 @@ object Types { /** Widen from TermRef to its underlying non-termref * base type, while also skipping Expr types. + * Preserves references to modules or singleton enum values */ final def widenTermRefExpr(using Context): Type = stripTypeVar match { + case tp: TermRef if tp.termSymbol.isAllOf(EnumCase) || tp.termSymbol.is(Module) => tp case tp: TermRef if !tp.isOverloaded => tp.underlying.widenExpr.widenTermRefExpr case _ => this } @@ -1187,8 +1189,6 @@ object Types { def widenSingletons(using Context): Type = dealias match { case tp: SingletonType => tp.widen - case tp: (TypeRef | AppliedType) if tp.typeSymbol.isAllOf(EnumCase) => - tp.parents.head case tp: OrType => val tp1w = tp.widenSingletons if (tp1w eq tp) this else tp1w diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index ade70b71dd7c..1dbbd63bd735 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -141,7 +141,7 @@ class PlainPrinter(_ctx: Context) extends Printer { toTextRHS(tp) case tp: TermRef if !tp.denotationIsCurrent && !homogenizedView || // always print underlying when testing picklers - tp.symbol.is(Module) || tp.symbol.name == nme.IMPORT => + tp.symbol.is(Module) || tp.symbol.isAllOf(EnumCase) || tp.symbol.name == nme.IMPORT => toTextRef(tp) ~ ".type" case tp: TermRef if tp.denot.isOverloaded => "" @@ -598,4 +598,3 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def coloredText(text: Text, color: String): Text = if (ctx.useColors) color ~ text ~ SyntaxHighlighting.NoColor else text } - diff --git a/tests/neg/enum-widen.scala b/tests/neg/enum-widen.scala deleted file mode 100644 index 73f41fadede4..000000000000 --- a/tests/neg/enum-widen.scala +++ /dev/null @@ -1,19 +0,0 @@ -import Nat._ - -enum Nat: - case Zero - case Succ[N <: Nat](n: N) - -enum MyEnum: - case A[E <: Enum](e: E) - -final case class Foo[T](t: T) - -inline def willNotReduce1 = inline Foo(Zero) match // assert that enums are widened when the bound is not a parent enum type - case Foo(Zero) => () - -inline def willNotReduce2 = inline MyEnum.A(Zero) match // assert that enums are only narrowed when bound is own enum type - case MyEnum.A(Zero) => () - -val foo = willNotReduce1 // error: cannot reduce inline match with scrutinee: Foo.apply[Nat](Nat$#Zero) : Foo[Nat] -val bar = willNotReduce2 // error: cannot reduce inline match with scrutinee: MyEnum.A.apply[Nat](Nat$#Zero): MyEnum.A[Nat] diff --git a/tests/run/enum-nat.scala b/tests/run/enum-nat.scala index ab1944837de3..870688b76d5d 100644 --- a/tests/run/enum-nat.scala +++ b/tests/run/enum-nat.scala @@ -13,6 +13,10 @@ inline def toInt(inline nat: Nat): Int = inline nat match case nat: Zero.type => 0 case nat: Succ[n] => toInt(nat.n) + 1 +inline def toIntUnapply(inline nat: Nat): Int = inline nat match + case Zero => 0 + case Succ(n) => toIntUnapply(n) + 1 + inline def toIntTypeTailRec[N <: Nat, Acc <: Int]: Int = inline erasedValue[N] match case _: Zero.type => constValue[Acc] case _: Succ[n] => toIntTypeTailRec[n, S[Acc]] @@ -20,6 +24,11 @@ inline def toIntTypeTailRec[N <: Nat, Acc <: Int]: Int = inline erasedValue[N] m inline def toIntErased[N <: Nat](inline nat: N): Int = toIntTypeTailRec[N, 0] @main def Test: Unit = - assert(toIntTypeLevel[Succ[Succ[Zero.type]]] == 2) - assert(toInt(Succ(Succ(Zero))) == 2) - assert(toIntErased(Succ(Succ(Zero))) == 2) + println("erased value:") + assert(toIntTypeLevel[Succ[Succ[Succ[Zero.type]]]] == 3) + println("type test:") + assert(toInt(Succ(Succ(Succ(Zero)))) == 3) + println("unapply:") + assert(toIntUnapply(Succ(Succ(Succ(Zero)))) == 3) + println("infer erased:") + assert(toIntErased(Succ(Succ(Succ(Zero)))) == 3) From 4b9de0df240d95d6ea498b1c7be7300ff1847495 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Wed, 9 Sep 2020 22:49:28 +0200 Subject: [PATCH 05/10] explicit bounds for dispatch on enums --- tests/patmat/i7186.scala | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/tests/patmat/i7186.scala b/tests/patmat/i7186.scala index 95c7d4db02c4..d5ab9e05f392 100644 --- a/tests/patmat/i7186.scala +++ b/tests/patmat/i7186.scala @@ -170,33 +170,30 @@ object printMips { } } - def oneAddr[O] + def oneAddr[O <: OneAddr] ( a: O, indent: String) ( r: O => Dest, - ): String = { - val name = a.getClass.getSimpleName.toLowerCase - s"${indent}$name ${rsrc(r(a))}$endl" - } + ): String = ( + s"${indent}${a.enumLabel} ${rsrc(r(a))}$endl" + ) - def twoAddr[O] + def twoAddr[O <: TwoAddr] ( a: O, indent: String) ( d: O => Register, r: O => Dest | Constant - ): String = { - val name = a.getClass.getSimpleName.toLowerCase - s"${indent}$name ${registers(d(a))}, ${rsrc(r(a))}$endl" - } + ): String = ( + s"${indent}${a.enumLabel} ${registers(d(a))}, ${rsrc(r(a))}$endl" + ) - def threeAddr[O] + def threeAddr[O <: ThreeAddr] ( a: O, indent: String ) ( d: O => Register, l: O => Register, r: O => Src - ): String = { - val name = a.getClass.getSimpleName.toLowerCase - s"${indent}$name ${registers(d(a))}, ${registers(l(a))}, ${rsrc(r(a))}$endl" - } + ): String = ( + s"${indent}${a.enumLabel} ${registers(d(a))}, ${registers(l(a))}, ${rsrc(r(a))}$endl" + ) def rsrc(v: Constant | Dest): String = v match { case Constant(c) => c.toString From 31391760e81531ac1a5fcbbe766c03b6acd0c917 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Thu, 10 Sep 2020 00:54:48 +0200 Subject: [PATCH 06/10] 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 fac1efba2bf9..df437ccb17bd 160000 --- a/community-build/community-projects/scodec +++ b/community-build/community-projects/scodec @@ -1 +1 @@ -Subproject commit fac1efba2bf9ea353f026bb0f7a3b825a691584a +Subproject commit df437ccb17bdfe768149bc81e784c0b9f8ab0ed8 From e25f912eec0c2f6f9a77fcbdae77ab166b871a3d Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 11 Sep 2020 15:07:38 +0200 Subject: [PATCH 07/10] deeply embed enum values in union types --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 5 +- .../tools/dotc/core/ConstraintHandling.scala | 11 +++-- .../dotty/tools/dotc/core/TypeErrors.scala | 1 - .../src/dotty/tools/dotc/core/Types.scala | 49 +++++++++++++++---- tests/pos/largeEnums.scala | 19 +++++++ 5 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 tests/pos/largeEnums.scala diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4ed857678b26..0ebcd76d3ed0 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -139,7 +139,7 @@ object DesugarEnums { CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(New(TypeTree(defn.IllegalArgumentExceptionType), List(msg :: Nil)))) val stringCases = enumValues.map(enumValue => - CaseDef(Literal(Constant(enumValue.name.toString)), EmptyTree, enumValue) + CaseDef(Literal(Constant(enumValue.name.toString)), EmptyTree, Typed(enumValue, rawEnumClassRef)) ) ::: defaultCase :: Nil Match(Ident(nme.nameDollar), stringCases) val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil), @@ -157,12 +157,13 @@ object DesugarEnums { def byOrdinal: List[Tree] = if isJavaEnum || !constraints.cached then Nil else + val rawEnumClassRef = rawRef(enumClass.typeRef) val defaultCase = val ord = Ident(nme.ordinal) val err = Throw(New(TypeTree(defn.IndexOutOfBoundsException.typeRef), List(Select(ord, nme.toString_) :: Nil))) CaseDef(ord, EmptyTree, err) val valueCases = constraints.enumCases.map((i, enumValue) => - CaseDef(Literal(Constant(i)), EmptyTree, enumValue) + CaseDef(Literal(Constant(i)), EmptyTree, Typed(enumValue, rawEnumClassRef)) ) ::: defaultCase :: Nil val fromOrdinalDef = DefDef(nme.fromOrdinalDollar, Nil, List(param(nme.ordinalDollar_, defn.IntType) :: Nil), rawRef(enumClass.typeRef), Match(Ident(nme.ordinalDollar_), valueCases)) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 1d5c2863a285..e3d0d2da007b 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -347,7 +347,7 @@ trait ConstraintHandling { if (tpw ne tp) && (tpw <:< bound) then tpw else tp def widenEnum(tp: Type) = - val tpw = tp.widenEnumClass + val tpw = tp.widenEnumCase if (tpw ne tp) && (tpw <:< bound) then tpw else tp def widenSingle(tp: Type) = @@ -363,11 +363,16 @@ trait ConstraintHandling { case _ => tp.typeSymbol.is(Enum, butNot=JavaDefined) val wideInst = - if isSingleton(bound) || isEnum(bound) then inst - else dropSuperTraits(widenOr(widenEnum(widenSingle(inst)))) + if isSingleton(bound) then inst + else + val lub = widenOr(widenSingle(inst)) + val asAdt = if isEnum(bound) then lub else widenEnum(lub) + dropSuperTraits(asAdt) wideInst match case wideInst: TypeRef if wideInst.symbol.is(Module) => TermRef(wideInst.prefix, wideInst.symbol.sourceModule) + case wideInst @ EnumValueRef() => + wideInst case _ => wideInst.dropRepeatedAnnot end widenInferred diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 950963497fbc..edcf77970dca 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -161,4 +161,3 @@ object CyclicReference { ex } } - diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index dbc0cd8e06d6..4a3b8d144195 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1099,12 +1099,22 @@ object Types { case _ => this } + /** same as widen, but preserves modules and singleton enum values */ + final def widenToEnum(using Context): Type = + def widenSingletonToEnum(self: Type)(using Context): Type = self.stripTypeVar.stripAnnots match + case tp @ EnumValueRef() => tp + case tp: SingletonType if !tp.isOverloaded => widenSingletonToEnum(tp.underlying) + case _ => self + widenSingletonToEnum(this) match + case tp: ExprType => tp.resultType.widenToEnum + case tp => tp + /** Widen from TermRef to its underlying non-termref * base type, while also skipping Expr types. * Preserves references to modules or singleton enum values */ final def widenTermRefExpr(using Context): Type = stripTypeVar match { - case tp: TermRef if tp.termSymbol.isAllOf(EnumCase) || tp.termSymbol.is(Module) => tp + case tp @ EnumValueRef() => tp case tp: TermRef if !tp.isOverloaded => tp.underlying.widenExpr.widenTermRefExpr case _ => this } @@ -1147,7 +1157,7 @@ object Types { * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ - def widenUnion(using Context): Type = widen match { + def widenUnion(using Context): Type = widenToEnum match { case tp @ OrNull(tp1): OrType => // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. val tp1Widen = tp1.widenUnionWithoutNull @@ -1157,12 +1167,13 @@ object Types { tp.widenUnionWithoutNull } - def widenUnionWithoutNull(using Context): Type = widen match { - case tp @ OrType(lhs, rhs) => - TypeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { - case union: OrType => union.join - case res => res - } + def widenUnionWithoutNull(using Context): Type = widenToEnum match { + case tp: OrType => + decomposeUnionOfEnum(tp)((lhs, rhs) => + TypeComparer.lub(lhs, rhs, canConstrain = true) match + case union: OrType => union.join + case res => res + ) case tp @ AndType(tp1, tp2) => tp derived_& (tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull) case tp: RefinedType => @@ -1175,9 +1186,24 @@ object Types { tp } - def widenEnumClass(using Context): Type = dealias match { + private inline def decomposeUnionOfEnum(tp: OrType)(inline f: Context ?=> (Type, Type) => Type)(using Context) = + def stripEnum(tp: Type)(using Context) = tp match + case ref @ EnumValueRef() => ref.widen.widenUnionWithoutNull + case tp => tp + + (tp.tp1.widenUnionWithoutNull, tp.tp2.widenUnionWithoutNull) match + case (ref1 @ EnumValueRef(), ref2 @ EnumValueRef()) if ref1.termSymbol eq ref2.termSymbol => + ref1 + case (lhs1, rhs1) => + f(stripEnum(lhs1), stripEnum(rhs1)) + + end decomposeUnionOfEnum + + def widenEnumCase(using Context): Type = dealias match { case tp: (TypeRef | AppliedType) if tp.typeSymbol.isAllOf(EnumCase) => tp.parents.head + case tp: TermRef if tp.termSymbol.isAllOf(EnumCase) => + tp.underlying.widenExpr case _ => this } @@ -1188,7 +1214,7 @@ object Types { */ def widenSingletons(using Context): Type = dealias match { case tp: SingletonType => - tp.widen + tp.widenToEnum case tp: OrType => val tp1w = tp.widenSingletons if (tp1w eq tp) this else tp1w @@ -2557,6 +2583,9 @@ object Types { apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot) } + object EnumValueRef: + def unapply(tp: TermRef)(using Context): Boolean = tp.termSymbol.isAllOf(EnumCase) + object TypeRef { /** Create a type ref with given prefix and name */ diff --git a/tests/pos/largeEnums.scala b/tests/pos/largeEnums.scala new file mode 100644 index 000000000000..08c80ef23c9c --- /dev/null +++ b/tests/pos/largeEnums.scala @@ -0,0 +1,19 @@ +enum MySuperLongJavaEnum extends java.lang.Enum[MySuperLongJavaEnum] { + case + A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0, L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0, Z0, + A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, L1, M1, N1, O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1, Y1, Z1, + A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, V2, W2, X2, Y2, Z2, + A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, L3, M3, N3, O3, P3, Q3, R3, S3, T3, U3, V3, W3, X3, Y3, Z3, + A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, L4, M4, N4, O4, P4, Q4, R4, S4, T4, U4, V4, W4, X4, Y4, Z4, + A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, L5, M5, N5, O5, P5, Q5, R5, S5, T5, U5, V5, W5, X5, Y5, Z5 +} + +enum MySuperLongScalaEnum { + case + A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0, L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0, Z0, + A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, L1, M1, N1, O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1, Y1, Z1, + A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, V2, W2, X2, Y2, Z2, + A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, L3, M3, N3, O3, P3, Q3, R3, S3, T3, U3, V3, W3, X3, Y3, Z3, + A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, L4, M4, N4, O4, P4, Q4, R4, S4, T4, U4, V4, W4, X4, Y4, Z4, + A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, L5, M5, N5, O5, P5, Q5, R5, S5, T5, U5, V5, W5, X5, Y5, Z5 +} From 19fc8fe47dda5e21866cb63d4d697fccae2fd8eb Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 11 Sep 2020 17:00:13 +0200 Subject: [PATCH 08/10] handle modules like enum values --- .../tools/dotc/core/ConstraintHandling.scala | 8 +-- .../src/dotty/tools/dotc/core/Types.scala | 59 +++++++------------ tests/pos/largeEnums.scala | 39 ++++++++++++ 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index e3d0d2da007b..2c543e9c9ad7 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -369,12 +369,8 @@ trait ConstraintHandling { val asAdt = if isEnum(bound) then lub else widenEnum(lub) dropSuperTraits(asAdt) wideInst match - case wideInst: TypeRef if wideInst.symbol.is(Module) => - TermRef(wideInst.prefix, wideInst.symbol.sourceModule) - case wideInst @ EnumValueRef() => - wideInst - case _ => - wideInst.dropRepeatedAnnot + case wideInst @ ModuleOrEnumValueRef() => wideInst + case wideInst => wideInst.dropRepeatedAnnot end widenInferred /** The instance type of `param` in the current constraint (which contains `param`). diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4a3b8d144195..1acba1ee7b22 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1100,13 +1100,13 @@ object Types { } /** same as widen, but preserves modules and singleton enum values */ - final def widenToEnum(using Context): Type = - def widenSingletonToEnum(self: Type)(using Context): Type = self.stripTypeVar.stripAnnots match - case tp @ EnumValueRef() => tp - case tp: SingletonType if !tp.isOverloaded => widenSingletonToEnum(tp.underlying) + final def widenToModule(using Context): Type = + def widenSingletonToModule(self: Type)(using Context): Type = self.stripTypeVar.stripAnnots match + case tp @ ModuleOrEnumValueRef() => tp + case tp: SingletonType if !tp.isOverloaded => widenSingletonToModule(tp.underlying) case _ => self - widenSingletonToEnum(this) match - case tp: ExprType => tp.resultType.widenToEnum + widenSingletonToModule(this) match + case tp: ExprType => tp.resultType.widenToModule case tp => tp /** Widen from TermRef to its underlying non-termref @@ -1114,7 +1114,7 @@ object Types { * Preserves references to modules or singleton enum values */ final def widenTermRefExpr(using Context): Type = stripTypeVar match { - case tp @ EnumValueRef() => tp + case tp @ ModuleOrEnumValueRef() => tp case tp: TermRef if !tp.isOverloaded => tp.underlying.widenExpr.widenTermRefExpr case _ => this } @@ -1157,7 +1157,7 @@ object Types { * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ - def widenUnion(using Context): Type = widenToEnum match { + def widenUnion(using Context): Type = widenToModule match { case tp @ OrNull(tp1): OrType => // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. val tp1Widen = tp1.widenUnionWithoutNull @@ -1167,13 +1167,12 @@ object Types { tp.widenUnionWithoutNull } - def widenUnionWithoutNull(using Context): Type = widenToEnum match { - case tp: OrType => - decomposeUnionOfEnum(tp)((lhs, rhs) => - TypeComparer.lub(lhs, rhs, canConstrain = true) match - case union: OrType => union.join - case res => res - ) + def widenUnionWithoutNull(using Context): Type = widenToModule match { + case tp @ OrType(lhs, rhs) => + TypeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { + case union: OrType => union.join + case res => res + } case tp @ AndType(tp1, tp2) => tp derived_& (tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull) case tp: RefinedType => @@ -1186,26 +1185,10 @@ object Types { tp } - private inline def decomposeUnionOfEnum(tp: OrType)(inline f: Context ?=> (Type, Type) => Type)(using Context) = - def stripEnum(tp: Type)(using Context) = tp match - case ref @ EnumValueRef() => ref.widen.widenUnionWithoutNull - case tp => tp - - (tp.tp1.widenUnionWithoutNull, tp.tp2.widenUnionWithoutNull) match - case (ref1 @ EnumValueRef(), ref2 @ EnumValueRef()) if ref1.termSymbol eq ref2.termSymbol => - ref1 - case (lhs1, rhs1) => - f(stripEnum(lhs1), stripEnum(rhs1)) - - end decomposeUnionOfEnum - def widenEnumCase(using Context): Type = dealias match { - case tp: (TypeRef | AppliedType) if tp.typeSymbol.isAllOf(EnumCase) => - tp.parents.head - case tp: TermRef if tp.termSymbol.isAllOf(EnumCase) => - tp.underlying.widenExpr - case _ => - this + case tp: (TypeRef | AppliedType) if tp.typeSymbol.isAllOf(EnumCase) => tp.parents.head + case tp: TermRef if tp.termSymbol.isAllOf(EnumCase, butNot=JavaDefined) => tp.underlying.widenExpr + case _ => this } /** Widen all top-level singletons reachable by dealiasing @@ -1214,7 +1197,7 @@ object Types { */ def widenSingletons(using Context): Type = dealias match { case tp: SingletonType => - tp.widenToEnum + tp.widenToModule case tp: OrType => val tp1w = tp.widenSingletons if (tp1w eq tp) this else tp1w @@ -2583,8 +2566,10 @@ object Types { apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot) } - object EnumValueRef: - def unapply(tp: TermRef)(using Context): Boolean = tp.termSymbol.isAllOf(EnumCase) + object ModuleOrEnumValueRef: + def unapply(tp: TermRef)(using Context): Boolean = + val sym = tp.termSymbol + sym.isAllOf(EnumCase, butNot=JavaDefined) || sym.is(Module) object TypeRef { diff --git a/tests/pos/largeEnums.scala b/tests/pos/largeEnums.scala index 08c80ef23c9c..524c33351725 100644 --- a/tests/pos/largeEnums.scala +++ b/tests/pos/largeEnums.scala @@ -17,3 +17,42 @@ enum MySuperLongScalaEnum { A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, L4, M4, N4, O4, P4, Q4, R4, S4, T4, U4, V4, W4, X4, Y4, Z4, A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, L5, M5, N5, O5, P5, Q5, R5, S5, T5, U5, V5, W5, X5, Y5, Z5 } + +trait FooBar + +enum MySuperLongAdt { + case A0 extends MySuperLongAdt with FooBar + case B0 extends MySuperLongAdt with FooBar + case C0 extends MySuperLongAdt with FooBar + case D0 extends MySuperLongAdt with FooBar + case E0 extends MySuperLongAdt with FooBar + case F0 extends MySuperLongAdt with FooBar + case G0 extends MySuperLongAdt with FooBar + case H0 extends MySuperLongAdt with FooBar + case I0 extends MySuperLongAdt with FooBar + case J0 extends MySuperLongAdt with FooBar + case K0 extends MySuperLongAdt with FooBar + case L0 extends MySuperLongAdt with FooBar + case M0 extends MySuperLongAdt with FooBar + case N0 extends MySuperLongAdt with FooBar + case O0 extends MySuperLongAdt with FooBar + case P0 extends MySuperLongAdt with FooBar + case Q0 extends MySuperLongAdt with FooBar + case R0 extends MySuperLongAdt with FooBar + case S0 extends MySuperLongAdt with FooBar + case T0 extends MySuperLongAdt with FooBar + case U0 extends MySuperLongAdt with FooBar + case V0 extends MySuperLongAdt with FooBar + case Acc[T <: MySuperLongAdt](t: T) +} + +object Test { + + import MySuperLongAdt._ + val sum: ( + A0.type | B0.type | C0.type | D0.type | E0.type | F0.type | G0.type | H0.type | I0.type | J0.type | K0.type | + L0.type | M0.type | N0.type | O0.type | P0.type | Q0.type | R0.type | S0.type | T0.type | U0.type | V0.type + ) = R0 + + def test = Acc(sum) +} From a71dac99193df430664ca5375b99ff6e36dae328 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 14 Sep 2020 15:42:13 +0200 Subject: [PATCH 09/10] add macro test, preserve singleton types --- .../tools/dotc/transform/ReifyQuotes.scala | 7 +- tests/patmat/i7186.scala | 75 ++++++++++--------- .../enum-nat-macro.scala/Macros_2.scala | 34 +++++++++ .../enum-nat-macro.scala/Nat_1.scala | 3 + .../enum-nat-macro.scala/Test_3.scala | 10 +++ 5 files changed, 90 insertions(+), 39 deletions(-) create mode 100644 tests/run-macros/enum-nat-macro.scala/Macros_2.scala create mode 100644 tests/run-macros/enum-nat-macro.scala/Nat_1.scala create mode 100644 tests/run-macros/enum-nat-macro.scala/Test_3.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 5ce5832dfd06..f5243c22ab4e 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -166,9 +166,10 @@ class ReifyQuotes extends MacroTransform { val meth = if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp) else - val tpe = - if originalTp =:= defn.NilModule.termRef then originalTp // Workaround #4987 - else originalTp.widen.dealias + val tpe = originalTp.widenToModule.dealias + // val tpe = + // if originalTp =:= defn.NilModule.termRef then originalTp // Workaround #4987 + // else originalTp.widen.dealias ref(defn.Unpickler_unpickleExpr).appliedToType(tpe) val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType) val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType)) diff --git a/tests/patmat/i7186.scala b/tests/patmat/i7186.scala index d5ab9e05f392..ea8229e4b73f 100644 --- a/tests/patmat/i7186.scala +++ b/tests/patmat/i7186.scala @@ -125,74 +125,77 @@ object printMips { case Syscall => s"${indent}syscall$endl" case jal: Jal => - oneAddr(jal,indent)(_.dest) + oneAddr(jal,indent)(_.enumLabel,_.dest) case jr: Jr => - oneAddr(jr,indent)(_.dest) + oneAddr(jr,indent)(_.enumLabel,_.dest) case j: J => - oneAddr(j,indent)(_.dest) + oneAddr(j,indent)(_.enumLabel,_.dest) case li: Li => - twoAddr(li,indent)(_.dest,_.source) + twoAddr(li,indent)(_.enumLabel,_.dest,_.source) case lw: Lw => - twoAddr(lw,indent)(_.dest,_.source) + twoAddr(lw,indent)(_.enumLabel,_.dest,_.source) case neg: Neg => - twoAddr(neg,indent)(_.dest,_.r) + twoAddr(neg,indent)(_.enumLabel,_.dest,_.r) case not: Not => - twoAddr(not,indent)(_.dest,_.r) + twoAddr(not,indent)(_.enumLabel,_.dest,_.r) case move: Move => - twoAddr(move,indent)(_.dest,_.source) + twoAddr(move,indent)(_.enumLabel,_.dest,_.source) case beqz: Beqz => - twoAddr(beqz,indent)(_.source,_.breakTo) + twoAddr(beqz,indent)(_.enumLabel,_.source,_.breakTo) case sw: Sw => - twoAddr(sw,indent)(_.source,_.dest) + twoAddr(sw,indent)(_.enumLabel,_.source,_.dest) case add: Add => - threeAddr(add,indent)(_.dest,_.l,_.r) + threeAddr(add,indent)(_.enumLabel,_.dest,_.l,_.r) case sub: Sub => - threeAddr(sub,indent)(_.dest,_.l,_.r) + threeAddr(sub,indent)(_.enumLabel,_.dest,_.l,_.r) case mul: Mul => - threeAddr(mul,indent)(_.dest,_.l,_.r) + threeAddr(mul,indent)(_.enumLabel,_.dest,_.l,_.r) case div: Div => - threeAddr(div,indent)(_.dest,_.l,_.r) + threeAddr(div,indent)(_.enumLabel,_.dest,_.l,_.r) case rem: Rem => - threeAddr(rem,indent)(_.dest,_.l,_.r) + threeAddr(rem,indent)(_.enumLabel,_.dest,_.l,_.r) case seq: Seq => - threeAddr(seq,indent)(_.dest,_.l,_.r) + threeAddr(seq,indent)(_.enumLabel,_.dest,_.l,_.r) case sne: Sne => - threeAddr(sne,indent)(_.dest,_.l,_.r) + threeAddr(sne,indent)(_.enumLabel,_.dest,_.l,_.r) case slt: Slt => - threeAddr(slt,indent)(_.dest,_.l,_.r) + threeAddr(slt,indent)(_.enumLabel,_.dest,_.l,_.r) case sgt: Sgt => - threeAddr(sgt,indent)(_.dest,_.l,_.r) + threeAddr(sgt,indent)(_.enumLabel,_.dest,_.l,_.r) case sle: Sle => - threeAddr(sle,indent)(_.dest,_.l,_.r) + threeAddr(sle,indent)(_.enumLabel,_.dest,_.l,_.r) case sge: Sge => - threeAddr(sge,indent)(_.dest,_.l,_.r) + threeAddr(sge,indent)(_.enumLabel,_.dest,_.l,_.r) case _ => s"${indent}???$endl" } } - def oneAddr[O <: OneAddr] - ( a: O, indent: String) - ( r: O => Dest, + def oneAddr[T] + ( a: T, indent: String ) + ( c: a.type => String, + r: a.type => Dest, ): String = ( - s"${indent}${a.enumLabel} ${rsrc(r(a))}$endl" + s"${indent}${c(a)} ${rsrc(r(a))}$endl" ) - def twoAddr[O <: TwoAddr] - ( a: O, indent: String) - ( d: O => Register, - r: O => Dest | Constant + def twoAddr[T] + ( a: T, indent: String ) + ( c: a.type => String, + d: a.type => Register, + r: a.type => Dest | Constant ): String = ( - s"${indent}${a.enumLabel} ${registers(d(a))}, ${rsrc(r(a))}$endl" + s"${indent}${c(a)} ${registers(d(a))}, ${rsrc(r(a))}$endl" ) - def threeAddr[O <: ThreeAddr] - ( a: O, + def threeAddr[T] + ( a: T, indent: String ) - ( d: O => Register, - l: O => Register, - r: O => Src + ( c: a.type => String, + d: a.type => Register, + l: a.type => Register, + r: a.type => Src ): String = ( - s"${indent}${a.enumLabel} ${registers(d(a))}, ${registers(l(a))}, ${rsrc(r(a))}$endl" + s"${indent}${c(a)} ${registers(d(a))}, ${registers(l(a))}, ${rsrc(r(a))}$endl" ) def rsrc(v: Constant | Dest): String = v match { diff --git a/tests/run-macros/enum-nat-macro.scala/Macros_2.scala b/tests/run-macros/enum-nat-macro.scala/Macros_2.scala new file mode 100644 index 000000000000..7e9f0e14b042 --- /dev/null +++ b/tests/run-macros/enum-nat-macro.scala/Macros_2.scala @@ -0,0 +1,34 @@ +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) } + +inline def toIntInline(inline nat: Nat): Int = inline nat match + case Zero => 0 + case Succ(n) => toIntInline(n) + 1 + +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.scala/Nat_1.scala b/tests/run-macros/enum-nat-macro.scala/Nat_1.scala new file mode 100644 index 000000000000..fbb209698469 --- /dev/null +++ b/tests/run-macros/enum-nat-macro.scala/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.scala/Test_3.scala b/tests/run-macros/enum-nat-macro.scala/Test_3.scala new file mode 100644 index 000000000000..6e53f47d5d85 --- /dev/null +++ b/tests/run-macros/enum-nat-macro.scala/Test_3.scala @@ -0,0 +1,10 @@ +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) + assert(toIntInline(n) == 3) // TODO: there is an error with positions when passing the result of toNatMacro(3) directly From e78a6e427db35606fa9da962f4acedc0d063c0dd Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 15 Sep 2020 10:20:32 +0200 Subject: [PATCH 10/10] fix #6781: add regression test --- tests/pos/i6781.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/pos/i6781.scala diff --git a/tests/pos/i6781.scala b/tests/pos/i6781.scala new file mode 100644 index 000000000000..c6ebd9c9c36f --- /dev/null +++ b/tests/pos/i6781.scala @@ -0,0 +1,12 @@ +enum Nat { + case Zero + case Succ[N <: Nat](n: N) +} +import Nat._ + +inline def toInt(n: => Nat): Int = inline n match { + case Zero => 0 + case Succ(n1) => toInt(n1) + 1 +} + +val natTwo = toInt(Succ(Succ(Zero)))