Skip to content

Commit 28c49f7

Browse files
committed
Switch to implementation scheme described in PR
The previous scheme was more complicated and looks more fragile, in particular if complex staging operations are applied to a retained inline method. In that case we have to rely on the fact that the rhs of a retained inline method is in fact its inlineable body, and not the retained body.
1 parent 9247e72 commit 28c49f7

31 files changed

+294
-114
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ object NameKinds {
353353
val ProtectedAccessorName: PrefixNameKind = new PrefixNameKind(PROTECTEDACCESSOR, "protected$")
354354
val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$")
355355

356+
val BodyRetainerName: SuffixNameKind = new SuffixNameKind(BODYRETAINER, "$retainedBody")
356357
val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") {
357358
override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString
358359
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,13 +938,16 @@ object SymDenotations {
938938
def isInlineMethod(implicit ctx: Context): Boolean =
939939
isAllOf(InlineMethod, butNot = Accessor)
940940

941+
def isInlineRetained(using Context): Boolean =
942+
!is(Deferred) && allOverriddenSymbols.exists(!_.is(Inline))
943+
941944
/** Is this a Scala 2 macro */
942945
final def isScala2Macro(implicit ctx: Context): Boolean = is(Macro) && symbol.owner.is(Scala2x)
943946

944947
/** An erased value or an inline method.
945948
*/
946949
def isEffectivelyErased(implicit ctx: Context): Boolean =
947-
is(Erased) || isInlineMethod
950+
is(Erased) || isInlineMethod && !isInlineRetained
948951

949952
/** ()T and => T types should be treated as equivalent for this symbol.
950953
* Note: For the moment, we treat Scala-2 compiled symbols as loose matching,

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ class TreeUnpickler(reader: TastyReader,
774774
def readRhs(implicit ctx: Context): LazyTree =
775775
if (nothingButMods(end))
776776
EmptyTree
777-
else if (sym.isInlineMethod)
777+
else if sym.isInlineMethod && !sym.is(Deferred) then
778778
// The body of an inline method is stored in an annotation, so no need to unpickle it again
779779
new Trees.Lazy[Tree] {
780780
def complete(implicit ctx: Context) = typer.Inliner.bodyToInline(sym)

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1699,7 +1699,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
16991699
}
17001700
def Symbol_annots(self: Symbol)(using ctx: Context): List[Term] =
17011701
self.annotations.flatMap {
1702-
case _: core.Annotations.LazyBodyAnnotation => Nil
1702+
case _: core.Annotations.BodyAnnotation => Nil
17031703
case annot => annot.tree :: Nil
17041704
}
17051705

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

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ import core.Types._
1212
import core.Names._
1313
import core.StdNames._
1414
import core.NameOps._
15-
import core.NameKinds.AdaptedClosureName
15+
import core.NameKinds.{AdaptedClosureName, BodyRetainerName}
1616
import core.Decorators._
1717
import core.Constants._
1818
import core.Definitions._
19+
import core.Annotations.BodyAnnotation
1920
import typer.{NoChecking, LiftErased}
2021
import typer.Inliner
2122
import typer.ProtoTypes._
2223
import core.TypeErasure._
2324
import core.Decorators._
2425
import dotty.tools.dotc.ast.{tpd, untpd}
2526
import ast.Trees._
27+
import ast.TreeTypeMap
2628
import dotty.tools.dotc.core.{Constants, Flags}
2729
import ValueClasses._
2830
import TypeUtils._
@@ -78,17 +80,32 @@ class Erasure extends Phase with DenotTransformer {
7880
val oldInfo = ref.info
7981
val newInfo = transformInfo(oldSymbol, oldInfo)
8082
val oldFlags = ref.flags
81-
val newFlags =
83+
var newFlags =
8284
if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner)) oldFlags &~ Flags.Param
8385
else oldFlags &~ Flags.HasDefaultParamsFlags // HasDefaultParamsFlags needs to be dropped because overriding might become overloading
84-
86+
val oldAnnotations = ref.annotations
87+
var newAnnotations = oldAnnotations
88+
if oldSymbol.isInlineMethod && oldSymbol.isInlineRetained then
89+
newFlags = newFlags &~ Flags.Inline
90+
newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation])
8591
// TODO: define derivedSymDenotation?
86-
if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldName eq newName) && (oldInfo eq newInfo) && (oldFlags == newFlags))
92+
if (oldSymbol eq newSymbol)
93+
&& (oldOwner eq newOwner)
94+
&& (oldName eq newName)
95+
&& (oldInfo eq newInfo)
96+
&& (oldFlags == newFlags)
97+
&& (oldAnnotations eq newAnnotations)
98+
then
8799
ref
88-
else {
100+
else
89101
assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}")
90-
ref.copySymDenotation(symbol = newSymbol, owner = newOwner, name = newName, initFlags = newFlags, info = newInfo)
91-
}
102+
ref.copySymDenotation(
103+
symbol = newSymbol,
104+
owner = newOwner,
105+
name = newName,
106+
initFlags = newFlags,
107+
info = newInfo,
108+
annotations = newAnnotations)
92109
}
93110
case ref: JointRefDenotation =>
94111
new UniqueRefDenotation(
@@ -813,7 +830,8 @@ object Erasure {
813830
* parameter of type `[]Object`.
814831
*/
815832
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree =
816-
if (sym.isEffectivelyErased) erasedDef(sym)
833+
if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then
834+
erasedDef(sym)
817835
else
818836
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
819837
var vparams = outerParamDefs(sym)
@@ -874,6 +892,35 @@ object Erasure {
874892
outerParamDefs(constr)
875893
else Nil
876894

895+
private def addRetainedInlineBodies(stats: List[untpd.Tree])(using ctx: Context): List[untpd.Tree] =
896+
lazy val retainerDef: Map[Symbol, DefDef] = stats.collect {
897+
case stat: DefDef if stat.symbol.name.is(BodyRetainerName) =>
898+
val retainer = stat.symbol
899+
val origName = retainer.name.asTermName.exclude(BodyRetainerName)
900+
val inlineMeth = ctx.atPhase(ctx.typerPhase) {
901+
retainer.owner.info.decl(origName)
902+
.matchingDenotation(retainer.owner.thisType, stat.symbol.info)
903+
.symbol
904+
}
905+
(inlineMeth, stat)
906+
}.toMap
907+
stats.mapConserve {
908+
case stat: DefDef if stat.symbol.isInlineMethod && stat.symbol.isInlineRetained =>
909+
val rdef = retainerDef(stat.symbol)
910+
def allParams(ddef: DefDef) =
911+
(ddef.tparams ::: ddef.vparamss.flatten).map(_.symbol)
912+
val fromParams = allParams(rdef)
913+
val toParams = allParams(stat)
914+
assert(fromParams.hasSameLengthAs(toParams))
915+
val mapBody = TreeTypeMap(
916+
oldOwners = rdef.symbol :: Nil,
917+
newOwners = stat.symbol :: Nil,
918+
substFrom = fromParams,
919+
substTo = toParams)
920+
cpy.DefDef(stat)(rhs = mapBody.transform(rdef.rhs))
921+
case stat => stat
922+
}
923+
877924
override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = {
878925
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
879926
var implClosure = super.typedClosure(tree, pt).asInstanceOf[Closure]
@@ -888,9 +935,10 @@ object Erasure {
888935
typed(tree.arg, pt)
889936

890937
override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): (List[Tree], Context) = {
938+
val stats0 = addRetainedInlineBodies(stats)(using preErasureCtx)
891939
val stats1 =
892-
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats)
893-
else stats
940+
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats0)
941+
else stats0
894942
val (stats2, finalCtx) = super.typedStats(stats1, exprOwner)
895943
(stats2.filter(!_.isEmpty), finalCtx)
896944
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ class TreeChecker extends Phase with SymTransformer {
8080
val badDeferredAndPrivate =
8181
sym.is(Method) && sym.is(Deferred) && sym.is(Private)
8282
&& !sym.hasAnnotation(defn.NativeAnnot)
83-
&& !sym.is(Erased)
83+
&& !sym.isEffectivelyErased
84+
8485
assert(!badDeferredAndPrivate, i"$sym is both Deferred and Private")
8586

8687
checkCompanion(symd)

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,6 @@ object Checking {
472472
fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym))
473473
checkWithDeferred(Private)
474474
checkWithDeferred(Final)
475-
checkWithDeferred(Inline)
476475
}
477476
if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass)
478477
fail(CannotExtendAnyVal(sym))

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import StdNames._
1515
import transform.SymUtils._
1616
import Contexts.Context
1717
import Names.{Name, TermName}
18-
import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName}
18+
import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName}
1919
import ProtoTypes.selectionProto
2020
import SymDenotations.SymDenotation
2121
import Inferencing.isFullyDefined
@@ -124,6 +124,38 @@ object Inliner {
124124
)
125125
}
126126

