From 88a2477e6ebce9710a2a48a66b9930e9581eaf8c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 12 Oct 2021 09:25:52 +0200 Subject: [PATCH 1/4] Name mangle methods with erased context results Add a `$` to the name of a method that has erased context results and that may override some other method. This is to prevent the two methods from having the same names and parameters in their erased signatures. We need a bridge between the two methods, so they are not allowed to already override after erasure. --- .../dotty/tools/dotc/core/Definitions.scala | 28 +++---------------- .../dotty/tools/dotc/core/TypeErasure.scala | 4 +-- .../transform/ContextFunctionResults.scala | 13 +++++++++ .../dotty/tools/dotc/transform/Erasure.scala | 18 +++++++++--- .../dotc/transform/GenericSignatures.scala | 4 +-- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 60f375c116fc..7f2ec75af8df 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1353,39 +1353,19 @@ class Definitions { def isBoxedUnitClass(cls: Symbol): Boolean = cls.isClass && (cls.owner eq ScalaRuntimePackageClass) && cls.name == tpnme.BoxedUnit - /** Returns the erased class of the function class `cls` - * - FunctionN for N > 22 becomes FunctionXXL - * - FunctionN for 22 > N >= 0 remains as FunctionN - * - ContextFunctionN for N > 22 becomes FunctionXXL - * - ContextFunctionN for N <= 22 becomes FunctionN - * - ErasedFunctionN becomes Function0 - * - ImplicitErasedFunctionN becomes Function0 - * - anything else becomes a NoSymbol - */ - def erasedFunctionClass(cls: Symbol): Symbol = { - val arity = scalaClassName(cls).functionArity - if (cls.name.isErasedFunction) FunctionClass(0) - else if (arity > 22) FunctionXXLClass - else if (arity >= 0) FunctionClass(arity) - else NoSymbol - } - /** Returns the erased type of the function class `cls` * - FunctionN for N > 22 becomes FunctionXXL * - FunctionN for 22 > N >= 0 remains as FunctionN * - ContextFunctionN for N > 22 becomes FunctionXXL * - ContextFunctionN for N <= 22 becomes FunctionN - * - ErasedFunctionN becomes Function0 - * - ImplicitErasedFunctionN becomes Function0 * - anything else becomes a NoType */ - def erasedFunctionType(cls: Symbol): Type = { + def functionTypeErasure(cls: Symbol): Type = val arity = scalaClassName(cls).functionArity - if (cls.name.isErasedFunction) FunctionType(0) - else if (arity > 22) FunctionXXLClass.typeRef - else if (arity >= 0) FunctionType(arity) + if cls.name.isErasedFunction then FunctionType(0) + else if arity > 22 then FunctionXXLClass.typeRef + else if arity >= 0 then FunctionType(arity) else NoType - } val predefClassNames: Set[Name] = Set("Predef$", "DeprecatedPredef", "LowPriorityImplicits").map(_.toTypeName.unmangleClassName) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 7e89b4e2c2c9..6435d0622ebd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -581,7 +581,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst val sym = tp.symbol if (!sym.isClass) this(tp.translucentSuperType) else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClass(tp) - else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym) + else if (defn.isSyntheticFunctionClass(sym)) defn.functionTypeErasure(sym) else eraseNormalClassRef(tp) case tp: AppliedType => val tycon = tp.tycon @@ -791,7 +791,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst if (erasedVCRef.exists) return sigName(erasedVCRef) } if (defn.isSyntheticFunctionClass(sym)) - sigName(defn.erasedFunctionType(sym)) + sigName(defn.functionTypeErasure(sym)) else val cls = normalizeClass(sym.asClass) val fullName = diff --git a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala index ee56767054fd..4247b835f640 100644 --- a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala +++ b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala @@ -8,12 +8,16 @@ import StdNames.nme import ast.untpd import ast.tpd._ import config.Config +import Decorators.* object ContextFunctionResults: /** Annotate methods that have context function result types directly matched by context * closures on their right-hand side. Parameters to such closures will be integrated * as additional method parameters in erasure. + * + * A @ContextResultCount(n) annotation means that the method's result type + * consists of a string of `n` nested context closures. */ def annotateContextResults(mdef: DefDef)(using Context): Unit = def contextResultCount(rhs: Tree, tp: Type): Int = tp match @@ -50,6 +54,15 @@ object ContextFunctionResults: crCount case none => 0 + /** True iff `ContextResultCount` is not zero and all context functions in the result + * type are erased. + */ + def contextResultsAreErased(sym: Symbol)(using Context): Boolean = + def allErased(tp: Type): Boolean = tp.dealias match + case defn.ContextFunctionType(_, resTpe, isErased) => isErased && allErased(resTpe) + case _ => true + contextResultCount(sym) > 0 && allErased(sym.info.finalResultType) + /** Turn the first `crCount` context function types in the result type of `tp` * into the curried method types. */ diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 2875e2e2d865..570071b4676c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -12,7 +12,7 @@ import core.Types._ import core.Names._ import core.StdNames._ import core.NameOps._ -import core.NameKinds.{AdaptedClosureName, BodyRetainerName} +import core.NameKinds.{AdaptedClosureName, BodyRetainerName, ImplMethName} import core.Scopes.newScopeWith import core.Decorators._ import core.Constants._ @@ -57,6 +57,17 @@ class Erasure extends Phase with DenotTransformer { case _ => false } } + def erasedName = + if ref.is(Flags.Method) + && contextResultsAreErased(ref.symbol) + && (ref.owner.is(Flags.Trait) || ref.symbol.allOverriddenSymbols.hasNext) + then + // Add a `$` to prevent this method from having the same signature + // as a method it overrides. We need a bridge between the + // two methods, so they are not allowed to already override after erasure. + ImplMethName(ref.targetName.asTermName) + else + ref.targetName assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}") if (ref.symbol eq defn.ObjectClass) { @@ -80,7 +91,7 @@ class Erasure extends Phase with DenotTransformer { val oldOwner = ref.owner val newOwner = if oldOwner == defn.AnyClass then defn.ObjectClass else oldOwner val oldName = ref.name - val newName = ref.targetName + val newName = erasedName val oldInfo = ref.info var newInfo = transformInfo(oldSymbol, oldInfo) val oldFlags = ref.flags @@ -392,7 +403,6 @@ object Erasure { cast(tree, pt) end adaptToType - /** The following code: * * val f: Function1[Int, Any] = x => ... @@ -714,7 +724,7 @@ object Erasure { assert(sym.isConstructor, s"${sym.showLocated}") defn.specialErasure(owner) else if defn.isSyntheticFunctionClass(owner) then - defn.erasedFunctionClass(owner) + defn.functionTypeErasure(owner).typeSymbol else owner diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index c12c5c8b4a6e..18b028d1a024 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -274,10 +274,10 @@ object GenericSignatures { jsig(erasedUnderlying, toplevel, primitiveOK) } else if (defn.isSyntheticFunctionClass(sym)) { - val erasedSym = defn.erasedFunctionClass(sym) + val erasedSym = defn.functionTypeErasure(sym).typeSymbol classSig(erasedSym, pre, if (erasedSym.typeParams.isEmpty) Nil else args) } - else if (sym.isClass) + else if sym.isClass then classSig(sym, pre, args) else jsig(erasure(tp), toplevel, primitiveOK) From f50b55c38ab078e843cb3d2e08e40a167bae7758 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 13 Oct 2021 09:58:44 +0200 Subject: [PATCH 2/4] Fix eta expand at erasure to take erased CFTs into account --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 19 +++++- .../src/dotty/tools/dotc/core/Symbols.scala | 4 ++ .../tools/dotc/transform/AccessProxies.scala | 5 +- .../dotty/tools/dotc/transform/Bridges.scala | 53 ++++++++++++++-- .../tools/dotc/transform/ByNameClosures.scala | 6 +- .../transform/ContextFunctionResults.scala | 34 +++-------- .../dotty/tools/dotc/transform/Erasure.scala | 60 +------------------ .../dotty/tools/dotc/typer/Synthesizer.scala | 2 +- .../quoted/runtime/impl/QuoteMatcher.scala | 2 +- .../quoted/runtime/impl/QuotesImpl.scala | 4 +- tests/run/i13691.scala | 53 ++++++++++++++++ tests/run/i13961a.scala | 11 ++++ 12 files changed, 154 insertions(+), 99 deletions(-) create mode 100644 tests/run/i13691.scala create mode 100644 tests/run/i13961a.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 0c12eff2a0ae..2db0bd6de2d4 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -121,9 +121,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Closure(Nil, call, targetTpt)) } - /** A closure whole anonymous function has the given method type */ + /** A closure whose anonymous function has the given method type */ def Lambda(tpe: MethodType, rhsFn: List[Tree] => Tree)(using Context): Block = { - val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, tpe) + val meth = newAnonFun(ctx.owner, tpe) Closure(meth, tss => rhsFn(tss.head).changeOwner(ctx.owner, meth)) } @@ -1104,6 +1104,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { if (sym.exists) sym.defTree = tree tree } + + def etaExpandCFT(using Context): Tree = + def expand(target: Tree, tp: Type)(using Context): Tree = tp match + case defn.ContextFunctionType(argTypes, resType, isErased) => + val anonFun = newAnonFun( + ctx.owner, + MethodType.companion(isContextual = true, isErased = isErased)(argTypes, resType), + coord = ctx.owner.coord) + def lambdaBody(refss: List[List[Tree]]) = + expand(target.select(nme.apply).appliedToArgss(refss), resType)( + using ctx.withOwner(anonFun)) + Closure(anonFun, lambdaBody) + case _ => + target + expand(tree, tree.tpe.widen) } inline val MapRecursionLimit = 10 diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 9e4730534bf7..e49399ff9791 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -712,6 +712,10 @@ object Symbols { coord: Coord = NoCoord)(using Context): TermSymbol = newSymbol(cls, nme.CONSTRUCTOR, flags | Method, MethodType(paramNames, paramTypes, cls.typeRef), privateWithin, coord) + /** Create an anonymous function symbol */ + def newAnonFun(owner: Symbol, info: Type, coord: Coord = NoCoord)(using Context): TermSymbol = + newSymbol(owner, nme.ANON_FUN, Synthetic | Method, info, coord = coord) + /** Create an empty default constructor symbol for given class `cls`. */ def newDefaultConstructor(cls: ClassSymbol)(using Context): TermSymbol = newConstructor(cls, EmptyFlags, Nil, Nil) diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 81d029e023fb..de25cb140da0 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -51,7 +51,10 @@ abstract class AccessProxies { forwardedArgss.nonEmpty && forwardedArgss.head.nonEmpty) // defensive conditions accessRef.becomes(forwardedArgss.head.head) else - accessRef.appliedToTypeTrees(forwardedTpts).appliedToArgss(forwardedArgss) + accessRef + .appliedToTypeTrees(forwardedTpts) + .appliedToArgss(forwardedArgss) + .etaExpandCFT(using ctx.withOwner(accessor)) rhs.withSpan(accessed.span) }) diff --git a/compiler/src/dotty/tools/dotc/transform/Bridges.scala b/compiler/src/dotty/tools/dotc/transform/Bridges.scala index d91840f247b6..f7c00ef74b94 100644 --- a/compiler/src/dotty/tools/dotc/transform/Bridges.scala +++ b/compiler/src/dotty/tools/dotc/transform/Bridges.scala @@ -9,6 +9,11 @@ import ast.untpd import collection.{mutable, immutable} import util.Spans.Span import util.SrcPos +import ContextFunctionResults.{contextResultCount, contextFunctionResultTypeAfter} +import StdNames.nme +import Constants.Constant +import TypeErasure.transformInfo +import Erasure.Boxing.adaptClosure /** A helper class for generating bridge methods in class `root`. */ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) { @@ -112,12 +117,52 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) { toBeRemoved += other } - def bridgeRhs(argss: List[List[Tree]]) = { + val memberCount = contextResultCount(member) + + /** Eta expand application `ref(args)` as needed. + * To do this correctly, we have to look at the member's original pre-erasure + * type and figure out which context function types in its result are + * not yet instantiated. + */ + def etaExpand(ref: Tree, args: List[Tree])(using Context): Tree = + def expand(args: List[Tree], tp: Type, n: Int)(using Context): Tree = + if n <= 0 then + assert(ctx.typer.isInstanceOf[Erasure.Typer]) + ctx.typer.typed(untpd.cpy.Apply(ref)(ref, args), member.info.finalResultType) + else + val defn.ContextFunctionType(argTypes, resType, isErased) = tp: @unchecked + val anonFun = newAnonFun(ctx.owner, + MethodType(if isErased then Nil else argTypes, resType), + coord = ctx.owner.coord) + anonFun.info = transformInfo(anonFun, anonFun.info) + + def lambdaBody(refss: List[List[Tree]]) = + val refs :: Nil = refss: @unchecked + val expandedRefs = refs.map(_.withSpan(ctx.owner.span.endPos)) match + case (bunchedParam @ Ident(nme.ALLARGS)) :: Nil => + argTypes.indices.toList.map(n => + bunchedParam + .select(nme.primitive.arrayApply) + .appliedTo(Literal(Constant(n)))) + case refs1 => refs1 + expand(args ::: expandedRefs, resType, n - 1)(using ctx.withOwner(anonFun)) + + val unadapted = Closure(anonFun, lambdaBody) + cpy.Block(unadapted)(unadapted.stats, + adaptClosure(unadapted.expr.asInstanceOf[Closure])) + end expand + + val otherCount = contextResultCount(other) + val start = contextFunctionResultTypeAfter(member, otherCount)(using preErasureCtx) + expand(args, start, memberCount - otherCount)(using ctx.withOwner(bridge)) + end etaExpand + + def bridgeRhs(argss: List[List[Tree]]) = assert(argss.tail.isEmpty) val ref = This(root).select(member) - if (member.info.isParameterless) ref // can happen if `member` is a module - else Erasure.partialApply(ref, argss.head) - } + if member.info.isParameterless then ref // can happen if `member` is a module + else if memberCount == 0 then ref.appliedToTermArgs(argss.head) + else etaExpand(ref, argss.head) bridges += DefDef(bridge, bridgeRhs(_).withSpan(bridge.span)) } diff --git a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala b/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala index 00bcd1e5076a..51242e7f2dbe 100644 --- a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala +++ b/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala @@ -30,11 +30,9 @@ class ByNameClosures extends TransformByNameApply with IdentityDenotTransformer // ExpanSAMs applied to partial functions creates methods that need // to be fully defined before converting. Test case is pos/i9391.scala. - override def mkByNameClosure(arg: Tree, argType: Type)(using Context): Tree = { - val meth = newSymbol( - ctx.owner, nme.ANON_FUN, Synthetic | Method, MethodType(Nil, Nil, argType)) + override def mkByNameClosure(arg: Tree, argType: Type)(using Context): Tree = + val meth = newAnonFun(ctx.owner, MethodType(Nil, argType)) Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase)).withSpan(arg.span) - } } object ByNameClosures { diff --git a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala index 4247b835f640..14134b4bb1fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala +++ b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala @@ -99,33 +99,13 @@ object ContextFunctionResults: normalParamCount(sym.info) end totalParamCount - /** The rightmost context function type in the result type of `meth` - * that represents `paramCount` curried, non-erased parameters that - * are included in the `contextResultCount` of `meth`. - * Example: - * - * Say we have `def m(x: A): B ?=> (C1, C2, C3) ?=> D ?=> E ?=> F`, - * paramCount == 4, and the contextResultCount of `m` is 3. - * Then we return the type `(C1, C2, C3) ?=> D ?=> E ?=> F`, since this - * type covers the 4 rightmost parameters C1, C2, C3 and D before the - * contextResultCount runs out at E ?=> F. - * Erased parameters are ignored; they contribute nothing to the - * parameter count. - */ - def contextFunctionResultTypeCovering(meth: Symbol, paramCount: Int)(using Context) = - atPhase(erasurePhase) { - // Recursive instances return pairs of context types and the - // # of parameters they represent. - def missingCR(tp: Type, crCount: Int): (Type, Int) = - if crCount == 0 then (tp, 0) - else - val defn.ContextFunctionType(formals, resTpe, isErased) = tp: @unchecked - val result @ (rt, nparams) = missingCR(resTpe, crCount - 1) - assert(nparams <= paramCount) - if nparams == paramCount || isErased then result - else (tp, nparams + formals.length) - missingCR(meth.info.finalResultType, contextResultCount(meth))._1 - } + /** The `depth` levels nested context function type in the result type of `meth` */ + def contextFunctionResultTypeAfter(meth: Symbol, depth: Int)(using Context) = + def recur(tp: Type, n: Int): Type = + if n == 0 then tp + else tp match + case defn.ContextFunctionType(_, resTpe, _) => recur(resTpe, n - 1) + recur(meth.info.finalResultType, depth) /** Should selection `tree` be eliminated since it refers to an `apply` * node of a context function type whose parameters will end up being diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 570071b4676c..c73b3c25c3f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -57,6 +57,7 @@ class Erasure extends Phase with DenotTransformer { case _ => false } } + def erasedName = if ref.is(Flags.Method) && contextResultsAreErased(ref.symbol) @@ -383,8 +384,8 @@ object Erasure { case _: FunProto | AnyFunctionProto => tree case _ => tree.tpe.widen match case mt: MethodType if tree.isTerm => - if mt.paramInfos.isEmpty then adaptToType(tree.appliedToNone, pt) - else etaExpand(tree, mt, pt) + assert(mt.paramInfos.isEmpty) + adaptToType(tree.appliedToNone, pt) case tpw => if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt) tree @@ -533,61 +534,6 @@ object Erasure { else tree end adaptClosure - - /** Eta expand given `tree` that has the given method type `mt`, so that - * it conforms to erased result type `pt`. - * To do this correctly, we have to look at the tree's original pre-erasure - * type and figure out which context function types in its result are - * not yet instantiated. - */ - def etaExpand(tree: Tree, mt: MethodType, pt: Type)(using Context): Tree = - report.log(i"eta expanding $tree") - val defs = new mutable.ListBuffer[Tree] - val tree1 = LiftErased.liftApp(defs, tree) - val xmt = if tree.isInstanceOf[Apply] then mt else expandedMethodType(mt, tree) - val targetLength = xmt.paramInfos.length - val origOwner = ctx.owner - - // The original type from which closures should be constructed - val origType = contextFunctionResultTypeCovering(tree.symbol, targetLength) - - def abstracted(args: List[Tree], tp: Type, pt: Type)(using Context): Tree = - if args.length < targetLength then - try - val defn.ContextFunctionType(argTpes, resTpe, isErased) = tp: @unchecked - if isErased then abstracted(args, resTpe, pt) - else - val anonFun = newSymbol( - ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method, - MethodType(argTpes, resTpe), coord = tree.span.endPos) - anonFun.info = transformInfo(anonFun, anonFun.info) - def lambdaBody(refss: List[List[Tree]]) = - val refs :: Nil = refss: @unchecked - val expandedRefs = refs.map(_.withSpan(tree.span.endPos)) match - case (bunchedParam @ Ident(nme.ALLARGS)) :: Nil => - argTpes.indices.toList.map(n => - bunchedParam - .select(nme.primitive.arrayApply) - .appliedTo(Literal(Constant(n)))) - case refs1 => refs1 - abstracted(args ::: expandedRefs, resTpe, anonFun.info.finalResultType)( - using ctx.withOwner(anonFun)) - - val unadapted = Closure(anonFun, lambdaBody) - cpy.Block(unadapted)(unadapted.stats, adaptClosure(unadapted.expr.asInstanceOf[Closure])) - catch case ex: MatchError => - println(i"error while abstracting tree = $tree | mt = $mt | args = $args%, % | tp = $tp | pt = $pt") - throw ex - else - assert(args.length == targetLength, i"wrong # args tree = $tree | args = $args%, % | mt = $mt | tree type = ${tree.tpe}") - val app = untpd.cpy.Apply(tree1)(tree1, args) - assert(ctx.typer.isInstanceOf[Erasure.Typer]) - ctx.typer.typed(app, pt) - .changeOwnerAfter(origOwner, ctx.owner, erasurePhase.asInstanceOf[Erasure]) - - seq(defs.toList, abstracted(Nil, origType, pt)) - end etaExpand - end Boxing class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking { diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 75e7c55ada28..5d54798c812d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -70,7 +70,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): ref(defn.NoneModule)) } val tpe = MethodType(List(nme.s))(_ => List(tp1), mth => defn.OptionClass.typeRef.appliedTo(mth.newParamRef(0) & tp2)) - val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, tpe, coord = span) + val meth = newAnonFun(ctx.owner, tpe, coord = span) val typeTestType = defn.TypeTestClass.typeRef.appliedTo(List(tp1, tp2)) Closure(meth, tss => body(tss.head).changeOwner(ctx.owner, meth), targetType = typeTestType).withSpan(span) case _ => diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 1ee0d9d34dfa..6d9ff6ca68a8 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -212,7 +212,7 @@ object QuoteMatcher { } val argTypes = args.map(x => x.tpe.widenTermRefExpr) val methTpe = MethodType(names)(_ => argTypes, _ => pattern.tpe) - val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, methTpe) + val meth = newAnonFun(ctx.owner, methTpe) def bodyFn(lambdaArgss: List[List[Tree]]): Tree = { val argsMap = args.map(_.symbol).zip(lambdaArgss.head).toMap val body = new TreeMap { diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index ab651408a0c4..b05311f162fd 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -385,7 +385,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case t => t } val closureTpe = Types.MethodType(mtpe.paramNames, mtpe.paramInfos, closureResType) - val closureMethod = dotc.core.Symbols.newSymbol(owner, nme.ANON_FUN, Synthetic | Method, closureTpe) + val closureMethod = dotc.core.Symbols.newAnonFun(owner, closureTpe) tpd.Closure(closureMethod, tss => new tpd.TreeOps(self).appliedToTermArgs(tss.head).etaExpand(closureMethod)) case _ => self } @@ -793,7 +793,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object Lambda extends LambdaModule: def apply(owner: Symbol, tpe: MethodType, rhsFn: (Symbol, List[Tree]) => Tree): Block = - val meth = dotc.core.Symbols.newSymbol(owner, nme.ANON_FUN, Synthetic | Method, tpe) + val meth = dotc.core.Symbols.newAnonFun(owner, tpe) withDefaultPos(tpd.Closure(meth, tss => xCheckMacroedOwners(xCheckMacroValidExpr(rhsFn(meth, tss.head.map(withDefaultPos))), meth))) def unapply(tree: Block): Option[(List[ValDef], Term)] = tree match { diff --git a/tests/run/i13691.scala b/tests/run/i13691.scala new file mode 100644 index 000000000000..6db01ee0de35 --- /dev/null +++ b/tests/run/i13691.scala @@ -0,0 +1,53 @@ +import language.experimental.erasedDefinitions + +erased class CanThrow[-E <: Exception] +erased class Foo +class Bar + +object unsafeExceptions: + given canThrowAny: CanThrow[Exception] = null + +object test1: + trait Decoder[+T]: + def apply(): T + + def deco: Decoder[CanThrow[Exception] ?=> Int] = new Decoder[CanThrow[Exception] ?=> Int]: + def apply(): CanThrow[Exception] ?=> Int = 1 + +object test2: + trait Decoder[+T]: + def apply(): T + + def deco: Decoder[(CanThrow[Exception], Foo) ?=> Int] = new Decoder[(CanThrow[Exception], Foo) ?=> Int]: + def apply(): (CanThrow[Exception], Foo) ?=> Int = 1 + +object test3: + trait Decoder[+T]: + def apply(): T + + def deco: Decoder[CanThrow[Exception] ?=> Foo ?=> Int] = new Decoder[CanThrow[Exception] ?=> Foo ?=> Int]: + def apply(): CanThrow[Exception] ?=> Foo ?=> Int = 1 + +object test4: + trait Decoder[+T]: + def apply(): T + + def deco: Decoder[CanThrow[Exception] ?=> Bar ?=> Int] = new Decoder[CanThrow[Exception] ?=> Bar ?=> Int]: + def apply(): CanThrow[Exception] ?=> Bar ?=> Int = 1 + +object test5: + trait Decoder[+T]: + def apply(): T + + def deco: Decoder[Bar ?=> CanThrow[Exception] ?=> Int] = new Decoder[Bar ?=> CanThrow[Exception] ?=> Int]: + def apply(): Bar ?=> CanThrow[Exception] ?=> Int = 1 + +@main def Test(): Unit = + import unsafeExceptions.canThrowAny + given Foo = ??? + given Bar = Bar() + test1.deco.apply().apply + test2.deco.apply().apply + test3.deco.apply().apply + test4.deco.apply().apply + test5.deco.apply().apply diff --git a/tests/run/i13961a.scala b/tests/run/i13961a.scala new file mode 100644 index 000000000000..9d49ab30b0a4 --- /dev/null +++ b/tests/run/i13961a.scala @@ -0,0 +1,11 @@ +import language.experimental.saferExceptions + +trait Decoder[+T]: + def apply(): T + +given Decoder[Int throws Exception] = new Decoder[Int throws Exception]: + def apply(): Int throws Exception = 1 + +@main def Test(): Unit = + import unsafeExceptions.canThrowAny + summon[Decoder[Int throws Exception]]() \ No newline at end of file From 64db3eb37b2580e8e724fecbfad870684cf5bfe9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Nov 2021 10:24:52 +0100 Subject: [PATCH 3/4] Bring back deleted doc comment --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 7f2ec75af8df..16ce0e381c75 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1358,6 +1358,8 @@ class Definitions { * - FunctionN for 22 > N >= 0 remains as FunctionN * - ContextFunctionN for N > 22 becomes FunctionXXL * - ContextFunctionN for N <= 22 becomes FunctionN + * - ErasedFunctionN becomes Function0 + * - ImplicitErasedFunctionN becomes Function0 * - anything else becomes a NoType */ def functionTypeErasure(cls: Symbol): Type = From 7e6e9ecc58521f921918640e211137f4f3911db9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Nov 2021 10:43:47 +0100 Subject: [PATCH 4/4] Use "$direct" instead of just "$" to prevent erasure clashes Use "$direct" instead of just "$" to prevent erasure clashes of methods with erased context function results. --- compiler/src/dotty/tools/dotc/core/NameKinds.scala | 2 +- compiler/src/dotty/tools/dotc/core/NameTags.scala | 7 ++++--- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index e17e772de993..b77f870b72b7 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -365,7 +365,7 @@ object NameKinds { val ExtMethName: SuffixNameKind = new SuffixNameKind(EXTMETH, "$extension") val ParamAccessorName: SuffixNameKind = new SuffixNameKind(PARAMACC, "$accessor") val ModuleClassName: SuffixNameKind = new SuffixNameKind(OBJECTCLASS, "$", optInfoString = "ModuleClass") - val ImplMethName: SuffixNameKind = new SuffixNameKind(IMPLMETH, "$") + val DirectMethName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") val AdaptedClosureName: SuffixNameKind = new SuffixNameKind(ADAPTEDCLOSURE, "$adapted") { override def definesNewName = true } val SyntheticSetterName: SuffixNameKind = new SuffixNameKind(SETTER, "_$eq") diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index 63aea8853235..67dfcec73c53 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -24,8 +24,9 @@ object NameTags extends TastyFormat.NameTags { final val ADAPTEDCLOSURE = 31 // Used in Erasure to adapt closures over primitive types. - final val IMPLMETH = 32 // Used to define methods in implementation classes - // (can probably be removed). + final val DIRECT = 32 // Used to define implementations of methods with + // erased context function results that can override some + // other method. final val PARAMACC = 33 // Used for a private parameter alias @@ -48,7 +49,7 @@ object NameTags extends TastyFormat.NameTags { case INITIALIZER => "INITIALIZER" case FIELD => "FIELD" case EXTMETH => "EXTMETH" - case IMPLMETH => "IMPLMETH" + case DIRECT => "DIRECT" case PARAMACC => "PARAMACC" case ADAPTEDCLOSURE => "ADAPTEDCLOSURE" case OBJECTCLASS => "OBJECTCLASS" diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index c73b3c25c3f7..7d2ff375a9b8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -12,7 +12,7 @@ import core.Types._ import core.Names._ import core.StdNames._ import core.NameOps._ -import core.NameKinds.{AdaptedClosureName, BodyRetainerName, ImplMethName} +import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName} import core.Scopes.newScopeWith import core.Decorators._ import core.Constants._ @@ -63,10 +63,10 @@ class Erasure extends Phase with DenotTransformer { && contextResultsAreErased(ref.symbol) && (ref.owner.is(Flags.Trait) || ref.symbol.allOverriddenSymbols.hasNext) then - // Add a `$` to prevent this method from having the same signature + // Add a `$direct` to prevent this method from having the same signature // as a method it overrides. We need a bridge between the // two methods, so they are not allowed to already override after erasure. - ImplMethName(ref.targetName.asTermName) + DirectMethName(ref.targetName.asTermName) else ref.targetName