Skip to content

Commit f548a18

Browse files
authored
Merge pull request #8386 from dotty-staging/change-cf-encoding
Replace ShortcutImplicits
2 parents 70a49f9 + cbd21a0 commit f548a18

18 files changed

+476
-376
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
@@ -169,6 +169,12 @@ object Config {
169169
/** If set, prints a trace of all symbol completions */
170170
final val showCompletions = false
171171

172+
/** If set, method results that are context functions are flattened by adding
173+
* the parameters of the context function results to the methods themselves.
174+
* This is an optimization that reduces closure allocations.
175+
*/
176+
final val flattenContextFunctionResults = true
177+
172178
/** If set, enables tracing */
173179
final val tracingEnabled = false
174180

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,8 +783,7 @@ class Definitions {
783783
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.AnnotationDefault")
784784
@tu lazy val BodyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Body")
785785
@tu lazy val ChildAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Child")
786-
@tu lazy val CovariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.CovariantBetween")
787-
@tu lazy val ContravariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContravariantBetween")
786+
@tu lazy val ContextResultCountAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContextResultCount")
788787
@tu lazy val DeprecatedAnnot: ClassSymbol = ctx.requiredClass("scala.deprecated")
789788
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitAmbiguous")
790789
@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
@@ -354,7 +354,6 @@ object NameKinds {
354354
val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$")
355355

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

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

Lines changed: 29 additions & 24 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
@@ -129,23 +130,23 @@ object TypeErasure {
129130
erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK))
130131

131132
/** The current context with a phase no later than erasure */
132-
private def erasureCtx(implicit ctx: Context) =
133+
def preErasureCtx(implicit ctx: Context) =
133134
if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase) else ctx
134135

135136
/** The standard erasure of a Scala type. Value classes are erased as normal classes.
136137
*
137138
* @param tp The type to erase.
138139
*/
139140
def erasure(tp: Type)(implicit ctx: Context): Type =
140-
erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(erasureCtx)
141+
erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(preErasureCtx)
141142

142143
/** The value class erasure of a Scala type, where value classes are semi-erased to
143144
* ErasedValueType (they will be fully erased in [[ElimErasedValueType]]).
144145
*
145146
* @param tp The type to erase.
146147
*/
147148
def valueErasure(tp: Type)(implicit ctx: Context): Type =
148-
erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(erasureCtx)
149+
erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(preErasureCtx)
149150

150151
/** Like value class erasure, but value classes erase to their underlying type erasure */
151152
def fullErasure(tp: Type)(implicit ctx: Context): Type =
@@ -156,7 +157,7 @@ object TypeErasure {
156157
def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = {
157158
val normTp = tp.underlyingIfRepeated(isJava)
158159
val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true)
159-
erase.sigName(normTp)(erasureCtx)
160+
erase.sigName(normTp)(preErasureCtx)
160161
}
161162

162163
/** The erasure of a top-level reference. Differs from normal erasure in that
@@ -195,9 +196,9 @@ object TypeErasure {
195196

196197
if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
197198
else if (sym.isAbstractType) TypeAlias(WildcardType)
198-
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx))
199-
else if (sym.is(Label)) erase.eraseResult(sym.info)(erasureCtx)
200-
else erase.eraseInfo(tp, sym)(erasureCtx) match {
199+
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(preErasureCtx))
200+
else if (sym.is(Label)) erase.eraseResult(sym.info)(preErasureCtx)
201+
else erase.eraseInfo(tp, sym)(preErasureCtx) match {
201202
case einfo: MethodType =>
202203
if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass))
203204
MethodType(Nil, defn.BoxedUnitClass.typeRef)
@@ -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)