127+
/** For a retained inline method add a RetainedBody annotaton that
128+
* records the tree for which code will be generated at runtime. This is
129+
* the inline expansion of a call to the method itself with its
130+
* parameters as arguments. Given an inline method
131+
*
132+
* inline def f[Ts](xs: Us) = body
133+
*
134+
* This sets up the call
135+
*
136+
* f[Ts'](xs')
137+
*
138+
* where the 'ed parameters are copies of the original ones. The call is
139+
* then inline processed in a context which has a clone f' of f as owner.
140+
* The cloning of owner and parameters is necessary since otherwise the
141+
* inliner gets confused in various ways. The inlined body is then
142+
* transformed back by replacing cloned versions of parameters with original
143+
* and replacing the cloned owner f' with f.
144+
*/
145+
def bodyRetainer(mdef: DefDef)(using ctx: Context): DefDef =
146+
val meth = mdef.symbol.asTerm
147+
148+
val retainer = meth.copy(
149+
name = BodyRetainerName(meth.name),
150+
flags = meth.flags &~ (Inline | Override) | Private,
151+
coord = mdef.rhs.span.startPos).asTerm
152+
polyDefDef(retainer, targs => prefss =>
153+
inlineCall(
154+
ref(meth).appliedToTypes(targs).appliedToArgss(prefss)
155+
.withSpan(mdef.rhs.span.startPos))(
156+
using ctx.withOwner(retainer)))
157+
.reporting(i"retainer for $meth: $result", inlining)
158+
127159
/** Replace `Inlined` node by a block that contains its bindings and expansion */
128160
def dropInlined(inlined: Inlined)(implicit ctx: Context): Tree =
129161
if (enclosingInlineds.nonEmpty) inlined // Remove in the outer most inlined call

