From c42db70d169dec02777622ae1784ceb1d8ee2f46 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Oct 2020 09:45:43 +0200 Subject: [PATCH 1/5] An alternative scheme for precise apply methods of enums Keeps many elements from #9922 but the modality where we do the widening is different. The new rule is as follows: In an application of a compiler-generated apply or copy method of an enum case, widen its type to the underlying supertype of the enum case by means of a type ascription, unless the expected type is an enum case itself. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 16 +------ .../dotty/tools/dotc/ast/DesugarEnums.scala | 46 ------------------- .../dotty/tools/dotc/transform/SymUtils.scala | 3 ++ .../dotty/tools/dotc/typer/Applications.scala | 27 ++++++++++- .../src/dotty/tools/dotc/typer/ReTyper.scala | 3 ++ tests/pos/enum-widen.scala | 16 +++++++ 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 +++++++++++++ 14 files changed, 147 insertions(+), 65 deletions(-) create mode 100644 tests/pos/enum-widen.scala 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/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 0df97a71e4e8..3e26be016339 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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, TypeTree(), creatorExpr) + .withMods(appMods) :: Nil } val unapplyMeth = { val hasRepeatedParam = constrVparamss.head.exists { 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. diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 62500b758612..133a7b9c4004 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -160,6 +160,9 @@ object SymUtils { def isField(using Context): Boolean = self.isTerm && !self.is(Method) + def isEnumCase(using Context): Boolean = + self.isAllOf(EnumCase, butNot = JavaDefined) + def annotationsCarrying(meta: ClassSymbol)(using Context): List[Annotation] = self.annotations.filter(_.symbol.hasAnnotation(meta)) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a2113ce2946f..eb309a5aa68d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -25,6 +25,7 @@ import ProtoTypes._ import Inferencing._ import reporting._ import transform.TypeUtils._ +import transform.SymUtils._ import Nullables.{postProcessByNameArgs, given _} import config.Feature @@ -891,7 +892,9 @@ trait Applications extends Compatibility { case funRef: TermRef => val app = ApplyTo(tree, fun1, funRef, proto, pt) convertNewGenericArray( - postProcessByNameArgs(funRef, app).computeNullable()) + widenEnumCase( + postProcessByNameArgs(funRef, app).computeNullable(), + pt)) case _ => handleUnexpectedFunType(tree, fun1) } @@ -1091,7 +1094,7 @@ trait Applications extends Compatibility { * It is performed during typer as creation of generic arrays needs a classTag. * we rely on implicit search to find one. */ - def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match { + def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match { case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor => fullyDefinedType(tree.tpe, "array", tree.span) @@ -1107,6 +1110,26 @@ trait Applications extends Compatibility { tree } + /** If `tree` is a complete application of a compiler-generated `apply` + * or `copy` method of an enum case, widen its type to the underlying + * type by means of a type ascription, unless the expected type is an + * enum case itself. + * The underlying type is the intersection of all class parents of the + * orginal type. + */ + def widenEnumCase(tree: Tree, pt: Type)(using Context): Tree = + val sym = tree.symbol + def isEnumCopy = sym.name == nme.copy && sym.owner.isEnumCase + def isEnumApply = sym.name == nme.apply && sym.owner.linkedClass.isEnumCase + if sym.is(Synthetic) && (isEnumApply || isEnumCopy) + && tree.tpe.classSymbol.isEnumCase + && !pt.isInstanceOf[FunProto] + && !pt.classSymbol.isEnumCase + then + Typed(tree, TypeTree(tree.tpe.parents.reduceLeft(TypeComparer.andType(_, _)))) + else + tree + /** Does `state` contain a "NotAMember" or "MissingIdent" message as * first pending error message? That message would be * `$memberName is not a member of ...` or `Not found: $memberName`. diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 35458aa89af4..5da1c44d9878 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -132,6 +132,9 @@ class ReTyper extends Typer with ReChecking { override def inferView(from: Tree, to: Type)(using Context): Implicits.SearchResult = Implicits.NoMatchingImplicitsFailure override def checkCanEqual(ltp: Type, rtp: Type, span: Span)(using Context): Unit = () + + override def widenEnumCase(tree: Tree, pt: Type)(using Context): Tree = tree + override protected def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = body override protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(using Context): Unit = () override protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean = true diff --git a/tests/pos/enum-widen.scala b/tests/pos/enum-widen.scala new file mode 100644 index 000000000000..3b017280fcb5 --- /dev/null +++ b/tests/pos/enum-widen.scala @@ -0,0 +1,16 @@ +object test: + + enum Option[+T]: + case Some[T](x: T) extends Option[T] + case None + + import Option._ + + var x = Some(1) + val y: Some[Int] = Some(2) + var xc = y.copy(3) + val yc: Some[Int] = y.copy(3) + x = None + xc = None + + 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 8e2aae22b485d8a4c86b3bcbb33cb717e25a3890 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Oct 2020 09:54:24 +0200 Subject: [PATCH 2/5] Refine widening of enumCases Widen whenever it is possible to do so while still conforming to expected type. --- .../tools/dotc/core/ConstraintHandling.scala | 83 ++++++++++--------- .../dotty/tools/dotc/core/TypeComparer.scala | 3 + .../dotty/tools/dotc/typer/Applications.scala | 12 +-- tests/pos/enum-widen.scala | 5 ++ 4 files changed, 57 insertions(+), 46 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index a392b0ddb10b..4aa72d8bc712 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -286,18 +286,53 @@ trait ConstraintHandling { } } + /** If `tp` is an intersection such that some operands are super trait instances + * and others are not, replace as many super trait instances as possible with Any + * as long as the result is still a subtype of `bound`. But fall back to the + * original type if the resulting widened type is a supertype of all dropped + * types (since in this case the type was not a true intersection of super traits + * and other types to start with). + */ + def dropSuperTraits(tp: Type, bound: Type)(using Context): Type = + var kept: Set[Type] = Set() // types to keep since otherwise bound would not fit + var dropped: List[Type] = List() // the types dropped so far, last one on top + + def dropOneSuperTrait(tp: Type): Type = + val tpd = tp.dealias + if tpd.typeSymbol.isSuperTrait && !tpd.isLambdaSub && !kept.contains(tpd) then + dropped = tpd :: dropped + defn.AnyType + else tpd match + case AndType(tp1, tp2) => + val tp1w = dropOneSuperTrait(tp1) + if tp1w ne tp1 then tp1w & tp2 + else + val tp2w = dropOneSuperTrait(tp2) + if tp2w ne tp2 then tp1 & tp2w + else tpd + case _ => + tp + + def recur(tp: Type): Type = + val tpw = dropOneSuperTrait(tp) + if tpw eq tp then tp + else if tpw <:< bound then recur(tpw) + else + kept += dropped.head + dropped = dropped.tail + recur(tp) + + val tpw = recur(tp) + if (tpw eq tp) || dropped.forall(_ frozen_<:< tpw) then tp else tpw + end dropSuperTraits + /** Widen inferred type `inst` with upper `bound`, according to the following rules: * 1. If `inst` is a singleton type, or a union containing some singleton types, * widen (all) the singleton type(s), provided the result is a subtype of `bound` * (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 such that some operands are super trait instances - * and others are not, replace as many super trait instances as possible with Any - * as long as the result is still a subtype of `bound`. But fall back to the - * original type if the resulting widened type is a supertype of all dropped - * types (since in this case the type was not a true intersection of super traits - * and other types to start with). + * 3. drop super traits from intersections (see @dropSuperTraits) * * 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, @@ -308,40 +343,6 @@ trait ConstraintHandling { * as those could leak the annotation to users (see run/inferred-repeated-result). */ def widenInferred(inst: Type, bound: Type)(using Context): Type = - - def dropSuperTraits(tp: Type): Type = - var kept: Set[Type] = Set() // types to keep since otherwise bound would not fit - var dropped: List[Type] = List() // the types dropped so far, last one on top - - def dropOneSuperTrait(tp: Type): Type = - val tpd = tp.dealias - if tpd.typeSymbol.isSuperTrait && !tpd.isLambdaSub && !kept.contains(tpd) then - dropped = tpd :: dropped - defn.AnyType - else tpd match - case AndType(tp1, tp2) => - val tp1w = dropOneSuperTrait(tp1) - if tp1w ne tp1 then tp1w & tp2 - else - val tp2w = dropOneSuperTrait(tp2) - if tp2w ne tp2 then tp1 & tp2w - else tpd - case _ => - tp - - def recur(tp: Type): Type = - val tpw = dropOneSuperTrait(tp) - if tpw eq tp then tp - else if tpw <:< bound then recur(tpw) - else - kept += dropped.head - dropped = dropped.tail - recur(tp) - - val tpw = recur(tp) - if (tpw eq tp) || dropped.forall(_ frozen_<:< tpw) then tp else tpw - end dropSuperTraits - def widenOr(tp: Type) = val tpw = tp.widenUnion if (tpw ne tp) && (tpw <:< bound) then tpw else tp @@ -356,7 +357,7 @@ trait ConstraintHandling { val wideInst = if isSingleton(bound) then inst - else dropSuperTraits(widenOr(widenSingle(inst))) + else dropSuperTraits(widenOr(widenSingle(inst)), bound) 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/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7d8a609c67db..75a0908abba1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2625,6 +2625,9 @@ object TypeComparer { def widenInferred(inst: Type, bound: Type)(using Context): Type = comparing(_.widenInferred(inst, bound)) + def dropSuperTraits(tp: Type, bound: Type)(using Context): Type = + comparing(_.dropSuperTraits(tp, bound)) + def constrainPatternType(pat: Type, scrut: Type)(using Context): Boolean = comparing(_.constrainPatternType(pat, scrut)) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index eb309a5aa68d..dafb9f8641c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1123,12 +1123,14 @@ trait Applications extends Compatibility { def isEnumApply = sym.name == nme.apply && sym.owner.linkedClass.isEnumCase if sym.is(Synthetic) && (isEnumApply || isEnumCopy) && tree.tpe.classSymbol.isEnumCase - && !pt.isInstanceOf[FunProto] - && !pt.classSymbol.isEnumCase + && tree.tpe.widen.isValueType then - Typed(tree, TypeTree(tree.tpe.parents.reduceLeft(TypeComparer.andType(_, _)))) - else - tree + val widened = TypeComparer.dropSuperTraits( + tree.tpe.parents.reduceLeft(TypeComparer.andType(_, _)), + pt) + if widened <:< pt then Typed(tree, TypeTree(widened)) + else tree + else tree /** Does `state` contain a "NotAMember" or "MissingIdent" message as * first pending error message? That message would be diff --git a/tests/pos/enum-widen.scala b/tests/pos/enum-widen.scala index 3b017280fcb5..32bbcf9a3242 100644 --- a/tests/pos/enum-widen.scala +++ b/tests/pos/enum-widen.scala @@ -13,4 +13,9 @@ object test: x = None xc = None + enum Nat: + case Z + case S[N <: Z.type | S[_]](pred: N) + import Nat._ + val two = S(S(Z)) From 657caec9c45c26c5f36e658bb2827a4d41d663e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Oct 2020 11:03:20 +0200 Subject: [PATCH 3/5] Change doc comment explaining widening rule --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 4 ++-- tests/run-macros/i8007/Test_4.scala | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index dafb9f8641c6..25f9a3ca1211 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1112,8 +1112,8 @@ trait Applications extends Compatibility { /** If `tree` is a complete application of a compiler-generated `apply` * or `copy` method of an enum case, widen its type to the underlying - * type by means of a type ascription, unless the expected type is an - * enum case itself. + * type by means of a type ascription, as long as the widened type is + * still compatible with the expected type. * The underlying type is the intersection of all class parents of the * orginal type. */ diff --git a/tests/run-macros/i8007/Test_4.scala b/tests/run-macros/i8007/Test_4.scala index ba0b20f00323..a16266fe6fd6 100644 --- a/tests/run-macros/i8007/Test_4.scala +++ b/tests/run-macros/i8007/Test_4.scala @@ -39,9 +39,8 @@ enum OptInv[+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 + val t5_2 = OptInv.Sm(23) === OptInv.Sm(23) + println(t5_2) // true println val t6 = Sm(Person("Test", 23)) === Sm(Person("Test", 23)) From 6c19236b006a53abbaf38843116c7b7e96a273a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Oct 2020 12:16:12 +0200 Subject: [PATCH 4/5] Update docs --- docs/docs/reference/enums/adts.md | 8 ++++++-- docs/docs/reference/enums/desugarEnums.md | 13 +++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/docs/reference/enums/adts.md b/docs/docs/reference/enums/adts.md index 6407d0ec5048..82a317e58089 100644 --- a/docs/docs/reference/enums/adts.md +++ b/docs/docs/reference/enums/adts.md @@ -52,11 +52,15 @@ Note that the type of the expressions above is always `Option`. That is, the implementation case classes are not visible in the result types of their `apply` methods. This is a subtle difference with respect to normal case classes. The classes making up the cases do -exist, and can be unveiled by constructing them directly with a `new`. +exist, and can be unveiled, either by constructing them directly with a `new`, +or by explicitly providing an expected type. + ```scala scala> new Option.Some(2) -val res3: t2.Option.Some[Int] = Some(2) +val res3: Option.Some[Int] = Some(2) +scala> val x: Option.Some[Int] = Option.Some(3) +val res4: Option.Some[Int] = Some(3) ``` As all other enums, ADTs can define methods. For instance, here is `Option` again, with an diff --git a/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index 9de02d5f4079..b7d29dd3c2b8 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -139,10 +139,7 @@ map into `case class`es or `val`s. ```scala final case class C extends ``` - However, unlike for a regular case class, the return type of the associated - `apply` method is a fully parameterized type instance of the enum class `E` - itself instead of `C`. Also the enum case defines an `ordinal` method of - the form + The enum case defines an `ordinal` method of the form ```scala def ordinal = n ``` @@ -153,6 +150,14 @@ map into `case class`es or `val`s. in a parameter type in `` or in a type argument of ``, unless that parameter is already a type parameter of the case, i.e. the parameter name is defined in ``. + The compiler-generated `apply` and `copy` methods of an enum case + ```scala + case C(ps) extends P1, ..., Pn + ``` + are treated specially. A call `C(ts)` of the apply method is ascribed the underlying type + `P1 & ... & Pn` (dropping any [super traits](../other-new-features/super-traits.html)) + as long as that type is still compatible with the expected type at the point of application. + A call `t.copy(ts)` of `C`'s `copy` method is treated in the same way. ### Translation of Enums with Singleton Cases From fe1355ae95d61527b7488c5479088e819dd23dd4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Oct 2020 15:15:58 +0200 Subject: [PATCH 5/5] Change wording --- docs/docs/reference/enums/adts.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/docs/reference/enums/adts.md b/docs/docs/reference/enums/adts.md index 82a317e58089..61258a7c490e 100644 --- a/docs/docs/reference/enums/adts.md +++ b/docs/docs/reference/enums/adts.md @@ -48,13 +48,7 @@ scala> Option.None val res2: t2.Option[Nothing] = None ``` -Note that the type of the expressions above is always `Option`. That -is, the implementation case classes are not visible in the result -types of their `apply` methods. This is a subtle difference with -respect to normal case classes. The classes making up the cases do -exist, and can be unveiled, either by constructing them directly with a `new`, -or by explicitly providing an expected type. - +Note that the type of the expressions above is always `Option`. Generally, the type of a enum case constructor application will be widened to the underlying enum type, unless a more specific type is expected. This is a subtle difference with respect to normal case classes. The classes making up the cases do exist, and can be unveiled, either by constructing them directly with a `new`, or by explicitly providing an expected type. ```scala scala> new Option.Some(2)