Skip to content

Commit 2b53d67

Browse files
committed
Unify handling of inline accessors and protected accessors
This is a preparation step to enable migrating generation of protected accessors after pickling. The actual migration will be done at a later point. To do this, I refactored much of the logic needed for generating acessors into a new class `AccessProxies`.
1 parent 0610cf2 commit 2b53d67

File tree

5 files changed

+171
-107
lines changed

5 files changed

+171
-107
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import Contexts.Context
6+
import Symbols._
7+
import Flags._
8+
import Names._
9+
import Decorators._
10+
import TypeUtils._
11+
import Annotations.Annotation
12+
import Types._
13+
import NameKinds.ClassifiedNameKind
14+
import ast.Trees._
15+
import util.Property
16+
import util.Positions.Position
17+
18+
abstract class AccessProxies {
19+
import ast.tpd._
20+
21+
def getterName: ClassifiedNameKind
22+
def setterName: ClassifiedNameKind
23+
24+
/** The accessor definitions that need to be added to class `cls`
25+
* As a side-effect, this method removes the `Accessed` annotations from
26+
* the accessor symbols. So a second call of the same method will yield the empty list.
27+
*/
28+
private def accessorDefs(cls: Symbol)(implicit ctx: Context): List[DefDef] =
29+
for {
30+
accessor <- cls.info.decls.filter(sym => sym.name.is(getterName) || sym.name.is(setterName))
31+
Annotation.Accessed(accessed) <- accessor.getAnnotation(defn.AccessedAnnot)
32+
}
33+
yield polyDefDef(accessor.asTerm, tps => argss => {
34+
accessor.removeAnnotation(defn.AccessedAnnot)
35+
val rhs =
36+
if (accessor.name.is(setterName) &&
37+
argss.nonEmpty && argss.head.nonEmpty) // defensive conditions
38+
ref(accessed).becomes(argss.head.head)
39+
else
40+
ref(accessed).appliedToTypes(tps).appliedToArgss(argss)
41+
rhs.withPos(accessed.pos)
42+
})
43+
44+
def addAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] = {
45+
val accDefs = accessorDefs(cls)
46+
if (accDefs.isEmpty) body else body ++ accDefs
47+
}
48+
49+
trait Insert {
50+
import ast.tpd._
51+
52+
def needsAccessor(sym: Symbol)(implicit ctx: Context): Boolean
53+
54+
/** A fresh accessor symbol */
55+
def newAccessorSymbol(accessed: Symbol, name: TermName, info: Type)(implicit ctx: Context): TermSymbol =
56+
ctx.newSymbol(accessed.owner.enclosingSubClass, name, Synthetic | Method,
57+
info, coord = accessed.pos).entered
58+
59+
/** Create an accessor unless one exists already, and replace the original
60+
* access with a reference to the accessor.
61+
*
62+
* @param reference The original reference to the non-public symbol
63+
* @param onLHS The reference is on the left-hand side of an assignment
64+
*/
65+
def useAccessor(reference: RefTree, onLHS: Boolean)(implicit ctx: Context): Tree = {
66+
67+
def nameKind = if (onLHS) setterName else getterName
68+
val accessed = reference.symbol.asTerm
69+
70+
def refersToAccessed(sym: Symbol) = sym.getAnnotation(defn.AccessedAnnot) match {
71+
case Some(Annotation.Accessed(sym)) => sym `eq` accessed
72+
case _ => false
73+
}
74+
75+
val accessorInfo =
76+
if (onLHS) MethodType(accessed.info :: Nil, defn.UnitType)
77+
else accessed.info.ensureMethodic
78+
val accessorName = nameKind(accessed.name)
79+
val accessorSymbol =
80+
accessed.owner.info.decl(accessorName).suchThat(refersToAccessed).symbol
81+
.orElse {
82+
val acc = newAccessorSymbol(accessed, accessorName, accessorInfo)
83+
acc.addAnnotation(Annotation.Accessed(accessed))
84+
acc
85+
}
86+
87+
{ reference match {
88+
case Select(qual, _) => qual.select(accessorSymbol)
89+
case Ident(name) => ref(accessorSymbol)
90+
}
91+
}.withPos(reference.pos)
92+
}
93+
94+
/** Replace tree with a reference to an accessor if needed */
95+
def accessorIfNeeded(tree: Tree)(implicit ctx: Context): Tree = tree match {
96+
case tree: RefTree if needsAccessor(tree.symbol) =>
97+
if (tree.symbol.isConstructor) {
98+
ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos)
99+
tree // TODO: create a proper accessor for the private constructor
100+
}
101+
else useAccessor(tree, onLHS = false)
102+
case Assign(lhs: RefTree, rhs) if needsAccessor(lhs.symbol) =>
103+
cpy.Apply(tree)(useAccessor(lhs, onLHS = true), List(rhs))
104+
case _ =>
105+
tree
106+
}
107+
}
108+
}

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import typer.ProtoTypes._
2020
import typer.ErrorReporting._
2121
import core.TypeErasure._
2222
import core.Decorators._
23+
import core.NameKinds._
2324
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
2425
import ast.Trees._
2526
import scala.collection.mutable.ListBuffer
@@ -30,7 +31,6 @@ import ExplicitOuter._
3031
import core.Mode
3132
import reporting.trace
3233