compiler/src/dotty/tools/dotc/typer/ReTyper.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class ReTyper extends Typer with ReChecking {
128128
throw ex
129129
}
130130

131-
override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree = mdef
131+
override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil
132132

133133
override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult =
134134
Implicits.NoMatchingImplicitsFailure

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,7 @@ object RefChecks {
157157
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
158158
* 1.9.1 If M is erased, O is erased. If O is erased, M is erased or inline.
159159
* 1.9.2 If M or O are extension methods, they must both be extension methods.
160-
* 1.10 If M is an inline or Scala-2 macro method, O cannot be deferred unless
161-
* there's also a concrete method that M overrides.
160+
* 1.10 If O is inline, M must be inline
162161
* 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro.
163162
* 2. Check that only abstract classes have deferred members
164163
* 3. Check that concrete classes do not have deferred definitions
@@ -398,9 +397,8 @@ object RefChecks {
398397
overrideError("is an extension method, cannot override a normal method")
399398
else if (other.isAllOf(ExtensionMethod) && !member.isAllOf(ExtensionMethod)) // (1.9.2)
400399
overrideError("is a normal method, cannot override an extension method")
401-
else if ((member.isInlineMethod || member.isScala2Macro) && other.is(Deferred) &&
402-
member.extendedOverriddenSymbols.forall(_.is(Deferred))) // (1.10)
403-
overrideError("is an inline method, must override at least one concrete method")
400+
else if other.isInlineMethod && !member.isInlineMethod then // (1.10)
401+
overrideError("is not inline, cannot override an inline method")
404402
else if (other.isScala2Macro && !member.isScala2Macro) // (1.11)
405403
overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros")
406404
else if (!compatibleTypes(memberTp(self), otherTp(self)) &&

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,8 +2336,9 @@ class Typer extends Namer
23362336
val newCtx = if (ctx.owner.isTerm && adaptCreationContext(mdef)) ctx
23372337
else ctx.withNotNullInfos(initialNotNullInfos)
23382338
typed(mdef)(using newCtx) match {
2339-
case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
2340-
buf += inlineExpansion(mdef1)
2339+
case mdef1: DefDef
2340+
if mdef1.symbol.is(Inline, butNot = Deferred) && !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
2341+
buf ++= inlineExpansion(mdef1)
23412342
// replace body with expansion, because it will be used as inlined body
23422343
// from separately compiled files - the original BodyAnnotation is not kept.
23432344
case mdef1: TypeDef if mdef1.symbol.is(Enum, butNot = Case) =>
@@ -2414,11 +2415,13 @@ class Typer extends Namer
24142415
}
24152416

24162417
/** Given an inline method `mdef`, the method rewritten so that its body
2417-
* uses accessors to access non-public members.
2418+
* uses accessors to access non-public members. Also, if the inline method
2419+
* is retained, add a method to record the retained version of the body.
24182420
* Overwritten in Retyper to return `mdef` unchanged.
24192421
*/
2420-
protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree =
2422+
protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] =
24212423
tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol))
2424+
:: (if mdef.symbol.isInlineRetained then Inliner.bodyRetainer(mdef) :: Nil else Nil)
24222425

