From fa3fc6d9ea2590f6d7effcfcd189686f3d815e48 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 Feb 2020 17:38:28 +0100 Subject: [PATCH 1/6] Do uncurrying "bottom up" A re-implementation of the uncurrying part of erasure. It now goes "bottom-up" by temporarily admitting Apply's that dop not yet have all their arguments. The previous implementation was "top-down" by looking at the prototypes. # Conflicts: # compiler/src/dotty/tools/dotc/transform/Erasure.scala --- .../dotty/tools/dotc/core/TypeErasure.scala | 14 +-- .../dotty/tools/dotc/transform/Erasure.scala | 95 ++++++++++--------- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 1d5b4864f036..e95e3d7139f1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -129,7 +129,7 @@ object TypeErasure { erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK)) /** The current context with a phase no later than erasure */ - private def erasureCtx(implicit ctx: Context) = + def preErasureCtx(implicit ctx: Context) = if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase) else ctx /** The standard erasure of a Scala type. Value classes are erased as normal classes. @@ -137,7 +137,7 @@ object TypeErasure { * @param tp The type to erase. */ def erasure(tp: Type)(implicit ctx: Context): Type = - erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(erasureCtx) + erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(preErasureCtx) /** The value class erasure of a Scala type, where value classes are semi-erased to * ErasedValueType (they will be fully erased in [[ElimErasedValueType]]). @@ -145,7 +145,7 @@ object TypeErasure { * @param tp The type to erase. */ def valueErasure(tp: Type)(implicit ctx: Context): Type = - erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(erasureCtx) + erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(preErasureCtx) /** Like value class erasure, but value classes erase to their underlying type erasure */ def fullErasure(tp: Type)(implicit ctx: Context): Type = @@ -156,7 +156,7 @@ object TypeErasure { def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = { val normTp = tp.underlyingIfRepeated(isJava) val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true) - erase.sigName(normTp)(erasureCtx) + erase.sigName(normTp)(preErasureCtx) } /** The erasure of a top-level reference. Differs from normal erasure in that @@ -195,9 +195,9 @@ object TypeErasure { if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractType) TypeAlias(WildcardType) - else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) - else if (sym.is(Label)) erase.eraseResult(sym.info)(erasureCtx) - else erase.eraseInfo(tp, sym)(erasureCtx) match { + else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(preErasureCtx)) + else if (sym.is(Label)) erase.eraseResult(sym.info)(preErasureCtx) + else erase.eraseInfo(tp, sym)(preErasureCtx) match { case einfo: MethodType => if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass)) MethodType(Nil, defn.BoxedUnitClass.typeRef) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 9fdb447b26b9..dcaaec489efa 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import core.Phases._ @@ -299,9 +300,9 @@ object Erasure { * e -> unbox(e, PT) if `PT` is a primitive type and `e` is not of primitive type * e -> cast(e, PT) otherwise */ - def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - if (pt.isInstanceOf[FunProto]) tree - else tree.tpe.widen match { + def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree = pt match + case _: FunProto | AnyFunctionProto => tree + case _ => tree.tpe.widen match { case MethodType(Nil) if tree.isTerm => adaptToType(tree.appliedToNone, pt) case tpw => @@ -461,7 +462,7 @@ object Erasure { def selectArrayMember(qual: Tree, erasedPre: Type): Tree = if erasedPre.isAnyRef then - runtimeCallWithProtoArgs(tree.name.genericArrayOp, pt, qual) + runtimeCall(tree.name.genericArrayOp, qual :: Nil) else if !(qual.tpe <:< erasedPre) then selectArrayMember(cast(qual, erasedPre), erasedPre) else @@ -507,20 +508,9 @@ object Erasure { outer.path(toCls = tree.symbol) } - private def runtimeCallWithProtoArgs(name: Name, pt: Type, args: Tree*)(implicit ctx: Context): Tree = { + private def runtimeCall(name: Name, args: List[Tree])(implicit ctx: Context): Tree = val meth = defn.runtimeMethodRef(name) - val followingParams = meth.symbol.info.firstParamTypes.drop(args.length) - val followingArgs = protoArgs(pt, meth.widen).zipWithConserve(followingParams)(typedExpr).asInstanceOf[List[tpd.Tree]] - ref(meth).appliedToArgs(args.toList ++ followingArgs) - } - - private def protoArgs(pt: Type, methTp: Type): List[untpd.Tree] = (pt, methTp) match { - case (pt: FunProto, methTp: MethodType) if methTp.isErasedMethod => - protoArgs(pt.resType, methTp.resType) - case (pt: FunProto, methTp: MethodType) => - pt.args ++ protoArgs(pt.resType, methTp.resType) - case _ => Nil - } + untpd.Apply(ref(meth), args.toList).withType(applyResultType(meth.widen.asInstanceOf[MethodType], args)) override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = { val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(ctx.withPhase(ctx.erasurePhase)).withSpan(tree.span) @@ -538,36 +528,51 @@ object Erasure { } } - /** Besides normal typing, this method collects all arguments - * to a compacted function into a single argument of array type. - */ - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + private def applyResultType(mt: MethodType, args: List[Tree], bunchArgs: Boolean = false)(using Context): Type = + if bunchArgs || mt.paramNames.length <= args.length then + mt.resultType + else + MethodType(mt.paramInfos.drop(args.length), mt.resultType) + + /** Besides normal typing, this method does uncurrying and collects parameters + * to anonymous functions of arity > 22. + * + */ + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = val Apply(fun, args) = tree if (fun.symbol == defn.cbnArg) typedUnadapted(args.head, pt) - else typedExpr(fun, FunProto(args, pt)(this, isUsingApply = false)) match { - case fun1: Apply => // arguments passed in prototype were already passed - fun1 - case fun1 => - fun1.tpe.widen match { - case mt: MethodType => - val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased - val ownArgs = if (mt.paramNames.nonEmpty && !mt.isErasedMethod) args else Nil - var args0 = outers ::: ownArgs ::: protoArgs(pt, tree.typeOpt) - - if (args0.length > MaxImplementedFunctionArity && mt.paramInfos.length == 1) { - val bunchedArgs = untpd.JavaSeqLiteral(args0, TypeTree(defn.ObjectType)) - .withType(defn.ArrayOf(defn.ObjectType)) - args0 = bunchedArgs :: Nil - } - assert(args0 hasSameLengthAs mt.paramInfos) - val args1 = args0.zipWithConserve(mt.paramInfos)(typedExpr) - untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType - case _ => - throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}") - } - } - } + else + val origFun = fun.asInstanceOf[tpd.Tree] + val origFunType = origFun.tpe.widen(using preErasureCtx) + val outers = outer.args(origFun) + val ownArgs = if origFunType.isErasedMethod then Nil else args + val args0 = outers ::: ownArgs + val fun1 = typedExpr(fun, AnyFunctionProto) + fun1.tpe.widen match + case mt: MethodType => + val bunchArgs = mt.paramInfos match + case JavaArrayType(elemType) :: Nil => // pre-test for efficiency + elemType.isRef(defn.ObjectClass) // pre-test for efficiency + && origFunType.paramInfoss.flatten.length > MaxImplementedFunctionArity // + case _ => false + val args1 = + if bunchArgs then args0.map(typedExpr(_, defn.ObjectType)) + else args0.zipWithConserve(mt.paramInfos)(typedExpr).asInstanceOf[List[Tree]] + val (fun2, args2) = fun1 match + case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs => + (fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) + case Apply(fun2, prevArgs) => + (fun2, prevArgs ++ args1) + case _ if bunchArgs => + (fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) + case _ => + (fun1, args1) + untpd.cpy.Apply(tree)(fun2, args2).withType(applyResultType(mt, args1, bunchArgs)) + case t => + if args0.isEmpty then fun1 + else throw new MatchError(i"tree $tree has unexpected type of function $fun1: $t, was $origFunType, args = $args0") + end typedApply // The following four methods take as the proto-type the erasure of the pre-existing type, // if the original proto-type is not a value type. From afacc725798cd68a2ee2514aa1fa3ee9e5e372c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 Feb 2020 23:37:34 +0100 Subject: [PATCH 2/6] Avoid double-passing of outer --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index dcaaec489efa..d02ab9b0bbfd 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -536,19 +536,18 @@ object Erasure { /** Besides normal typing, this method does uncurrying and collects parameters * to anonymous functions of arity > 22. - * - */ + */ override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = val Apply(fun, args) = tree - if (fun.symbol == defn.cbnArg) + if fun.symbol == defn.cbnArg then typedUnadapted(args.head, pt) else val origFun = fun.asInstanceOf[tpd.Tree] val origFunType = origFun.tpe.widen(using preErasureCtx) - val outers = outer.args(origFun) + val fun1 = typedExpr(fun, AnyFunctionProto) + val outers = if fun1.isInstanceOf[Apply] then Nil else outer.args(origFun) val ownArgs = if origFunType.isErasedMethod then Nil else args val args0 = outers ::: ownArgs - val fun1 = typedExpr(fun, AnyFunctionProto) fun1.tpe.widen match case mt: MethodType => val bunchArgs = mt.paramInfos match From d985eb7bbd62f84db299a8ca04867a19f6038ae1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 26 Feb 2020 08:55:49 +0100 Subject: [PATCH 3/6] Polishings --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index d02ab9b0bbfd..14d023a6f81c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -528,6 +528,7 @@ object Erasure { } } + /** The type of an Apply node which might still be missing some arguments */ private def applyResultType(mt: MethodType, args: List[Tree], bunchArgs: Boolean = false)(using Context): Type = if bunchArgs || mt.paramNames.length <= args.length then mt.resultType @@ -551,9 +552,9 @@ object Erasure { fun1.tpe.widen match case mt: MethodType => val bunchArgs = mt.paramInfos match - case JavaArrayType(elemType) :: Nil => // pre-test for efficiency - elemType.isRef(defn.ObjectClass) // pre-test for efficiency - && origFunType.paramInfoss.flatten.length > MaxImplementedFunctionArity // + case JavaArrayType(elemType) :: Nil => + elemType.isRef(defn.ObjectClass) + && origFunType.paramInfoss.flatten.length > MaxImplementedFunctionArity case _ => false val args1 = if bunchArgs then args0.map(typedExpr(_, defn.ObjectType)) From 0f41cea7af195b5d92255bce725845dca6c1efa1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 29 Feb 2020 16:49:06 +0100 Subject: [PATCH 4/6] Replace ShortcutImplicits Don't duplicate methods in ShortcutImplicits. Instead, always merge context function result parameters with normal parameters in one erased method, as long as the context functions were expanded as closures in the method's rhs. Om some cases (notably, but not exclusively, for bridges) this needs an eta expansion step at erasure. # Conflicts: # compiler/src/dotty/tools/dotc/core/NameKinds.scala # compiler/src/dotty/tools/dotc/transform/PostTyper.scala --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 - compiler/src/dotty/tools/dotc/ast/tpd.scala | 5 +- .../src/dotty/tools/dotc/config/Config.scala | 6 + .../dotty/tools/dotc/core/Definitions.scala | 17 +- .../src/dotty/tools/dotc/core/NameKinds.scala | 2 +- .../dotty/tools/dotc/core/TypeErasure.scala | 39 ++-- .../dotty/tools/dotc/transform/Bridges.scala | 27 +-- .../transform/ContextFunctionResults.scala | 145 ++++++++++++ .../dotty/tools/dotc/transform/Erasure.scala | 214 +++++++++++++----- .../tools/dotc/transform/PostTyper.scala | 2 + .../dotc/transform/ShortcutImplicits.scala | 202 ----------------- .../dotty/tools/dotc/transform/Splicer.scala | 8 +- .../dotty/tools/dotc/typer/EtaExpansion.scala | 11 +- .../dotty/tools/dotc/typer/RefChecks.scala | 1 - .../internal/ContextResultCount.scala | 9 + tests/run/context-functions.scala | 66 ++++++ tests/run/implicit-shortcut-bridge.scala | 45 ---- 17 files changed, 448 insertions(+), 352 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala delete mode 100644 compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala create mode 100644 library/src/scala/annotation/internal/ContextResultCount.scala create mode 100644 tests/run/context-functions.scala delete mode 100644 tests/run/implicit-shortcut-bridge.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e2e5229bb3fc..683ced4cb699 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -66,7 +66,6 @@ class Compiler { new ProtectedAccessors, // Add accessors for protected members new ExtensionMethods, // Expand methods of value classes with extension methods new CacheAliasImplicits, // Cache RHS of parameterless alias implicits - new ShortcutImplicits, // Allow implicit functions without creating closures new ByNameClosures, // Expand arguments to by-name parameters to closures new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 5f1717124a8c..b5d3bae31f2d 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -81,9 +81,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def seq(stats: List[Tree], expr: Tree)(implicit ctx: Context): Tree = if (stats.isEmpty) expr else expr match { + case Block(_, _: Closure) => + Block(stats, expr) // leave closures in their own block case Block(estats, eexpr) => cpy.Block(expr)(stats ::: estats, eexpr).withType(ta.avoidingType(eexpr, stats)) - case _ => Block(stats, expr) + case _ => + Block(stats, expr) } def If(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If = diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 628f998155a9..b97529296c19 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -169,6 +169,12 @@ object Config { /** If set, prints a trace of all symbol completions */ final val showCompletions = false + /** If set, method results that are context functions are flattened by adding + * the parameters of the context function results to the methods themselves. + * This is an optimization that reduces closure allocations. + */ + final val flattenContextFunctionResults = true + /** If set, enables tracing */ final val tracingEnabled = false diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9743557cc8a7..04b1d9b3749b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -783,8 +783,7 @@ class Definitions { @tu lazy val AnnotationDefaultAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.AnnotationDefault") @tu lazy val BodyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Body") @tu lazy val ChildAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Child") - @tu lazy val CovariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.CovariantBetween") - @tu lazy val ContravariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContravariantBetween") + @tu lazy val ContextResultCountAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContextResultCount") @tu lazy val DeprecatedAnnot: ClassSymbol = ctx.requiredClass("scala.deprecated") @tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitAmbiguous") @tu lazy val ImplicitNotFoundAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitNotFound") @@ -1268,6 +1267,20 @@ class Definitions { def isContextFunctionType(tp: Type)(implicit ctx: Context): Boolean = asContextFunctionType(tp).exists + /** An extractor for context function types `As ?=> B`, possibly with + * dependent refinements. Optionally returns a triple consisting of the argument + * types `As`, the result type `B` and a whether the type is an erased context function. + */ + object ContextFunctionType: + def unapply(tp: Type)(using ctx: Context): Option[(List[Type], Type, Boolean)] = + if ctx.erasedTypes then unapply(tp)(using ctx.withPhase(ctx.erasurePhase)) + else + val tp1 = tp.dealias + if isContextFunctionClass(tp1.typeSymbol) then + val args = asContextFunctionType(tp).dropDependentRefinement.argInfos + Some((args.init, args.last, tp1.typeSymbol.name.isErasedFunction)) + else None + def isErasedFunctionType(tp: Type)(implicit ctx: Context): Boolean = isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 0d6879229f3c..6228101cddfe 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -354,7 +354,7 @@ object NameKinds { val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$") val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$") - val DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true } + val CacheName = new SuffixNameKind(CACHE, "$_cache") val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") { override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index e95e3d7139f1..d74be5cb95c9 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -5,9 +5,10 @@ package core import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._ import Flags.JavaDefined import Uniques.unique -import dotc.transform.ExplicitOuter._ -import dotc.transform.ValueClasses._ +import transform.ExplicitOuter._ +import transform.ValueClasses._ import transform.TypeUtils._ +import transform.ContextFunctionResults._ import Decorators._ import Definitions.MaxImplementedFunctionArity import scala.annotation.tailrec @@ -526,21 +527,25 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean * `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them * to the underlying type. */ - def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case ExprType(rt) => - if (sym.is(Param)) apply(tp) - // Note that params with ExprTypes are eliminated by ElimByName, - // but potentially re-introduced by ResolveSuper, when we add - // forwarders to mixin methods. - // See doc comment for ElimByName for speculation how we could improve this. - else MethodType(Nil, Nil, eraseResult(sym.info.finalResultType.underlyingIfRepeated(isJava))) - case tp: PolyType => - eraseResult(tp.resultType) match { - case rt: MethodType => rt - case rt => MethodType(Nil, Nil, rt) - } - case tp => this(tp) - } + def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = + val tp1 = tp match + case tp: MethodicType => integrateContextResults(tp, contextResultCount(sym)) + case _ => tp + tp1 match + case ExprType(rt) => + if sym.is(Param) then apply(tp1) + // Note that params with ExprTypes are eliminated by ElimByName, + // but potentially re-introduced by ResolveSuper, when we add + // forwarders to mixin methods. + // See doc comment for ElimByName for speculation how we could improve this. + else + MethodType(Nil, Nil, + eraseResult(sym.info.finalResultType.underlyingIfRepeated(isJava))) + case tp1: PolyType => + eraseResult(tp1.resultType) match + case rt: MethodType => rt + case rt => MethodType(Nil, Nil, rt) + case tp1 => this(tp1) private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = { val cls = tref.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/transform/Bridges.scala b/compiler/src/dotty/tools/dotc/transform/Bridges.scala index 31d8e804cded..44b071e8792e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Bridges.scala +++ b/compiler/src/dotty/tools/dotc/transform/Bridges.scala @@ -7,7 +7,6 @@ import Symbols._, Types._, Contexts._, Decorators._, Flags._, Scopes._ import DenotTransformers._ import ast.untpd import collection.{mutable, immutable} -import ShortcutImplicits._ import util.Spans.Span import util.SourcePosition @@ -29,9 +28,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont override def parents = Array(root.superClass) override def exclude(sym: Symbol) = - !sym.isOneOf(MethodOrModule) || - isImplicitShortcut(sym) || - super.exclude(sym) + !sym.isOneOf(MethodOrModule) || super.exclude(sym) } //val site = root.thisType @@ -102,9 +99,10 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont } 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 ref.appliedToArgss(argss) + else Erasure.partialApply(ref, argss.head) } bridges += DefDef(bridge, bridgeRhs(_).withSpan(bridge.span)) @@ -113,24 +111,13 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont /** Add all necessary bridges to template statements `stats`, and remove at the same * time deferred methods in `stats` that are replaced by a bridge with the same signature. */ - def add(stats: List[untpd.Tree]): List[untpd.Tree] = { + def add(stats: List[untpd.Tree]): List[untpd.Tree] = val opc = new BridgesCursor()(preErasureCtx) val ectx = ctx.withPhase(thisPhase) - while (opc.hasNext) { - if (!opc.overriding.is(Deferred)) { + while opc.hasNext do + if !opc.overriding.is(Deferred) then addBridgeIfNeeded(opc.overriding, opc.overridden) - - if (needsImplicitShortcut(opc.overriding)(ectx) && needsImplicitShortcut(opc.overridden)(ectx)) - // implicit shortcuts do not show up in the Bridges cursor, since they - // are created only when referenced. Therefore we need to generate a bridge - // for them specifically, if one is needed for the original methods. - addBridgeIfNeeded( - shortcutMethod(opc.overriding, thisPhase)(ectx), - shortcutMethod(opc.overridden, thisPhase)(ectx)) - } opc.next() - } - if (bridges.isEmpty) stats + if bridges.isEmpty then stats else stats.filterNot(stat => toBeRemoved contains stat.symbol) ::: bridges.toList - } } diff --git a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala new file mode 100644 index 000000000000..9dae7a82d1b7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala @@ -0,0 +1,145 @@ +package dotty.tools +package dotc +package transform + +import core._ +import Contexts._, Symbols._, Types._, Annotations._, Constants._ +import StdNames.nme +import ast.untpd +import ast.tpd._ +import config.Config + +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. + */ + def annotateContextResults(mdef: DefDef)(using Context): Unit = + def contextResultCount(rhs: Tree, tp: Type): Int = tp match + case defn.ContextFunctionType(_, resTpe, _) => + rhs match + case closureDef(meth) => 1 + contextResultCount(meth.rhs, resTpe) + case _ => 0 + case _ => 0 + + val meth = mdef.symbol + + // Disable context result annotations for anonymous functions + // and for implementations of PolyFunction + def disabled = + meth.isAnonymousFunction + || meth.name == nme.apply + && meth.owner.isAnonymousClass + && meth.owner.info.parents.exists(_.isRef(defn.PolyFunctionClass)) + + val count = contextResultCount(mdef.rhs, mdef.tpt.tpe) + + if Config.flattenContextFunctionResults && count != 0 && !disabled then + val countAnnot = Annotation(defn.ContextResultCountAnnot, Literal(Constant(count))) + mdef.symbol.addAnnotation(countAnnot) + end annotateContextResults + + /** The argument of a ContextResultCount annotation, or 0 if none exists. + * See PostTyper#annotateContextResults. + */ + def contextResultCount(sym: Symbol)(using Context): Int = + sym.getAnnotation(defn.ContextResultCountAnnot) match + case Some(annot) => + val ast.Trees.Literal(Constant(crCount: Int)) :: Nil: @unchecked = annot.arguments + crCount + case none => 0 + + /** Turn the first `crCount` context function types in the result type of `tp` + * into the curried method types. + */ + def integrateContextResults(tp: Type, crCount: Int)(using Context): Type = + if crCount == 0 then tp + else tp match + case ExprType(rt) => + integrateContextResults(rt, crCount) + case tp: MethodOrPoly => + tp.derivedLambdaType(resType = integrateContextResults(tp.resType, crCount)) + case defn.ContextFunctionType(argTypes, resType, isErased) => + val methodType: MethodTypeCompanion = + if isErased then ErasedMethodType else MethodType + methodType(argTypes, integrateContextResults(resType, crCount - 1)) + + /** The total number of parameters of method `sym`, not counting + * erased parameters, but including context result parameters. + */ + def totalParamCount(sym: Symbol)(using Context): Int = + + def contextParamCount(tp: Type, crCount: Int): Int = + if crCount == 0 then 0 + else + val defn.ContextFunctionType(params, resTpe, isErased): @unchecked = tp + val rest = contextParamCount(resTpe, crCount - 1) + if isErased then rest else params.length + rest + + def normalParamCount(tp: Type): Int = tp.widenExpr.stripPoly match + case mt @ MethodType(pnames) => + val rest = normalParamCount(mt.resType) + if mt.isErasedMethod then rest else pnames.length + rest + case _ => contextParamCount(tp, contextResultCount(sym)) + + 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 ctx: Context) = + given Context = ctx.withPhase(ctx.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): @unchecked = tp + 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 + end contextFunctionResultTypeCovering + + /** Should selection `tree` be eliminated since it refers to an `apply` + * node of a context function type whose parameters will end up being + * integrated in the preceding method? + * @param `n` the select nodes seen in previous recursive iterations of this method + */ + def integrateSelect(tree: untpd.Tree, n: Int = 0)(using ctx: Context): Boolean = + if ctx.erasedTypes then + integrateSelect(tree, n)(using ctx.withPhase(ctx.erasurePhase)) + else tree match + case Select(qual, name) => + if name == nme.apply && defn.isContextFunctionClass(tree.symbol.maybeOwner) then + integrateSelect(qual, n + 1) + else + n > 0 && contextResultCount(tree.symbol) >= n + case Ident(name) => + n > 0 && contextResultCount(tree.symbol) >= n + case Apply(fn, args) => + integrateSelect(fn, n) + case TypeApply(fn, _) => + integrateSelect(fn, n) + case Block(_, expr) => + integrateSelect(expr, n) + case Inlined(_, _, expr) => + integrateSelect(expr, n) + case _ => + false + +end ContextFunctionResults \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 14d023a6f81c..71a10cf6eb20 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -16,7 +16,7 @@ import core.NameKinds.AdaptedClosureName import core.Decorators._ import core.Constants._ import core.Definitions._ -import typer.NoChecking +import typer.{NoChecking, LiftErased} import typer.Inliner import typer.ProtoTypes._ import core.TypeErasure._ @@ -26,9 +26,12 @@ import ast.Trees._ import dotty.tools.dotc.core.{Constants, Flags} import ValueClasses._ import TypeUtils._ +import ContextFunctionResults._ import ExplicitOuter._ import core.Mode +import util.Property import reporting.trace +import collection.mutable class Erasure extends Phase with DenotTransformer { @@ -155,7 +158,37 @@ object Erasure { val name: String = "erasure" - object Boxing { + /** An attachment on Apply nodes indicating that multiple arguments + * are passed in a single array. This occurs only if the function + * implements a FunctionXXL apply. + */ + private val BunchedArgs = new Property.Key[Unit] + + /** An Apply node which might still be missing some arguments */ + def partialApply(fn: Tree, args: List[Tree])(using Context): Tree = + untpd.Apply(fn, args.toList) + .withType(applyResultType(fn.tpe.widen.asInstanceOf[MethodType], args)) + + /** The type of an Apply node which might still be missing some arguments */ + private def applyResultType(mt: MethodType, args: List[Tree])(using Context): Type = + if mt.paramInfos.length <= args.length then mt.resultType + else MethodType(mt.paramInfos.drop(args.length), mt.resultType) + + /** The method type corresponding to `mt`, except that bunched parameters + * are expanded. The expansion is determined using the original function + * tree `origFun` which has a type that is not yet converted to a type + * with bunched arguments. + */ + def expandedMethodType(mt: MethodType, origFun: Tree)(using Context): MethodType = + mt.paramInfos match + case JavaArrayType(elemType) :: Nil if elemType.isRef(defn.ObjectClass) => + val origArity = totalParamCount(origFun.symbol)(using preErasureCtx) + if origArity > MaxImplementedFunctionArity then + MethodType(List.fill(origArity)(defn.ObjectType), mt.resultType) + else mt + case _ => mt + + object Boxing: def isUnbox(sym: Symbol)(implicit ctx: Context): Boolean = sym.name == nme.unbox && sym.owner.linkedClass.isPrimitiveValueClass @@ -302,9 +335,10 @@ object Erasure { */ def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree = pt match case _: FunProto | AnyFunctionProto => tree - case _ => tree.tpe.widen match { - case MethodType(Nil) if tree.isTerm => - adaptToType(tree.appliedToNone, pt) + 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) case tpw => if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt) tree @@ -318,8 +352,61 @@ object Erasure { adaptToType(unbox(tree, pt), pt) else cast(tree, pt) - } - } + end adaptToType + + /** 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 ctx: Context): Tree = + ctx.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 ctx: Context): Tree = + if args.length < targetLength then + try + val defn.ContextFunctionType(argTpes, resTpe, isErased): @unchecked = tp + if isErased then abstracted(args, resTpe, pt) + else + val anonFun = ctx.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 : @unchecked = refss + 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)) + Closure(anonFun, lambdaBody) + 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[Typer]) + ctx.typer.typed(app, pt) + .changeOwnerAfter(origOwner, ctx.owner, ctx.erasurePhase.asInstanceOf[Erasure]) + + seq(defs.toList, abstracted(Nil, origType, pt)) + end etaExpand + + end Boxing class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking { import Boxing._ @@ -427,6 +514,9 @@ object Erasure { * e.m -> e.[]m if `m` is an array operation other than `clone`. */ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + if tree.name == nme.apply && integrateSelect(tree) then + return typed(tree.qualifier, pt) + val qual1 = typed(tree.qualifier, AnySelectionProto) def mapOwner(sym: Symbol): Symbol = { @@ -440,7 +530,7 @@ object Erasure { } polyOwner orElse { - val owner = sym.owner + val owner = sym.maybeOwner if (defn.specialErasure.contains(owner)) { assert(sym.isConstructor, s"${sym.showLocated}") defn.specialErasure(owner) @@ -453,6 +543,10 @@ object Erasure { } val origSym = tree.symbol + + if !origSym.exists && qual1.tpe.widen.isInstanceOf[JavaArrayType] then + return tree.asInstanceOf[Tree] // we are re-typing a primitive array op + val owner = mapOwner(origSym) val sym = if (owner eq origSym.maybeOwner) origSym else owner.info.decl(tree.name).symbol assert(sym.exists, origSym.showLocated) @@ -462,7 +556,7 @@ object Erasure { def selectArrayMember(qual: Tree, erasedPre: Type): Tree = if erasedPre.isAnyRef then - runtimeCall(tree.name.genericArrayOp, qual :: Nil) + partialApply(ref(defn.runtimeMethodRef(tree.name.genericArrayOp)), qual :: Nil) else if !(qual.tpe <:< erasedPre) then selectArrayMember(cast(qual, erasedPre), erasedPre) else @@ -508,16 +602,12 @@ object Erasure { outer.path(toCls = tree.symbol) } - private def runtimeCall(name: Name, args: List[Tree])(implicit ctx: Context): Tree = - val meth = defn.runtimeMethodRef(name) - untpd.Apply(ref(meth), args.toList).withType(applyResultType(meth.widen.asInstanceOf[MethodType], args)) - override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = { val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(ctx.withPhase(ctx.erasurePhase)).withSpan(tree.span) ntree match { case TypeApply(fun, args) => - val fun1 = typedExpr(fun, WildcardType) + val fun1 = typedExpr(fun, AnyFunctionProto) fun1.tpe.widen match { case funTpe: PolyType => val args1 = args.mapconserve(typedType(_)) @@ -528,13 +618,6 @@ object Erasure { } } - /** The type of an Apply node which might still be missing some arguments */ - private def applyResultType(mt: MethodType, args: List[Tree], bunchArgs: Boolean = false)(using Context): Type = - if bunchArgs || mt.paramNames.length <= args.length then - mt.resultType - else - MethodType(mt.paramInfos.drop(args.length), mt.resultType) - /** Besides normal typing, this method does uncurrying and collects parameters * to anonymous functions of arity > 22. */ @@ -545,33 +628,45 @@ object Erasure { else val origFun = fun.asInstanceOf[tpd.Tree] val origFunType = origFun.tpe.widen(using preErasureCtx) - val fun1 = typedExpr(fun, AnyFunctionProto) - val outers = if fun1.isInstanceOf[Apply] then Nil else outer.args(origFun) val ownArgs = if origFunType.isErasedMethod then Nil else args - val args0 = outers ::: ownArgs + val fun1 = typedExpr(fun, AnyFunctionProto) + val fun1core = stripBlock(fun1) fun1.tpe.widen match case mt: MethodType => - val bunchArgs = mt.paramInfos match - case JavaArrayType(elemType) :: Nil => - elemType.isRef(defn.ObjectClass) - && origFunType.paramInfoss.flatten.length > MaxImplementedFunctionArity - case _ => false - val args1 = - if bunchArgs then args0.map(typedExpr(_, defn.ObjectType)) - else args0.zipWithConserve(mt.paramInfos)(typedExpr).asInstanceOf[List[Tree]] - val (fun2, args2) = fun1 match + val (xmt, // A method type like `mt` but with bunched arguments expanded to individual ones + bunchArgs, // whether arguments are bunched + outers) = // the outer reference parameter(s) + if fun1core.isInstanceOf[Apply] then + (mt, fun1core.removeAttachment(BunchedArgs).isDefined, Nil) + else + val xmt = expandedMethodType(mt, origFun) + (xmt, xmt ne mt, outer.args(origFun)) + + val args0 = outers ::: ownArgs + val args1 = args0.zipWithConserve(xmt.paramInfos)(typedExpr) + .asInstanceOf[List[Tree]] + + def mkApply(finalFun: Tree, finalArgs: List[Tree]) = + val app = untpd.cpy.Apply(tree)(finalFun, finalArgs) + .withType(applyResultType(xmt, args1)) + if bunchArgs then app.withAttachment(BunchedArgs, ()) else app + + def app(fun1: Tree): Tree = fun1 match + case Block(stats, expr) => + cpy.Block(fun1)(stats, app(expr)) case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs => - (fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) + mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) case Apply(fun2, prevArgs) => - (fun2, prevArgs ++ args1) + mkApply(fun2, prevArgs ++ args1) case _ if bunchArgs => - (fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) + mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) case _ => - (fun1, args1) - untpd.cpy.Apply(tree)(fun2, args2).withType(applyResultType(mt, args1, bunchArgs)) + mkApply(fun1, args1) + + app(fun1) case t => - if args0.isEmpty then fun1 - else throw new MatchError(i"tree $tree has unexpected type of function $fun1: $t, was $origFunType, args = $args0") + if ownArgs.isEmpty then fun1 + else throw new MatchError(i"tree $tree has unexpected type of function $fun/$fun1: $t, was $origFunType, args = $ownArgs") end typedApply // The following four methods take as the proto-type the erasure of the pre-existing type, @@ -613,32 +708,43 @@ object Erasure { */ override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree = if (sym.isEffectivelyErased) erasedDef(sym) - else { - val restpe = - if (sym.isConstructor) defn.UnitType - else sym.info.resultType - var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil - var rhs1 = ddef.rhs - if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) { + else + val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType + var vparams = outer.paramDefs(sym) + ::: ddef.vparamss.flatten.filterConserve(!_.symbol.is(Flags.Erased)) + + def skipContextClosures(rhs: Tree, crCount: Int)(using Context): Tree = + if crCount == 0 then rhs + else rhs match + case closureDef(meth) => + val contextParams = meth.vparamss.head + for param <- contextParams do + param.symbol.copySymDenotation(owner = sym).installAfter(erasurePhase) + vparams ++= contextParams + if crCount == 1 then meth.rhs.changeOwnerAfter(meth.symbol, sym, erasurePhase) + else skipContextClosures(meth.rhs, crCount - 1) + + var rhs1 = skipContextClosures(ddef.rhs.asInstanceOf[Tree], contextResultCount(sym)) + + if sym.isAnonymousFunction && vparams.length > MaxImplementedFunctionArity then val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType)) def selector(n: Int) = ref(bunchedParam) .select(defn.Array_apply) .appliedTo(Literal(Constant(n))) - val paramDefs = vparamss1.head.zipWithIndex.map { + val paramDefs = vparams.zipWithIndex.map { case (paramDef, idx) => assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol) } - vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil - rhs1 = untpd.Block(paramDefs, rhs1) - } - vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Erased))) + vparams = ValDef(bunchedParam) :: Nil + rhs1 = Block(paramDefs, rhs1) + val ddef1 = untpd.cpy.DefDef(ddef)( tparams = Nil, - vparamss = vparamss1, + vparamss = vparams :: Nil, tpt = untpd.TypedSplice(TypeTree(restpe).withSpan(ddef.tpt.span)), rhs = rhs1) super.typedDefDef(ddef1, sym) - } + end typedDefDef override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = { val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 7d5b9f4c741a..6a57a8f1bbfe 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -11,6 +11,7 @@ import Types._, Contexts._, Names._, Flags._, DenotTransformers._, Phases._ import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ import Symbols._, SymUtils._ +import ContextFunctionResults.annotateContextResults import config.Printers.typr import reporting.diagnostic.messages._ @@ -262,6 +263,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) processMemberDef(super.transform(tree1)) case tree: DefDef => + annotateContextResults(tree) val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) processMemberDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) case tree: TypeDef => diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala deleted file mode 100644 index a7a102c1981c..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ /dev/null @@ -1,202 +0,0 @@ -package dotty.tools.dotc -package transform - -import MegaPhase._ -import core.DenotTransformers.{DenotTransformer, IdentityDenotTransformer} -import core.Symbols._ -import core.Contexts._ -import core.Types._ -import core.Flags._ -import core.StdNames.nme -import core.NameKinds.DirectMethodName -import ast.Trees._ -import ast.tpd -import util.Store - -/** This phase optimizes code using implicit function types, by applying two rewrite rules. - * Let IF be the implicit function type - * - * implicit Us => R - * - * (1) A method definition - * - * def m(xs: Ts): IF = implicit (ys: Us) => E - * - * is expanded to two methods: - * - * def m(xs: Ts): IF = implicit (ys: Us) => m$direct(xs)(ys) - * def m$direct(xs: Ts)(ys: Us): R = E - * - * (and equivalently for methods with type parameters or a different number of value parameter lists). - * An abstract method definition - * - * def m(xs: Ts): IF - * - * is expanded to: - * - * def m(xs: Ts): IF - * def m$direct(xs: Ts)(ys: Us): R - * - * (2) A reference `qual.apply` where `qual` has implicit function type and - * `qual` refers to a method `m` is rewritten to a reference to `m$direct`, - * keeping the same type and value arguments as they are found in `qual`. - * - * Note: The phase adds direct methods for all methods with IFT results that - * are defined in the transformed compilation unit, as well as for all - * methods that are referenced from inside the unit. It does NOT do an - * info transformer that adds these methods everywhere where an IFT returning - * method exists (including in separately compiled classes). - * Adding such an info transformer is impractical because it would mean - * that we have to force the types of all members of classes that are referenced. - * But not adding an info transformer can lead to inconsistencies in RefChecks. - * We solve that by ignoring direct methods in Refchecks. - * Another, related issue is bridge generation, where we also generate - * shortcut methods on the fly. - */ -class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPhase => - import ShortcutImplicits._ - import tpd._ - - override def phaseName: String = ShortcutImplicits.name - - override def changesMembers: Boolean = true // the phase adds "direct" methods - - /** A map to cache mapping local methods to their direct counterparts. - * A fresh map is created for each unit. - */ - private var DirectMeth: Store.Location[MutableSymbolMap[Symbol]] = _ - private def directMeth(implicit ctx: Context) = ctx.store(DirectMeth) - - override def initContext(ctx: FreshContext): Unit = - DirectMeth = ctx.addLocation[MutableSymbolMap[Symbol]]() - - - override def prepareForUnit(tree: Tree)(implicit ctx: Context): Context = - ctx.fresh.updateStore(DirectMeth, newMutableSymbolMap[Symbol]) - - - /** The direct method `m$direct` that accompanies the given method `m`. - * Create one if it does not exist already. - */ - private def directMethod(sym: Symbol)(implicit ctx: Context): Symbol = - if (sym.owner.isClass) shortcutMethod(sym, thisPhase) - else directMeth.getOrElseUpdate(sym, newShortcutMethod(sym)) - - /** Transform `qual.apply` occurrences according to rewrite rule (2) above */ - override def transformSelect(tree: Select)(implicit ctx: Context): Tree = - if (tree.name == nme.apply && - defn.isContextFunctionType(tree.qualifier.tpe.widen) && - needsImplicitShortcut(tree.qualifier.symbol)) { - def directQual(tree: Tree): Tree = tree match { - case Apply(fn, args) => cpy.Apply(tree)(directQual(fn), args) - case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args) - case Block(stats, expr) => cpy.Block(tree)(stats, directQual(expr)) - case tree: RefTree => - cpy.Ref(tree)(DirectMethodName(tree.name.asTermName)) - .withType(tree.tpe.asInstanceOf[NamedType].prefix.select(directMethod(tree.symbol))) - } - directQual(tree.qualifier) - } - else tree - - /** Transform methods with implicit function type result according to rewrite rule (1) above */ - override def transformDefDef(mdef: DefDef)(implicit ctx: Context): Tree = { - val original = mdef.symbol - if (needsImplicitShortcut(original)) { - val direct = directMethod(original) - - // Move @tailrec to the direct method - original.getAnnotation(defn.TailrecAnnot) match { - case Some(annot) => - direct.addAnnotation(annot) - original.removeAnnotation(defn.TailrecAnnot) - case _ => - } - - def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match { - case Block(Nil, expr) => splitClosure(expr) - case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: Nil, cl: Closure) => - val tparamSyms = mdef.tparams.map(_.symbol) - val vparamSymss = mdef.vparamss.map(_.map(_.symbol)) - val clparamSyms = clparams.map(_.symbol) - val remappedCore = (ts: List[Type]) => (prefss: List[List[Tree]]) => - meth.rhs - .subst(tparamSyms ::: (vparamSymss.flatten ++ clparamSyms), - ts.map(_.typeSymbol) ::: prefss.flatten.map(_.symbol)) - .changeOwnerAfter(original, direct, thisPhase) - .changeOwnerAfter(meth.symbol, direct, thisPhase) - val forwarder = ref(direct) - .appliedToTypeTrees(tparamSyms.map(ref(_))) - .appliedToArgss(vparamSymss.map(_.map(ref(_))) :+ clparamSyms.map(ref(_))) - val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: Nil, cl) - (remappedCore, fwdClosure) - case id: RefTree => - val SAMType(mt) = id.tpe.widen - splitClosure(tpd.Lambda(mt, args => id.select(nme.apply).appliedToArgs(args))(ctx.withOwner(original))) - case EmptyTree => - (_ => _ => EmptyTree, EmptyTree) - } - - val (remappedCore, fwdClosure) = splitClosure(mdef.rhs) - val originalDef = cpy.DefDef(mdef)(rhs = fwdClosure) - val directDef = transformDefDef(polyDefDef(direct.asTerm, remappedCore)) - flatTree(List(originalDef, directDef)) - } - else mdef - } -} - -object ShortcutImplicits { - val name: String = "shortcutImplicits" - - /** If this option is true, we don't specialize symbols that are known to be only - * targets of monomorphic calls. - * The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch - * scenarios inlining and escape analysis can often remove all calling overhead, so we might as - * well not duplicate the code. We need more experience to decide on the best setting of this option. - */ - final val specializeMonoTargets = true - - /** Should `sym` get a ..$direct companion? - * This is the case if `sym` is a method with a non-nullary implicit function type as final result type. - * However if `specializeMonoTargets` is false, we exclude symbols that are known - * to be only targets of monomorphic calls because they are effectively - * final and don't override anything. - */ - def needsImplicitShortcut(sym: Symbol)(implicit ctx: Context): Boolean = - sym.is(Method, butNot = Accessor) && - defn.isContextFunctionType(sym.info.finalResultType) && - defn.functionArity(sym.info.finalResultType) > 0 && - !sym.isAnonymousFunction && - (specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty) - - /** @pre The type's final result type is an implicit function type `implicit Ts => R`. - * @return The type of the `apply` member of `implicit Ts => R`. - */ - private def directInfo(info: Type)(implicit ctx: Context): Type = info match { - case info: PolyType => info.derivedLambdaType(resType = directInfo(info.resultType)) - case info: MethodType => info.derivedLambdaType(resType = directInfo(info.resultType)) - case info: ExprType => directInfo(info.resultType) - case info => info.member(nme.apply).info - } - - /** A new `m$direct` method to accompany the given method `m` */ - private def newShortcutMethod(sym: Symbol)(implicit ctx: Context): Symbol = { - val direct = sym.copy( - name = DirectMethodName(sym.name.asTermName).asInstanceOf[sym.ThisName], - flags = sym.flags | Synthetic, - info = directInfo(sym.info)) - // make flags conformant to RefChecks. Override is meaningless after RefChecks. - if (direct.allOverriddenSymbols.isEmpty) direct.resetFlag(Override) - direct - } - - def shortcutMethod(sym: Symbol, phase: DenotTransformer)(implicit ctx: Context): Symbol = - sym.owner.info.decl(DirectMethodName(sym.name.asTermName)) - .suchThat(_.info matches directInfo(sym.info)).symbol - .orElse(newShortcutMethod(sym).enteredAfter(phase)) - - def isImplicitShortcut(sym: Symbol)(implicit ctx: Context): Boolean = sym.name.is(DirectMethodName) -} - - diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 7c747b6cc92a..c01962ea3027 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -315,13 +315,7 @@ object Splicer { (inst, inst.getClass) } - def getDirectName(tp: Type, name: TermName): TermName = tp.widenDealias match { - case tp: AppliedType if defn.isContextFunctionType(tp) => - getDirectName(tp.args.last, NameKinds.DirectMethodName(name)) - case _ => name - } - - val name = getDirectName(fn.info.finalResultType, fn.name.asTermName) + val name = fn.name.asTermName val method = getMethod(clazz, name, paramsSig(fn)) (args: List[Object]) => stopIfRuntimeException(method.invoke(inst, args: _*), method) } diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index ec2cc685fb4f..9af82375dd0a 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -40,6 +40,9 @@ abstract class Lifter { /** The tree of a lifted definition */ protected def liftedDef(sym: TermSymbol, rhs: Tree)(implicit ctx: Context): MemberDef = ValDef(sym, rhs) + /** Is lifting performed on erased terms? */ + protected def isErased = false + private def lift(defs: mutable.ListBuffer[Tree], expr: Tree, prefix: TermName = EmptyTermName)(implicit ctx: Context): Tree = if (noLift(expr)) expr else { @@ -107,7 +110,10 @@ abstract class Lifter { */ def liftApp(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = tree match { case Apply(fn, args) => - cpy.Apply(tree)(liftApp(defs, fn), liftArgs(defs, fn.tpe, args)) + val fn1 = liftApp(defs, fn) + val args1 = liftArgs(defs, fn.tpe, args) + if isErased then untpd.cpy.Apply(tree)(fn1, args1).withType(tree.tpe) // application may be partial + else cpy.Apply(tree)(fn1, args1) case TypeApply(fn, targs) => cpy.TypeApply(tree)(liftApp(defs, fn), targs) case Select(pre, name) if isPureRef(tree) => @@ -149,6 +155,9 @@ class LiftComplex extends Lifter { } object LiftComplex extends LiftComplex +object LiftErased extends LiftComplex: + override def isErased = true + /** Lift all impure or complex arguments to `def`s */ object LiftToDefs extends LiftComplex { override def liftedFlags: FlagSet = Method diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 8a1e2bd11390..9792ba1ee061 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -475,7 +475,6 @@ object RefChecks { def ignoreDeferred(mbr: Symbol) = mbr.isType || mbr.isSuperAccessor // not yet synthesized - || ShortcutImplicits.isImplicitShortcut(mbr) // only synthesized when referenced, see Note in ShortcutImplicits || mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr) def isImplemented(mbr: Symbol) = diff --git a/library/src/scala/annotation/internal/ContextResultCount.scala b/library/src/scala/annotation/internal/ContextResultCount.scala new file mode 100644 index 000000000000..ab6b9ef0f6ba --- /dev/null +++ b/library/src/scala/annotation/internal/ContextResultCount.scala @@ -0,0 +1,9 @@ +package scala.annotation +package internal + +/** An annotation that's aitomatically added for methods + * that have one or more nested context closures as their right hand side. + * The parameter `n` is an Int Literal that tells how many nested closures + * there are. + */ +class ContextResultCount(n: Int) extends StaticAnnotation diff --git a/tests/run/context-functions.scala b/tests/run/context-functions.scala new file mode 100644 index 000000000000..e71f0de10378 --- /dev/null +++ b/tests/run/context-functions.scala @@ -0,0 +1,66 @@ +trait A: + + type Ctx[T] + type Mega[T] + + def f: Ctx[Ctx[Int]] + def g(x: Boolean): Ctx[Int] + val a: Ctx[Int] + def b: Ctx[Int] + + def h(x: Int): String ?=> Int + def mega: Mega[Int] + + def trans(x: Ctx[Int]): Ctx[Int] = x +end A + +object m extends A: + + type Ctx[T] = String ?=> T + type Mega[T] = (Int, Int, Int, Int, Int, + Int, Int, Int, Int, Int, + Int, Int, Int, Int, Int, + Int, Int, Int, Int, Int, + Int, Int, String) ?=> T + + def f: Ctx[Ctx[Int]] = summon[String].length + + def g(x: Boolean): Ctx[Int] = + if x then summon[String].length else 0 + val a: Ctx[Int] = summon[String].length + var b: Ctx[Int] = _ + b = summon[String].length + + def h(x: Int): Ctx[Int] = x + g(true) + f + a + b + + def mega: Mega[Int] = summon[String].length +end m + +trait B: + + type Ctx[T] + + def wrap[T](x: T): Ctx[T] + def drop[T](x: Ctx[T]): T + def id[T](x: T): T = drop(wrap(x)) + +end B +object n extends B: + type Ctx[T] = String ?=> Int ?=> T + + def wrap[T](x: T): Ctx[T] = x + def drop[T](x: Ctx[T]): T = x(using "X")(using 22) +end n + +@main def Test = + assert(m.h(1)(using "abc") == 13) + val a: A = m + assert(a.h(1)(using "abc") == 13) + locally { + given Int = 2 + given String = "abc" + assert(m.mega == 3) + assert(m.trans(m.a) == 3) + } + assert(n.id(1.0) == 1.0) + diff --git a/tests/run/implicit-shortcut-bridge.scala b/tests/run/implicit-shortcut-bridge.scala deleted file mode 100644 index 811d57b56174..000000000000 --- a/tests/run/implicit-shortcut-bridge.scala +++ /dev/null @@ -1,45 +0,0 @@ -abstract class A[T] { - def foo: T -} -class B extends A[Int ?=> Int] { - // No bridge needed for foo$direct - def foo: Int ?=> Int = 1 -} - -abstract class X[T] extends A[T ?=> T] { - def foo: T ?=> T -} - -class Y extends X[Int] { - def foo: Int ?=> Int = 1 -} - -object Test { - def check(expected: Set[String], cls: Class[_]): Unit = { - val actual = cls.getMethods.filter(_.getName.startsWith("foo")).map(_.toString).toSet - assert(expected == actual, s"[$cls] expected: ${expected}\nactual: $actual") - } - - def main(args: Array[String]): Unit = { - val expectedB = Set( - "public scala.Function1 B.foo()", // user-written method - "public int B.foo$direct(int)", // shortcut added by ShortcutImplicits - "public java.lang.Object B.foo()" // bridge to A#foo - ) - val expectedX = Set( - "public abstract scala.Function1 X.foo()", // user-written method - "public abstract java.lang.Object X.foo$direct(java.lang.Object)", // shortcut - "public abstract java.lang.Object A.foo()" // Inherited from A - ) - val expectedY = Set( - "public scala.Function1 Y.foo()", // user-written method - "public java.lang.Object Y.foo()", // Bridge to A#foo - "public int Y.foo$direct(int)", // shortcut - "public java.lang.Object Y.foo$direct(java.lang.Object)", // bridge to X#foo$direct - ) - - check(expectedB, classOf[B]) - check(expectedX, classOf[X[_]]) - check(expectedY, classOf[Y]) - } -} From 923d5913f9fdc8ae8b9e77e22a8146709bcbf0e4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 Mar 2020 17:33:29 +0100 Subject: [PATCH 5/6] Fix cmdTest Always use bootstrapped compiler for macro operations --- project/scripts/cmdTests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/scripts/cmdTests b/project/scripts/cmdTests index 7bbc2d97aa85..0b6e5a889d3c 100755 --- a/project/scripts/cmdTests +++ b/project/scripts/cmdTests @@ -39,7 +39,7 @@ grep -qe "def main(args: scala.Array\[scala.Predef.String\]): scala.Unit =" "$tm echo "testing sbt dotc with suspension" clear_out "$OUT" -"$SBT" "dotc -d $OUT tests/pos-macros/macros-in-same-project-1/Bar.scala tests/pos-macros/macros-in-same-project-1/Foo.scala" > "$tmp" +"$SBT" "dotty-compiler-bootstrapped/dotc -d $OUT tests/pos-macros/macros-in-same-project-1/Bar.scala tests/pos-macros/macros-in-same-project-1/Foo.scala" > "$tmp" # check that missing source file does not crash message rendering echo "testing that missing source file does not crash message rendering" From cbd21a0fbfd0c566f55649dc1dcfcb054529f18b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 6 Mar 2020 09:16:29 +0100 Subject: [PATCH 6/6] Fix rebase breakage --- compiler/src/dotty/tools/dotc/core/NameKinds.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 6228101cddfe..04767f86fe06 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -354,7 +354,6 @@ object NameKinds { val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$") val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$") - val CacheName = new SuffixNameKind(CACHE, "$_cache") val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") { override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString }