Skip to content

Commit 1986c03

Browse files
committed
Create only one inline accessor per accessed member
Revise scheme how inline accessors are created so that exactly one inline accessor is created for each member that needs one.
1 parent 9cde3dd commit 1986c03

File tree

7 files changed

+90
-161
lines changed

7 files changed

+90
-161
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ object NameKinds {
275275
}
276276

277277
/** Other unique names */
278-
val InlineAccessorName = new UniqueNameKind("$_inlineAccessor_$")
279278
val TempResultName = new UniqueNameKind("ev$")
280279
val EvidenceParamName = new UniqueNameKind("evidence$")
281280
val DepParamName = new UniqueNameKind("(param)")
@@ -356,6 +355,9 @@ object NameKinds {
356355
val InitializerName = new PrefixNameKind(INITIALIZER, "initial$")
357356
val ProtectedAccessorName = new PrefixNameKind(PROTECTEDACCESSOR, "protected$")
358357
val ProtectedSetterName = new PrefixNameKind(PROTECTEDSETTER, "protected$set") // dubious encoding, kept for Scala2 compatibility
358+
val InlineGetterName = new PrefixNameKind(INLINEGETTER, "inline_get$")
359+
val InlineSetterName = new PrefixNameKind(INLINESETTER, "inline_set$")
360+
359361
val AvoidClashName = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
360362
val DirectMethodName = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true }
361363
val FieldName = new SuffixNameKind(FIELD, "$$local") {

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ object NameTags extends TastyFormat.NameTags {
1515
// outer accessor that will be filled in by ExplicitOuter.
1616
// <num> indicates the number of hops needed to select the outer field.
1717

18-
final val INITIALIZER = 24 // A mixin initializer method
18+
final val INITIALIZER = 26 // A mixin initializer method
1919

20-
final val AVOIDCLASH = 25 // Adds a suffix to avoid a name clash;
20+
final val AVOIDCLASH = 27 // Adds a suffix to avoid a name clash;
2121
// Used in FirstTransform for synthesized companion objects of classes
2222
// if they would clash with another value.
2323

24-
final val DIRECT = 26 // Used by ShortCutImplicits for the name of methods that
24+
final val DIRECT = 28 // Used by ShortCutImplicits for the name of methods that
2525
// implement implicit function result types directly.
2626

27-
final val FIELD = 27 // Used by Memoize to tag the name of a class member field.
27+
final val FIELD = 29 // Used by Memoize to tag the name of a class member field.
2828

29-
final val EXTMETH = 28 // Used by ExtensionMethods for the name of an extension method
29+
final val EXTMETH = 30 // Used by ExtensionMethods for the name of an extension method
3030
// implementing a value class method.
3131

32-
final val ADAPTEDCLOSURE = 29 // Used in Erasure to adapt closures over primitive types.
32+
final val ADAPTEDCLOSURE = 31 // Used in Erasure to adapt closures over primitive types.
3333

34-
final val IMPLMETH = 30 // Used to define methods in implementation classes
34+
final val IMPLMETH = 32 // Used to define methods in implementation classes
3535
// (can probably be removed).
3636

3737
def nameTagToString(tag: Int): String = tag match {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ object TastyFormat {
227227

228228
final val header = Array(0x5C, 0xA1, 0xAB, 0x1F)
229229
val MajorVersion = 7
230-
val MinorVersion = 0
230+
val MinorVersion = 1
231231

232232
/** Tags used to serialize names */
233233
class NameTags {
@@ -260,6 +260,10 @@ object TastyFormat {
260260

261261
final val OBJECTCLASS = 23 // The name of an object class (or: module class) `<name>$`.
262262

263+
final val INLINEGETTER = 24 // The name of an inline getter `inline_get$name`
264+
265+
final val INLINESETTER = 25 // The name of an inline setter `inline_set$name`
266+
263267
final val SIGNED = 63 // A pair of a name and a signature, used to idenitfy
264268
// possibly overloaded methods.
265269
}

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

Lines changed: 57 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import StdNames.nme
1515
import Contexts.Context
1616
import Names.{Name, TermName, EmptyTermName}
1717
import NameOps._
18-
import NameKinds.InlineAccessorName
18+
import NameKinds.{ClassifiedNameKind, InlineGetterName, InlineSetterName}
1919
import ProtoTypes.selectionProto
2020
import SymDenotations.SymDenotation
2121
import Annotations._
@@ -47,11 +47,6 @@ object Inliner {
4747
* from inlined code. Accessors are collected in the `accessors` buffer.
4848
*/
4949
object addAccessors extends TreeMap {
50-
val accessors = new mutable.ListBuffer[MemberDef]
51-
52-
type AccessorMap = mutable.HashMap[Symbol, DefDef]
53-
private val getters = new AccessorMap
54-
private val setters = new AccessorMap
5550

5651
/** A definition needs an accessor if it is private, protected, or qualified private
5752
* and it is not part of the tree that gets inlined. The latter test is implemented
@@ -62,140 +57,64 @@ object Inliner {
6257
(sym.is(AccessFlags) || sym.privateWithin.exists) &&
6358
!sym.owner.isContainedIn(inlineMethod)
6459

65-
/** The name of the next accessor to be generated */
66-
def accessorName(implicit ctx: Context) = InlineAccessorName.fresh(inlineMethod.name.asTermName)
67-
6860
/** A fresh accessor symbol.
6961
*
7062
* @param tree The tree representing the original access to the non-public member
71-
* @param accessorInfo The type of the accessor
7263
*/
73-
def accessorSymbol(tree: Tree, accessorInfo: Type)(implicit ctx: Context): Symbol =
74-
ctx.newSymbol(
75-
owner = inlineMethod.owner,
76-
name = if (tree.isTerm) accessorName.toTermName else accessorName.toTypeName,
77-
flags = if (tree.isTerm) Synthetic | Method else Synthetic,
78-
info = accessorInfo,
79-
coord = tree.pos).entered
80-
81-
/** Add an accessor to a non-public method and replace the original access with a
82-
* call to the accessor.
64+
def newAccessorSymbol(accessed: TermSymbol, name: TermName, info: Type)(implicit ctx: Context): TermSymbol =
65+
ctx.newSymbol(accessed.owner, name, Synthetic | Method, info, coord = accessed.pos).entered
66+
67+
/** Create an inline accessor unless one exists already, and replace the original
68+
* access with a reference to the accessor.
8369
*
84-
* @param tree The original access to the non-public symbol
85-
* @param refPart The part that refers to the method or field of the original access
86-
* @param targs All type arguments passed in the access, if any
87-
* @param argss All value arguments passed in the access, if any
88-
* @param accessedType The type of the accessed method or field, as seen from the access site.
89-
* @param rhs A function that builds the right-hand side of the accessor,
90-
* given a reference to the accessed symbol and any type and
91-
* value arguments the need to be integrated.
92-
* @param seen An map of already generated accessor methods of this kind (getter or setter)
93-
* @return The call to the accessor method that replaces the original access.
70+
* @param reference The original reference to the non-public symbol
71+
* @param onLHS The reference is on the left-hand side of an assignment
9472
*/
95-
def addAccessor(tree: Tree, refPart: Tree, targs: List[Tree], argss: List[List[Tree]],
96-
accessedType: Type, rhs: (Tree, List[Type], List[List[Tree]]) => Tree,
97-
seen: AccessorMap)(implicit ctx: Context): Tree = {
98-
val qual = qualifier(refPart)
99-
100-
def refIsLocal = qual match {
101-
case qual: This => qual.symbol == refPart.symbol.owner
102-
case _ => false
103-
}
73+
def useAccessor(reference: RefTree, onLHS: Boolean)(implicit ctx: Context): Tree = {
10474

105-
def accessorDef(accessorType: Type, accessorDefFn: TermSymbol => DefDef): DefDef =
106-
seen.getOrElseUpdate(refPart.symbol, {
107-
val acc = accessorSymbol(tree, accessorType).asTerm
108-
val accessorDef = accessorDefFn(acc)
109-
accessors += accessorDef
110-
inlining.println(i"added inline accessor: $accessorDef")
111-
accessorDef
112-
})
113-
114-
if (refPart.symbol.isStatic || refIsLocal) {
115-
// Easy case: Reference to a static symbol or a symbol referenced via `this.`
116-
val accDef = accessorDef(
117-
accessedType.ensureMethodic,
118-
polyDefDef(_, tps => argss => rhs(refPart, tps, argss).withPos(tree.pos.focus)))
75+
def nameKind = if (onLHS) InlineSetterName else InlineGetterName
76+
val accessed = reference.symbol.asTerm
11977

120-
ref(accDef.symbol).appliedToTypeTrees(targs).appliedToArgss(argss).withPos(tree.pos)
78+
def refersToAccessed(sym: Symbol) = sym.getAnnotation(defn.AccessedAnnot) match {
79+
case Some(Annotation.Accessed(sym)) => sym `eq` accessed
80+
case _ => false
12181
}
122-
else {
123-
// Hard case: Reference needs to go via a dynamic prefix
124-
inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))")
125-
126-
// Need to dealias in order to catch all possible references to abstracted over types in
127-
// substitutions
128-
val dealiasMap = new TypeMap {
129-
def apply(t: Type) = mapOver(t.dealias)
130-
}
13182

132-
val qualType = dealiasMap(qual.tpe.widen)
83+
val accessorInfo =
84+
if (onLHS) MethodType(accessed.info :: Nil, defn.UnitType)
85+
else accessed.info.ensureMethodic
86+
val accessorName = nameKind(accessed.name)
87+
val accessorSymbol =
88+
accessed.owner.info.decl(accessorName).suchThat(refersToAccessed).symbol
89+
.orElse {
90+
val acc = newAccessorSymbol(accessed, accessorName, accessorInfo)
91+
acc.addAnnotation(Annotation.Accessed(accessed))
92+
acc
93+
}
13394

134-
// Add qualifier type as leading method argument to argument `tp`
135-
def addQualType(tp: Type): Type = tp match {
136-
case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType))
137-
case tp: ExprType => addQualType(tp.resultType)
138-
case tp => MethodType(qualType :: Nil, tp)
95+
{ reference match {
96+
case Select(qual, _) => qual.select(accessorSymbol)
97+
case Ident(name) => ref(accessorSymbol)
13998
}
140-
141-
// The types that are local to the inlined method, and that therefore have
142-
// to be abstracted out in the accessor, which is external to the inlined method
143-
val localRefs = qualType.namedPartsWith(ref =>
144-
ref.isType && ref.symbol.isContainedIn(inlineMethod)).toList
145-
146-
// Abstract accessed type over local refs
147-
def abstractQualType(mtpe: Type): Type =
148-
if (localRefs.isEmpty) mtpe
149-
else PolyType.fromParams(localRefs.map(_.symbol.asType), mtpe)
150-
.asInstanceOf[PolyType].flatten
151-
152-
val accDef = accessorDef(
153-
abstractQualType(addQualType(dealiasMap(accessedType))),
154-
polyDefDef(_, tps => argss =>
155-
rhs(argss.head.head.select(refPart.symbol), tps.drop(localRefs.length), argss.tail)
156-
.withPos(tree.pos.focus)))
157-
158-
ref(accDef.symbol)
159-
.appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs)
160-
.appliedToArgss((qual :: Nil) :: argss)
161-
.withPos(tree.pos)
162-
}
99+
}.withPos(reference.pos)
163100
}
164101

102+
// TODO: Also handle references to non-public types.
103+
// This is quite tricky, as such types can appear anywhere, including as parts
104+
// of types of other things. For the moment we do nothing and complain
105+
// at the implicit expansion site if there's a reference to an inaccessible type.
165106
override def transform(tree: Tree)(implicit ctx: Context): Tree = super.transform {
166107
tree match {
167-
case _: Apply | _: TypeApply | _: RefTree if needsAccessor(tree.symbol) =>
168-
if (tree.isTerm) {
169-
val (methPart, targs, argss) = decomposeCall(tree)
170-
if (methPart.symbol.isConstructor && needsAccessor(methPart.symbol)) {
171-
ctx.error("Cannot use private constructors in inline methods", tree.pos)
172-
tree // TODO: create a proper accessor for the private constructor
173-
}
174-
else
175-
addAccessor(tree, methPart, targs, argss,
176-
accessedType = methPart.tpe.widen,
177-
rhs = (qual, tps, argss) => qual.appliedToTypes(tps).appliedToArgss(argss),
178-
seen = getters)
179-
}
180-
else {
181-
// TODO: Handle references to non-public types.
182-
// This is quite tricky, as such types can appear anywhere, including as parts
183-
// of types of other things. For the moment we do nothing and complain
184-
// at the implicit expansion site if there's a reference to an inaccessible type.
185-
// Draft code (incomplete):
186-
//
187-
// val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType
188-
// myAccessors += TypeDef(accessor).withPos(tree.pos.focus)
189-
// ref(accessor).withPos(tree.pos)
190-
//
191-
tree
108+
case tree: RefTree if needsAccessor(tree.symbol) =>
109+
if (tree.symbol.isConstructor) {
110+
ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos)
111+
tree // TODO: create a proper accessor for the private constructor
192112
}
113+
else useAccessor(tree, onLHS = false)
193114
case Assign(lhs: RefTree, rhs) if needsAccessor(lhs.symbol) =>
194-
addAccessor(tree, lhs, Nil, (rhs :: Nil) :: Nil,
195-
accessedType = MethodType(rhs.tpe.widen :: Nil, defn.UnitType),
196-
rhs = (lhs, tps, argss) => lhs.becomes(argss.head.head),
197-
seen = setters)
198-
case _ => tree
115+
cpy.Apply(tree)(useAccessor(lhs, onLHS = true), List(rhs))
116+
case _ =>
117+
tree
199118
}
200119
}
201120
}
@@ -204,12 +123,23 @@ object Inliner {
204123
// Inline methods in local scopes can only be called in the scope they are defined,
205124
// so no accessors are needed for them.
206125
tree
207-
else {
208-
val tree1 = addAccessors.transform(tree)
209-
flatTree(tree1 :: addAccessors.accessors.toList)
210-
}
126+
else addAccessors.transform(tree)
211127
}
212128

129+
/** The inline accessor definitions that need to be added to class `cls` */
130+
def accessorDefs(cls: Symbol)(implicit ctx: Context): List[DefDef] =
131+
for (accessor <- cls.info.decls.filter(sym => sym.name.is(InlineGetterName) || sym.name.is(InlineSetterName)))
132+
yield polyDefDef(accessor.asTerm, tps => argss => {
133+
val Annotation.Accessed(accessed) = accessor.getAnnotation(defn.AccessedAnnot).get
134+
val rhs =
135+
if (accessor.name.is(InlineSetterName) &&
136+
argss.nonEmpty && argss.head.nonEmpty) // defensive conditions
137+
ref(accessed).becomes(argss.head.head)
138+
else
139+
ref(accessed).appliedToTypes(tps).appliedToArgss(argss)
140+
rhs.withPos(accessed.pos)
141+
})
142+
213143
/** Register inline info for given inline method `sym`.
214144
*
215145
* @param sym The symbol denotatioon of the inline method for which info is registered
@@ -241,27 +171,11 @@ object Inliner {
241171
def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean =
242172
sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot)
243173

244-
private def bodyAndAccessors(sym: SymDenotation)(implicit ctx: Context): (Tree, List[MemberDef]) =
245-
sym.unforcedAnnotation(defn.BodyAnnot).get.tree match {
246-
case Thicket(body :: accessors) => (body, accessors.asInstanceOf[List[MemberDef]])
247-
case body => (body, Nil)
248-
}
249-
250174
/** The body to inline for method `sym`.
251175
* @pre hasBodyToInline(sym)
252176
*/
253177
def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree =
254-
bodyAndAccessors(sym)._1
255-
256-
/** The accessors to non-public members needed by the inlinable body of `sym`.
257-
* These accessors are dropped as a side effect of calling this method.
258-
* @pre hasBodyToInline(sym)
259-
*/
260-
def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = {
261-
val (body, accessors) = bodyAndAccessors(sym)
262-
if (accessors.nonEmpty) sym.updateAnnotation(ConcreteBodyAnnotation(body))
263-
accessors
264-
}
178+
sym.unforcedAnnotation(defn.BodyAnnot).get.tree
265179

266180
/** Try to inline a call to a `@inline` method. Fail with error if the maximal
267181
* inline depth is exceeded.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class ReTyper extends Typer with ReChecking {
122122
override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult =
123123
Implicits.NoMatchingImplicitsFailure
124124
override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = ()
125-
override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil
126-
125+
override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree = mdef
126+
override protected def addInlineAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] = body
127127
override protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(implicit ctx: Context): Unit = ()
128128
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,8 @@ class Typer extends Namer
15231523
cdef.withType(UnspecifiedErrorType)
15241524
} else {
15251525
val dummy = localDummy(cls, impl)
1526-
val body1 = typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol))
1526+
val body1 = addInlineAccessorDefs(cls,
1527+
typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol)))
15271528
if (!ctx.isAfterTyper)
15281529
cls.setNoInitsFlags((NoInitsInterface /: body1) ((fs, stat) => fs & defKind(stat)))
15291530

@@ -1561,6 +1562,11 @@ class Typer extends Namer
15611562
}
15621563
}
15631564

1565+
protected def addInlineAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] = {
1566+
val accDefs = Inliner.accessorDefs(cls)
1567+
if (accDefs.isEmpty) body else body ++ accDefs
1568+
}
1569+
15641570
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
15651571
* If that's not already the case, add one. The added class type CT is determined as follows.
15661572
* First, let C be the unique class such that
@@ -1882,7 +1888,7 @@ class Typer extends Namer
18821888
case none =>
18831889
typed(mdef) match {
18841890
case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) =>
1885-
buf ++= inlineExpansion(mdef1)
1891+
buf += inlineExpansion(mdef1)
18861892
case mdef1 =>
18871893
import untpd.modsDeco
18881894
mdef match {
@@ -1913,12 +1919,11 @@ class Typer extends Namer
19131919
}
19141920

19151921
/** Given an inline method `mdef`, the method rewritten so that its body
1916-
* uses accessors to access non-public members, followed by the accessor definitions.
1922+
* uses accessors to access non-public members.
19171923
* Overwritten in Retyper to return `mdef` unchanged.
19181924
*/
1919-
protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] =
1920-
tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol)) ::
1921-
Inliner.removeInlineAccessors(mdef.symbol)
1925+
protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): Tree =
1926+
tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol))
19221927

19231928
def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree =
19241929
typed(tree, pt)(ctx retractMode Mode.PatternOrTypeBits)

0 commit comments

Comments
 (0)