24232426
def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree =
24242427
typed(tree, pt)(ctx retractMode Mode.PatternOrTypeBits)
@@ -2936,9 +2939,13 @@ class Typer extends Namer
29362939
!suppressInline) {
29372940
tree.tpe <:< wildApprox(pt)
29382941
val errorCount = ctx.reporter.errorCount
2939-
val inlined = Inliner.inlineCall(tree)
2940-
if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined)
2941-
else inlined
2942+
val meth = methPart(tree).symbol
2943+
if meth.is(Deferred) then
2944+
errorTree(tree, i"Deferred inline ${meth.showLocated} cannot be invoked")
2945+
else
2946+
val inlined = Inliner.inlineCall(tree)
2947+
if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined)
2948+
else inlined
29422949
}
29432950
else if (tree.symbol.isScala2Macro &&
29442951
// raw and s are eliminated by the StringInterpolatorOpt phase

docs/docs/reference/metaprogramming/inline.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,44 @@ funkyAssertEquals(computeActual(), computeExpected(), computeDelta())
142142
// if (actual - expected).abs > computeDelta() then
143143
// throw new AssertionError(s"difference between ${expected} and ${actual} was larger than ${computeDelta()}")
144144
```
145+
### Rules for Overriding
146+
147+
Inline methods can override other methods and can themselves be overridden by other inline methods. The rules are as follows:
148+
149+
1. If an inline method `f` implements or overrides another, non-inline method, the inline method can also be invoked at runtime. For instance, consider the scenario:
150+
```scala
151+
abstract class A {
152+
def f(): Int
153+
def g(): Int = f()
154+
}
155+
class B extends A {
156+
inline def f() = 22
157+
override inline def g() = f() + 11
158+
}
159+
val b = B()
160+
val a: A = b
161+
// inlined invocatons
162+
assert(b.f() == 22)
163+
assert(b.g() == 33)
164+
// dynamic invocations
165+
assert(a.f() == 22)
166+
assert(a.g() == 33)
167+
```
168+
The inlined invocations and the dynamically dispatched invocations give the same results.
169+
170+
2. Inline methods can override or implement normal methods, as the previous example shows. Inline methods can be overridden only by other inline methods.
171+
172+
3. Inline methods can also be abstract. An abstract inline method can be implemented only by other inline methods. It cannot be invoked directly:
173+
```scala
174+
abstract class A {
175+
inline def f(): Int
176+
}
177+
object B extends A {
178+
inline def f(): Int = 22
179+
}
180+
B.f() // OK
181+
val a: A = B; a.f() // error: cannot inline f() in A.
182+
```
145183

146184
### Relationship to @inline
147185

tasty/src/dotty/tools/tasty/TastyFormat.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ Standard-Section: "ASTs" TopLevelStat*
5959
TYPEDEF Length NameRef (type_Term | Template) Modifier* -- modifiers type name (= type | bounds) | moifiers class name template
6060
IMPORT Length qual_Term Selector* -- import qual selectors
6161
ValOrDefDef = VALDEF Length NameRef type_Term rhs_Term? Modifier* -- modifiers val name : type (= rhs)?
62-
DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term?
63-
Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)?
62+
DEFDEF Length NameRef TypeParam* Params* returnType_Term
63+
rhs_Term? Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)?
6464
Selector = IMPORTED name_NameRef -- name, "_" for normal wildcards, "" for given wildcards
6565
RENAMED to_NameRef -- => name
6666
BOUNDED type_Term -- type bound
@@ -279,6 +279,9 @@ object TastyFormat {
279279

280280
final val INLINEACCESSOR = 21 // The name of an inline accessor `inline$name`
281281

282+
final val BODYRETAINER = 22 // The name of a synthetic method that retains the runtime
283+
// body of an inline method
284+
282285
final val OBJECTCLASS = 23 // The name of an object class (or: module class) `<name>$`.
283286

284287
final val SIGNED = 63 // A pair of a name and a signature, used to identify
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package test
2+
import scala.util.FromDigits
3+
import scala.quoted._
4+
5+
object BigFloatFromDigitsImpl:
6+
def apply(digits: Expr[String])(using ctx: QuoteContext): Expr[BigFloat] =
7+
digits match
8+
case Const(ds) =>
9+
try
10+
val BigFloat(m, e) = BigFloat(ds)
11+
'{BigFloat(${Expr(m)}, ${Expr(e)})}
12+
catch case ex: FromDigits.FromDigitsException =>
13+
ctx.error(ex.getMessage)
14+
'{BigFloat(0, 0)}
15+
case digits =>
16+
'{BigFloat($digits)}

0 commit comments

Comments
 (0)