33-
3434
class Erasure extends Phase with DenotTransformer {
3535

3636
override def phaseName: String = Erasure.name
@@ -315,6 +315,10 @@ object Erasure {
315315
}
316316
}
317317

318+
/** The erasure typer.
319+
* Also inserts protected accessors where needed. This logic is placed here
320+
* since it is most naturally done in a macro transform.
321+
*/
318322
class Typer extends typer.ReTyper with NoChecking {
319323
import Boxing._
320324

@@ -323,6 +327,23 @@ object Erasure {
323327
if (tree.isTerm) erasedRef(tp) else valueErasure(tp)
324328
}
325329

330+
object ProtectedAccessors extends AccessProxies {
331+
def getterName = ProtectedAccessorName
332+
def setterName = ProtectedSetterName
333+
334+
val insert = new Insert {
335+
def needsAccessor(sym: Symbol)(implicit ctx: Context): Boolean =
336+
false &&
337+
sym.isTerm && sym.is(Flags.Protected) &&
338+
ctx.owner.enclosingPackageClass != sym.enclosingPackageClass &&
339+
!ctx.owner.enclosingClass.derivesFrom(sym.owner) &&
340+
{ println(i"need protected acc $sym accessed from ${ctx.owner}"); assert(false); false }
341+
}
342+
}
343+
344+
override def addAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] =
345+
ProtectedAccessors.addAccessorDefs(cls, body)
346+
326347
override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
327348
assert(tree.hasType)
328349
val erasedTp = erasedType(tree)
@@ -357,6 +378,9 @@ object Erasure {
357378
else
358379
super.typedLiteral(tree)
359380

381+
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree =
382+
ProtectedAccessors.insert.accessorIfNeeded(super.typedIdent(tree, pt))
383+
360384
/** Type check select nodes, applying the following rewritings exhaustively
361385
* on selections `e.m`, where `OT` is the type of the owner of `m` and `ET`
362386
* is the erased type of the selection's original qualifier expression.
@@ -437,9 +461,12 @@ object Erasure {
437461
}
438462
}
439463

440-
recur(typed(tree.qualifier, AnySelectionProto))
464+
ProtectedAccessors.insert.accessorIfNeeded(recur(typed(tree.qualifier, AnySelectionProto)))
441465
}
442466

467+
override def typedAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree =
468+
ProtectedAccessors.insert.accessorIfNeeded(super.typedAssign(tree, pt))
469+
443470
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
444471
if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
445472
else {

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

Lines changed: 30 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import NameKinds.{ClassifiedNameKind, InlineGetterName, InlineSetterName}
1919
import ProtoTypes.selectionProto
2020
import SymDenotations.SymDenotation
2121
import Annotations._
22-
import transform.ExplicitOuter
22+
import transform.{ExplicitOuter, AccessProxies}
2323
import Inferencing.fullyDefinedType
2424
import config.Printers.inlining
2525
import ErrorReporting.errorTree
@@ -31,119 +31,51 @@ import util.Positions.Position
3131
object Inliner {
3232
import tpd._
3333

34-
/** Adds accessors for all non-public term members accessed
35-
* from `tree`. Non-public type members are currently left as they are.
36-
* This means that references to a private type will lead to typing failures
37-
* on the code when it is inlined. Less than ideal, but hard to do better (see below).
38-
*
39-
* @return If there are accessors generated, a thicket consisting of the rewritten `tree`
40-
* and all accessors, otherwise the original tree.
41-
*/
42-
private def makeInlineable(tree: Tree)(implicit ctx: Context) = {
43-
44-
val inlineMethod = ctx.owner
34+
object InlineAccessors extends AccessProxies {
35+
def getterName = InlineGetterName
36+
def setterName = InlineSetterName
4537

4638
/** A tree map which inserts accessors for all non-public term members accessed
47-
* from inlined code. Accessors are collected in the `accessors` buffer.
48-
*/
49-
object addAccessors extends TreeMap {
39+
* from inlined code. Accessors are collected in the `accessors` buffer.
40+
*/
41+
class MakeInlineable(inlineSym: Symbol) extends TreeMap with Insert {
5042

5143
/** A definition needs an accessor if it is private, protected, or qualified private
52-
* and it is not part of the tree that gets inlined. The latter test is implemented
53-
* by excluding all symbols properly contained in the inlined method.
54-
*/
44+
* and it is not part of the tree that gets inlined. The latter test is implemented
45+
* by excluding all symbols properly contained in the inlined method.
46+
*/
5547
def needsAccessor(sym: Symbol)(implicit ctx: Context) =
5648
sym.isTerm &&
5749
(sym.is(AccessFlags) || sym.privateWithin.exists) &&
58-
!sym.owner.isContainedIn(inlineMethod)
59-
60-
/** A fresh accessor symbol.
61-
*
62-
* @param tree The tree representing the original access to the non-public member
63-
*/
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.
69-
*
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
72-
*/
73-
def useAccessor(reference: RefTree, onLHS: Boolean)(implicit ctx: Context): Tree = {
74-
75-
def nameKind = if (onLHS) InlineSetterName else InlineGetterName
76-
val accessed = reference.symbol.asTerm
77-
78-
def refersToAccessed(sym: Symbol) = sym.getAnnotation(defn.AccessedAnnot) match {
79-
case Some(Annotation.Accessed(sym)) => sym `eq` accessed
80-
case _ => false
81-
}
50+
!sym.isContainedIn(inlineSym)
8251

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-
}
94-
95-
{ reference match {
96-
case Select(qual, _) => qual.select(accessorSymbol)
97-
case Ident(name) => ref(accessorSymbol)
98-
}
99-
}.withPos(reference.pos)
100-
}
10152

10253
// TODO: Also handle references to non-public types.
10354
// This is quite tricky, as such types can appear anywhere, including as parts
10455
// of types of other things. For the moment we do nothing and complain
10556
// at the implicit expansion site if there's a reference to an inaccessible type.
106-
override def transform(tree: Tree)(implicit ctx: Context): Tree = super.transform {
107-
tree match {
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
112-
}
113-
else useAccessor(tree, onLHS = false)
114-
case Assign(lhs: RefTree, rhs) if needsAccessor(lhs.symbol) =>
115-
cpy.Apply(tree)(useAccessor(lhs, onLHS = true), List(rhs))
116-
case _ =>
117-
tree
118-
}
119-
}
57+
override def transform(tree: Tree)(implicit ctx: Context): Tree =
58+
super.transform(accessorIfNeeded(tree))
12059
}
12160

122-
if (inlineMethod.owner.isTerm)
123-
// Inline methods in local scopes can only be called in the scope they are defined,
124-
// so no accessors are needed for them.
125-
tree
126-
else addAccessors.transform(tree)
61+
/** Adds accessors for all non-public term members accessed
62+
* from `tree`. Non-public type members are currently left as they are.
63+
* This means that references to a private type will lead to typing failures
64+
* on the code when it is inlined. Less than ideal, but hard to do better (see below).
65+
*
66+
* @return If there are accessors generated, a thicket consisting of the rewritten `tree`
67+
* and all accessors, otherwise the original tree.
68+
*/
69+
def makeInlineable(tree: Tree)(implicit ctx: Context) = {
70+
val inlineSym = ctx.owner
71+
if (inlineSym.owner.isTerm)
72+
// Inline methods in local scopes can only be called in the scope they are defined,
73+
// so no accessors are needed for them.
74+
tree
75+
else new MakeInlineable(inlineSym).transform(tree)
76+
}
12777
}
12878

129-
/** The inline accessor definitions that need to be added to class `cls`
130-
* As a side-effect, this method removes the `Accessed` annotations from
131-
* the accessor symbols. So a second call of the same method will yield the empty list.
132-
*/
133-
def accessorDefs(cls: Symbol)(implicit ctx: Context): List[DefDef] =
134-
for (accessor <- cls.info.decls.filter(sym => sym.name.is(InlineGetterName) || sym.name.is(InlineSetterName)))
135-
yield polyDefDef(accessor.asTerm, tps => argss => {
136-
val Annotation.Accessed(accessed) = accessor.getAnnotation(defn.AccessedAnnot).get
137-
accessor.removeAnnotation(defn.AccessedAnnot)
138-
val rhs =
139-
if (accessor.name.is(InlineSetterName) &&
140-
argss.nonEmpty && argss.head.nonEmpty) // defensive conditions
141-
ref(accessed).becomes(argss.head.head)
142-
else
143-
ref(accessed).appliedToTypes(tps).appliedToArgss(argss)
144-
rhs.withPos(accessed.pos)
145-
})
146-
14779
/** Register inline info for given inline method `sym`.
14880
*
14981
* @param sym The symbol denotatioon of the inline method for which info is registered
@@ -163,7 +95,7 @@ object Inliner {
16395
sym.updateAnnotation(LazyBodyAnnotation { _ =>
16496
implicit val ctx = inlineCtx
16597
val body = treeExpr(ctx)
166-
if (ctx.reporter.hasErrors) body else makeInlineable(body)
98+
if (ctx.reporter.hasErrors) body else InlineAccessors.makeInlineable(body)
16799
})
168100
}
169101
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,6 @@ class ReTyper extends Typer with ReChecking {
123123
Implicits.NoMatchingImplicitsFailure
124124
override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = ()
125125
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
126+
override protected def addAccessorDefs(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: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import ErrorReporting._
2525
import Checking._
2626
import Inferencing._
2727
import EtaExpansion.etaExpand
28-
import dotty.tools.dotc.transform.Erasure.Boxing
2928
import util.Positions._
3029
import util.common._
3130
import util.{Property, SourcePosition}
@@ -1523,7 +1522,7 @@ class Typer extends Namer
15231522
cdef.withType(UnspecifiedErrorType)
15241523
} else {
15251524
val dummy = localDummy(cls, impl)
1526-
val body1 = addInlineAccessorDefs(cls,
1525+
val body1 = addAccessorDefs(cls,
15271526
typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol)))
15281527
if (!ctx.isAfterTyper)
15291528
cls.setNoInitsFlags((NoInitsInterface /: body1) ((fs, stat) => fs & defKind(stat)))
@@ -1562,10 +1561,8 @@ class Typer extends Namer
15621561
}
15631562
}
15641563

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-
}
1564+
protected def addAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] =
1565+
Inliner.InlineAccessors.addAccessorDefs(cls, body)
15691566

15701567
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
15711568
* If that's not already the case, add one. The added class type CT is determined as follows.

0 commit comments

Comments
 (0)