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..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 DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true } 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 1d5b4864f036..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 @@ -129,7 +130,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 +138,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 +146,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 +157,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 +196,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) @@ -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 9fdb447b26b9..71a10cf6eb20 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._ @@ -15,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._ @@ -25,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 { @@ -154,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 @@ -299,11 +333,12 @@ 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 { - case MethodType(Nil) if tree.isTerm => - adaptToType(tree.appliedToNone, pt) + def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree = pt match + 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) case tpw => if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt) tree @@ -317,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._ @@ -426,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 = { @@ -439,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) @@ -452,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) @@ -461,7 +556,7 @@ object Erasure { def selectArrayMember(qual: Tree, erasedPre: Type): Tree = if erasedPre.isAnyRef then - runtimeCallWithProtoArgs(tree.name.genericArrayOp, pt, qual) + partialApply(ref(defn.runtimeMethodRef(tree.name.genericArrayOp)), qual :: Nil) else if !(qual.tpe <:< erasedPre) then selectArrayMember(cast(qual, erasedPre), erasedPre) else @@ -507,27 +602,12 @@ object Erasure { outer.path(toCls = tree.symbol) } - private def runtimeCallWithProtoArgs(name: Name, pt: Type, args: 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 - } - 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(_)) @@ -538,36 +618,56 @@ 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 = { + /** 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 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 ownArgs = if origFunType.isErasedMethod then Nil else args + val fun1 = typedExpr(fun, AnyFunctionProto) + val fun1core = stripBlock(fun1) + fun1.tpe.widen match + case mt: MethodType => + 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 => + mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil) + case Apply(fun2, prevArgs) => + mkApply(fun2, prevArgs ++ args1) + case _ if bunchArgs => + mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil) + case _ => + mkApply(fun1, args1) + + app(fun1) + case t => + 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, // if the original proto-type is not a value type. @@ -608,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/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" 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]) - } -}