Skip to content

Commit 5f53cfa

Browse files
committed
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.
1 parent a143000 commit 5f53cfa

17 files changed

+447
-352
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ class Compiler {
6666
new ProtectedAccessors, // Add accessors for protected members
6767
new ExtensionMethods, // Expand methods of value classes with extension methods
6868
new CacheAliasImplicits, // Cache RHS of parameterless alias implicits
69-
new ShortcutImplicits, // Allow implicit functions without creating closures
7069
new ByNameClosures, // Expand arguments to by-name parameters to closures
7170
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope
7271
new ClassOf, // Expand `Predef.classOf` calls.

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
8181
def seq(stats: List[Tree], expr: Tree)(implicit ctx: Context): Tree =
8282
if (stats.isEmpty) expr
8383
else expr match {
84+
case Block(_, _: Closure) =>
85+
Block(stats, expr) // leave closures in their own block
8486
case Block(estats, eexpr) =>
8587
cpy.Block(expr)(stats ::: estats, eexpr).withType(ta.avoidingType(eexpr, stats))
86-
case _ => Block(stats, expr)
88+
case _ =>
89+
Block(stats, expr)
8790
}
8891

8992
def If(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If =

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ object Config {
163163
/** If set, prints a trace of all symbol completions */
164164
final val showCompletions = false
165165

166+
/** If set, method results that are context functions are flattened by adding
167+
* the parameters of the context function results to the methods themselves.
168+
* This is an optimization that reduces closure allocations.
169+
*/
170+
final val flattenContextFunctionResults = true
171+
166172
/** If set, enables tracing */
167173
final val tracingEnabled = false
168174

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,7 @@ class Definitions {
784784
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.AnnotationDefault")
785785
@tu lazy val BodyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Body")
786786
@tu lazy val ChildAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Child")
787-
@tu lazy val CovariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.CovariantBetween")
788-
@tu lazy val ContravariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContravariantBetween")
787+
@tu lazy val ContextResultCountAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContextResultCount")
789788
@tu lazy val DeprecatedAnnot: ClassSymbol = ctx.requiredClass("scala.deprecated")
790789
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitAmbiguous")
791790
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitNotFound")
@@ -1269,6 +1268,20 @@ class Definitions {
12691268
def isContextFunctionType(tp: Type)(implicit ctx: Context): Boolean =
12701269
asContextFunctionType(tp).exists
12711270

1271+
/** An extractor for context function types `As ?=> B`, possibly with
1272+
* dependent refinements. Optionally returns a triple consisting of the argument
1273+
* types `As`, the result type `B` and a whether the type is an erased context function.
1274+
*/
1275+
object ContextFunctionType:
1276+
def unapply(tp: Type)(using ctx: Context): Option[(List[Type], Type, Boolean)] =
1277+
if ctx.erasedTypes then unapply(tp)(using ctx.withPhase(ctx.erasurePhase))
1278+
else
1279+
val tp1 = tp.dealias
1280+
if isContextFunctionClass(tp1.typeSymbol) then
1281+
val args = asContextFunctionType(tp).dropDependentRefinement.argInfos
1282+
Some((args.init, args.last, tp1.typeSymbol.name.isErasedFunction))
1283+
else None
1284+
12721285
def isErasedFunctionType(tp: Type)(implicit ctx: Context): Boolean =
12731286
isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction
12741287

compiler/src/dotty/tools/dotc/core/NameKinds.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,6 @@ object NameKinds {
355355

356356
val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
357357
val CacheName = new SuffixNameKind(CACHE, "$_cache")
358-
val DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true }
359358
val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") {
360359
override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString
361360
}

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ package core
55
import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._
66
import Flags.JavaDefined
77
import Uniques.unique
8-
import dotc.transform.ExplicitOuter._
9-
import dotc.transform.ValueClasses._
8+
import transform.ExplicitOuter._
9+
import transform.ValueClasses._
1010
import transform.TypeUtils._
11+
import transform.ContextFunctionResults._
1112
import Decorators._
1213
import Definitions.MaxImplementedFunctionArity
1314
import scala.annotation.tailrec
@@ -526,21 +527,25 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
526527
* `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them
527528
* to the underlying type.
528529
*/
529-
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match {
530-
case ExprType(rt) =>
531-
if (sym.is(Param)) apply(tp)
532-
// Note that params with ExprTypes are eliminated by ElimByName,
533-
// but potentially re-introduced by ResolveSuper, when we add
534-
// forwarders to mixin methods.
535-
// See doc comment for ElimByName for speculation how we could improve this.
536-
else MethodType(Nil, Nil, eraseResult(sym.info.finalResultType.underlyingIfRepeated(isJava)))
537-
case tp: PolyType =>
538-
eraseResult(tp.resultType) match {
539-
case rt: MethodType => rt
540-
case rt => MethodType(Nil, Nil, rt)
541-
}
542-
case tp => this(tp)
543-
}
530+
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
531+
val tp1 = tp match
532+
case tp: MethodicType => integrateContextResults(tp, contextResultCount(sym))
533+
case _ => tp
534+
tp1 match
535+
case ExprType(rt) =>
536+
if sym.is(Param) then apply(tp1)
537+
// Note that params with ExprTypes are eliminated by ElimByName,
538+
// but potentially re-introduced by ResolveSuper, when we add
539+
// forwarders to mixin methods.
540+
// See doc comment for ElimByName for speculation how we could improve this.
541+
else
542+
MethodType(Nil, Nil,
543+
eraseResult(sym.info.finalResultType.underlyingIfRepeated(isJava)))
544+
case tp1: PolyType =>
545+
eraseResult(tp1.resultType) match
546+
case rt: MethodType => rt
547+
case rt => MethodType(Nil, Nil, rt)
548+
case tp1 => this(tp1)
544549

545550
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
546551
val cls = tref.symbol.asClass

compiler/src/dotty/tools/dotc/transform/Bridges.scala

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Symbols._, Types._, Contexts._, Decorators._, Flags._, Scopes._
77
import DenotTransformers._
88
import ast.untpd
99
import collection.{mutable, immutable}
10-
import ShortcutImplicits._
1110
import util.Spans.Span
1211
import util.SourcePosition
1312

@@ -29,9 +28,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
2928
override def parents = Array(root.superClass)
3029

3130
override def exclude(sym: Symbol) =
32-
!sym.isOneOf(MethodOrModule) ||
33-
isImplicitShortcut(sym) ||
34-
super.exclude(sym)
31+
!sym.isOneOf(MethodOrModule) || super.exclude(sym)
3532
}
3633

3734
//val site = root.thisType
@@ -102,9 +99,10 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
10299
}
103100

104101
def bridgeRhs(argss: List[List[Tree]]) = {
102+
assert(argss.tail.isEmpty)
105103
val ref = This(root).select(member)
106104
if (member.info.isParameterless) ref // can happen if `member` is a module
107-
else ref.appliedToArgss(argss)
105+
else Erasure.partialApply(ref, argss.head)
108106
}
109107

110108
bridges += DefDef(bridge, bridgeRhs(_).withSpan(bridge.span))
@@ -113,24 +111,13 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
113111
/** Add all necessary bridges to template statements `stats`, and remove at the same
114112
* time deferred methods in `stats` that are replaced by a bridge with the same signature.
115113
*/
116-
def add(stats: List[untpd.Tree]): List[untpd.Tree] = {
114+
def add(stats: List[untpd.Tree]): List[untpd.Tree] =
117115
val opc = new BridgesCursor()(preErasureCtx)
118116
val ectx = ctx.withPhase(thisPhase)
119-
while (opc.hasNext) {
120-
if (!opc.overriding.is(Deferred)) {
117+
while opc.hasNext do
118+
if !opc.overriding.is(Deferred) then
121119
addBridgeIfNeeded(opc.overriding, opc.overridden)
122-
123-
if (needsImplicitShortcut(opc.overriding)(ectx) && needsImplicitShortcut(opc.overridden)(ectx))
124-
// implicit shortcuts do not show up in the Bridges cursor, since they
125-
// are created only when referenced. Therefore we need to generate a bridge
126-
// for them specifically, if one is needed for the original methods.
127-
addBridgeIfNeeded(
128-
shortcutMethod(opc.overriding, thisPhase)(ectx),
129-
shortcutMethod(opc.overridden, thisPhase)(ectx))
130-
}
131120
opc.next()
132-
}
133-
if (bridges.isEmpty) stats
121+
if bridges.isEmpty then stats
134122
else stats.filterNot(stat => toBeRemoved contains stat.symbol) ::: bridges.toList
135-
}
136123
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import core._
6+
import Contexts._, Symbols._, Types._, Annotations._, Constants._
7+
import StdNames.nme
8+
import ast.untpd
9+
import ast.tpd._
10+
import config.Config
11+
12+
object ContextFunctionResults:
13+
14+
/** Annotate methods that have context function result types directly matched by context
15+
* closures on their right-hand side. Parameters to such closures will be integrated
16+
* as additional method parameters in erasure.
17+
*/
18+
def annotateContextResults(mdef: DefDef)(using Context): Unit =
19+
def contextResultCount(rhs: Tree, tp: Type): Int = tp match
20+
case defn.ContextFunctionType(_, resTpe, _) =>
21+
rhs match
22+
case closureDef(meth) => 1 + contextResultCount(meth.rhs, resTpe)
23+
case _ => 0
24+
case _ => 0
25+
26+
val meth = mdef.symbol
27+
28+
// Disable context result annotations for anonymous functions
29+
// and for implementations of PolyFunction
30+
def disabled =
31+
meth.isAnonymousFunction
32+
|| meth.name == nme.apply
33+
&& meth.owner.isAnonymousClass
34+
&& meth.owner.info.parents.exists(_.isRef(defn.PolyFunctionClass))
35+
36+
val count = contextResultCount(mdef.rhs, mdef.tpt.tpe)
37+
38+
if Config.flattenContextFunctionResults && count != 0 && !disabled then
39+
val countAnnot = Annotation(defn.ContextResultCountAnnot, Literal(Constant(count)))
40+
mdef.symbol.addAnnotation(countAnnot)
41+
end annotateContextResults
42+
43+
/** The argument of a ContextResultCount annotation, or 0 if none exists.
44+
* See PostTyper#annotateContextResults.
45+
*/
46+
def contextResultCount(sym: Symbol)(using Context): Int =
47+
sym.getAnnotation(defn.ContextResultCountAnnot) match
48+
case Some(annot) =>
49+
val ast.Trees.Literal(Constant(crCount: Int)) :: Nil: @unchecked = annot.arguments
50+
crCount
51+
case none => 0
52+
53+
/** Turn the first `crCount` context function types in the result type of `tp`
54+
* into the curried method types.
55+
*/
56+
def integrateContextResults(tp: Type, crCount: Int)(using Context): Type =
57+
if crCount == 0 then tp
58+
else tp match
59+
case ExprType(rt) =>
60+
integrateContextResults(rt, crCount)
61+
case tp: MethodOrPoly =>
62+
tp.derivedLambdaType(resType = integrateContextResults(tp.resType, crCount))
63+
case defn.ContextFunctionType(argTypes, resType, isErased) =>
64+
val methodType: MethodTypeCompanion =
65+
if isErased then ErasedMethodType else MethodType
66+
methodType(argTypes, integrateContextResults(resType, crCount - 1))
67+
68+
/** The total number of parameters of method `sym`, not counting
69+
* erased parameters, but including context result parameters.
70+
*/
71+
def totalParamCount(sym: Symbol)(using Context): Int =
72+
73+
def contextParamCount(tp: Type, crCount: Int): Int =
74+
if crCount == 0 then 0
75+
else
76+
val defn.ContextFunctionType(params, resTpe, isErased): @unchecked = tp
77+
val rest = contextParamCount(resTpe, crCount - 1)
78+
if isErased then rest else params.length + rest
79+
80+
def normalParamCount(tp: Type): Int = tp.widenExpr.stripPoly match
81+
case mt @ MethodType(pnames) =>
82+
val rest = normalParamCount(mt.resType)
83+
if mt.isErasedMethod then rest else pnames.length + rest
84+
case _ => contextParamCount(tp, contextResultCount(sym))
85+
86+
normalParamCount(sym.info)
87+
end totalParamCount
88+
89+
/** The rightmost context function type in the result type of `meth`
90+
* that represents `paramCount` curried, non-erased parameters that
91+
* are included in the `contextResultCount` of `meth`.
92+
* Example:
93+
*
94+
* Say we have `def m(x: A): B ?=> (C1, C2, C3) ?=> D ?=> E ?=> F`,
95+
* paramCount == 4, and the contextResultCount of `m` is 3.
96+
* Then we return the type `(C1, C2, C3) ?=> D ?=> E ?=> F`, since this
97+
* type covers the 4 rightmost parameters C1, C2, C3 and D before the
98+
* contextResultCount runs out at E ?=> F.
99+
* Erased parameters are ignored; they contribute nothing to the
100+
* parameter count.
101+
*/
102+
def contextFunctionResultTypeCovering(meth: Symbol, paramCount: Int)(using ctx: Context) =
103+
given Context = ctx.withPhase(ctx.erasurePhase)
104+
105+
// Recursive instances return pairs of context types and the
106+
// # of parameters they represent.
107+
def missingCR(tp: Type, crCount: Int): (Type, Int) =
108+
if crCount == 0 then (tp, 0)
109+
else
110+
val defn.ContextFunctionType(formals, resTpe, isErased): @unchecked = tp
111+
val result @ (rt, nparams) = missingCR(resTpe, crCount - 1)
112+
assert(nparams <= paramCount)
113+
if nparams == paramCount || isErased then result
114+
else (tp, nparams + formals.length)
115+
missingCR(meth.info.finalResultType, contextResultCount(meth))._1
116+
end contextFunctionResultTypeCovering
117+
118+
/** Should selection `tree` be eliminated since it refers to an `apply`
119+
* node of a context function type whose parameters will end up being
120+
* integrated in the preceding method?
121+
* @param `n` the select nodes seen in previous recursive iterations of this method
122+
*/
123+
def integrateSelect(tree: untpd.Tree, n: Int = 0)(using ctx: Context): Boolean =
124+
if ctx.erasedTypes then
125+
integrateSelect(tree, n)(using ctx.withPhase(ctx.erasurePhase))
126+
else tree match
127+
case Select(qual, name) =>
128+
if name == nme.apply && defn.isContextFunctionClass(tree.symbol.maybeOwner) then
129+
integrateSelect(qual, n + 1)
130+
else
131+
n > 0 && contextResultCount(tree.symbol) >= n
132+
case Ident(name) =>
133+
n > 0 && contextResultCount(tree.symbol) >= n
134+
case Apply(fn, args) =>
135+
integrateSelect(fn, n)
136+
case TypeApply(fn, _) =>
137+
integrateSelect(fn, n)
138+
case Block(_, expr) =>
139+
integrateSelect(expr, n)
140+
case Inlined(_, _, expr) =>
141+
integrateSelect(expr, n)
142+
case _ =>
143+
false
144+
145+
end ContextFunctionResults

0 commit comments

Comments
 (0)