forked from scala/scala
/
Fields.scala
407 lines (330 loc) · 21.5 KB
/
Fields.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author
*/
package scala.tools.nsc
package transform
import scala.annotation.tailrec
import symtab.Flags._
/** Lift rhs out of vals, synthesize accessors and field for each (strict) val owned by a class.
*
* For traits:
* - Namers translates a definition `val x = rhs` into a getter `def x = rhs`
* - This phase moves `rhs` into a block nested under the template's local dummy.
* If the value is memoized (stored), the block's final expression assigns the value to the val,
* and the val itself is left without a rhs.
* (Dotty uses a method `x$compute` instead of a block, which is used in the constructor to initialize the val.
* It also unifies strict and lazy vals, in that the RHS is lifted to a method for both.)
* If the value of the rhs is a literal or unit, it is not stored and the final expression of the block is ().
* The val then retains this statically known value as its rhs, with its side-effects still lifted to the block.
* - This phases also synthesizes accessors and fields for any vals mixed into a non-trait class.
* - Constructors moves the expressions in the template into the constructor,
* which means it will initialize the values defined in this template.
*
* Runs before erasure (to get bridges), but before lambdalift/flatten, so nested functions/definitions must be considered.
*/
abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers {
import global._
import definitions._
/** the following two members override abstract members in Transform */
val phaseName: String = "fields"
// used for internal communication between info and tree transform of this phase -- not pickled, not in initialflags
override def phaseNewFlags: Long = NEEDS_TREES | OVERRIDDEN_TRAIT_SETTER | FINAL_TRAIT_ACCESSOR
protected def newTransformer(unit: CompilationUnit): Transformer =
new FieldsTransformer(unit)
def matchingAccessor(pre: Type, member: Symbol, clazz: Symbol) = {
val res = member.matchingSymbol(clazz, pre) filter (sym => (sym hasFlag ACCESSOR) && (!(sym hasFlag DEFERRED) || (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS)))
// if (res != NoSymbol) println(s"matching accessor for $member in $clazz = $res (under $pre)")
// else println(s"no matching accessor for $member in $clazz (under $pre) among ${clazz.info.decls}")
res
}
private def isOverriddenAccessor(member: Symbol, site: Symbol): Boolean = {
val pre = site.thisType
@tailrec def loop (bcs: List[Symbol]): Boolean = {
// println(s"checking ${bcs.head} for member overriding $member (of ${member.owner})")
bcs.nonEmpty && bcs.head != member.owner && (matchingAccessor(pre, member, bcs.head) != NoSymbol || loop(bcs.tail))
}
member.exists && loop(site.info.baseClasses)
}
class FieldMemoization(accessorOrField: Symbol, site: Symbol) {
private val tp = fieldTypeOfAccessorIn(accessorOrField, site.thisType)
val memoized = !tp.isInstanceOf[ConstantType]
// do not assign to field
// getter for Unit-typed val, or setter for var that isn't stored (of type Unit)
val effectOnly = isUnitType(tp) || (!site.isTrait && accessorOrField.isSetter && !memoized)
val effectful = memoized || effectOnly
val needsField = !effectOnly && memoized
// println(s"fieldMemoizationIn $sym $site = $tp")
def assignSym: Symbol =
if (effectOnly) NoSymbol
else if (accessorOrField hasFlag ACCESSOR) accessorOrField.setterIn(site)
else accessorOrField
}
private def fieldTypeForGetterIn(getter: Symbol, pre: Type): Type = getter.info.finalResultType.asSeenFrom(pre, getter.owner)
private def fieldTypeForSetterIn(setter: Symbol, pre: Type): Type = setter.info.paramTypes.head.asSeenFrom(pre, setter.owner)
def fieldTypeOfAccessorIn(accessor: Symbol, pre: Type) = {
// TODO: is there a more elegant way?
if (accessor.isSetter) fieldTypeForSetterIn(accessor, pre) else fieldTypeForGetterIn(accessor, pre)
}
// Constant/unit typed vals are not memoized (their value is so cheap it doesn't make sense to store it in a field)
// for a unit-typed getter, we perform the effect at the appropriate time (constructor for eager ones, lzyCompute for lazy),
// and have the getter just return Unit (who does that!?)
// NOTE: this only considers type, filter on flags first!
def fieldMemoizationIn(accessorOrField: Symbol, site: Symbol) = new FieldMemoization(accessorOrField, site)
override def transformInfo(sym: Symbol, tp: Type): Type = if (!sym.isJavaDefined) synthFieldsAndAccessors(tp) else tp
private def newTraitSetter(getter: Symbol, clazz: Symbol) = {
// Add setter for an immutable, memoizing getter
// (can't emit during namers because we don't yet know whether it's going to be memoized or not)
// TODO: any flags to inherit from getter??? (probably not -- certainly exclude: stable, override, implicit, private, local)
// TODO: stable/mutable? (we need to access the setter from the init method, so it needs to be in the interface)
// TODO: annotations?
// TODO: ARTIFACT or SYNTHETIC?? neither?
// protected, because implemented by subclass, never used outside of hierarchy
val setterFlags = (getter.flags & ~(STABLE | PrivateLocal | OVERRIDE | IMPLICIT | FINAL)) | MUTABLE | ACCESSOR | NEEDS_TREES | DEFERRED
val setterName = nme.expandedSetterName(getter.name.setterName, clazz)
val setter = clazz.newMethod(setterName, getter.pos.focus, setterFlags)
val fieldTp = fieldTypeForGetterIn(getter, clazz.thisType)
// println(s"newTraitSetter in $clazz for $getter = $setterName : $fieldTp")
setter setInfo MethodType(List(setter.newSyntheticValueParam(fieldTp)), UnitTpe)
setter
}
private val synthFieldsAndAccessors = new TypeMap {
def apply(tp0: Type): Type = mapOver(tp0) match {
// TODO: make less destructive (name changes, decl additions, flag setting --
// none of this is actually undone when travelling back in time using atPhase)
case tp@ClassInfoType(parents, decls, clazz) if clazz.isTrait =>
val newSetters = collection.mutable.ListBuffer[Symbol]()
// strict, memoized accessors will receive an implementation in first real class to extend this trait
decls.foreach {
case accessor if accessor hasFlag ACCESSOR =>
// check flags before calling makeNotPrivate
val memoizedGetter = !(accessor hasFlag (DEFERRED | LAZY)) && fieldMemoizationIn(accessor, clazz).needsField
val finality = if (accessor hasFlag FINAL) FINAL_TRAIT_ACCESSOR else 0
// only affects private symbols, with a destructive update of their name, also sets flags
// required for private vals in traits
accessor.makeNotPrivate(clazz)
// Need to mark as notPROTECTED, so that it's carried over to the synthesized member in subclasses,
// since the trait member will receive this flag later in ExplicitOuter, but the synthetic subclass member will not.
// If we don't add notPROTECTED to the synthesized one, the member will not be seen as overriding the trait member.
// Therefore, addForwarders's call to membersBasedOnFlags would see the deferred member in the trait,
// instead of the concrete (desired) one in the class
if (accessor.isProtected) accessor setFlag notPROTECTED
// trait members cannot be final (but the synthesized ones should be)
// must not reset LOCAL, as we must maintain protected[this]ness to allow that variance hole
// (not sure why this only problem only arose when we started setting the notPROTECTED flag)
accessor resetFlag (FINAL)
// derive trait setter after calling makeNotPrivate (so that names are mangled consistently)
if (memoizedGetter) {
// a memoized accessor in a trait is made deferred now (mixins will deal with non-memoized getters like any other method)
// can't mark getter as FINAL in trait, but remember for when we synthetisize the impl in the subclass to make it FINAL
// (it'll receive an implementation in the first real class to extend this trait)
accessor setFlag (finality | DEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS)
if ((accessor hasFlag STABLE) && accessor.isGetter) // TODO: isGetter is probably redundant?
newSetters += newTraitSetter(accessor, clazz)
}
case _ =>
}
if (newSetters nonEmpty) {
// println(s"newSetters for $clazz = $newSetters")
val newDecls = decls.cloneScope
newSetters foreach newDecls.enter
ClassInfoType(parents, newDecls, clazz)
} else tp
// mix in fields & accessors for all mixed in traits
case tp@ClassInfoType(parents, oldDecls, clazz) if !clazz.isPackageClass =>
val site = clazz.thisType
// TODO (1): improve logic below, which is used to avoid mixing in anything that would result in an error in refchecks
// (a reason to run after refchecks? we should run before pickler, though, I think, so that the synthesized stats are pickled)
// TODO (2): produce the right error message for (and all combos of val/var/def keywords for the two defs):
// trait OneVal { val x: Int = 123 }
// class Conflicting extends OneVal { def x: Int = 1 }
// Currently, this wrongly produces:
// test/files/trait-defaults/fields.scala:2: error: overriding variable x in trait OneVal of type Int;
// method x needs to be a stable, immutable value
// class Conflicting extends OneVal { def x: Int = 1 }
// test/files/trait-defaults/fields.scala:2: error: class Conflicting needs to be abstract, since variable x in trait OneVal of type Int is not defined
// (Note that an abstract var requires a setter in addition to the getter)
// class Conflicting extends OneVal { def x: Int = 1 }
// two errors found
//
// it should simply say we need `override` for the def in Conflicting
//
// the setter for the immutable val in OneVal has already been synthesized above
// TODO (3): what should this produce?
// trait OneVal[T] { val x: Int = 123 }
// class OverridingVal extends OneVal[Int] { val x: Int }
val accessorsMaybeNeedingImpl = clazz.mixinClasses.flatMap { mixin =>
// afterOwnPhase, so traits receive trait setters for vals
afterOwnPhase { mixin.info }.decls.toList.filter(accessorImplementedInSubclass)
}
// println(s"mixing in $accessorsMaybeNeedingImpl from ${clazz.mixinClasses}")
// TODO: setter conflicts?
def accessorConflictsExistingVal(accessor: Symbol): Boolean = {
val existingGetter = oldDecls.lookup(accessor.name.getterName)
// println(s"$existingGetter from $accessor to ${accessor.name.getterName}")
val tp = fieldTypeOfAccessorIn(accessor, site)
(existingGetter ne NoSymbol) && (tp matches (site memberInfo existingGetter)) // !existingGetter.isDeferred && -- see (3)
}
// TODO: special dance when we're overriding a val
// trait OneVal[T] { val x: Int = 123 }
// class OverridingVal extends OneVal[Int] { override val x: Int = ??? }
// mixin field accessors --
// invariant: (accessorsMaybeNeedingImpl, mixedInAccessorAndFields).zipped.forall(case (acc, clone :: _) => `clone` is clone of `acc` case _ => true)
val mixedInAccessorAndFields = accessorsMaybeNeedingImpl map { accessor =>
def cloneAccessor() = {
val clonedAccessor = (accessor cloneSymbol clazz) setPos clazz.pos setFlag NEEDS_TREES resetFlag DEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS | FINAL_TRAIT_ACCESSOR
if (accessor hasFlag FINAL_TRAIT_ACCESSOR) {
clonedAccessor setFlag FINAL | lateFINAL // lateFINAL thrown in for good measure, by analogy to makeNotPrivate
}
// if we don't cloneInfo, method argument symbols are shared between trait and subclasses --> lambalift proxy crash
// TODO: use derive symbol variant?
// println(s"cloning accessor $accessor to $clazz / $clonedInfo -> $relativeInfo")
clonedAccessor setInfo ((clazz.thisType memberType accessor) cloneInfo clonedAccessor) // accessor.info.cloneInfo(clonedAccessor).asSeenFrom(clazz.thisType, accessor.owner)
}
// when considering whether to mix in the trait setter, forget about conflicts -- they will be reported for the getter
// a trait setter for an overridden val will receive a unit body in the tree transform
// (this is communicated using the DEFERRED flag)
if (nme.isTraitSetterName(accessor.name)) {
val getter = accessor.getterIn(accessor.owner)
val overridden = isOverriddenAccessor(getter, clazz)
// println(s"mixing in trait setter ${accessor.defString}: $overridden")
val clone = cloneAccessor()
clone filterAnnotations (ai => !ai.matches(TraitSetterAnnotationClass)) // only supposed to be set in trait
if (overridden) clone setFlag OVERRIDDEN_TRAIT_SETTER
else if (getter.isEffectivelyFinal) clone setFlag FINAL // TODO: why isn't the FINAL_TRAIT_ACCESSOR carry-over from getter enough?
// println(s"mixed in trait setter ${clone.defString}")
List(clone)
}
// avoid creating early errors in case of conflicts (wait until refchecks);
// also, skip overridden accessors contributed by supertraits (only act on the last overriding one)
else if (accessorConflictsExistingVal(accessor) || isOverriddenAccessor(accessor, clazz)) Nil
else if (accessor.isGetter && fieldMemoizationIn(accessor, clazz).needsField) {
// add field if needed
val field = clazz.newValue(accessor.localName, accessor.pos) setInfo fieldTypeForGetterIn(accessor, clazz.thisType)
val newFlags = (
(PrivateLocal | NEEDS_TREES)
| (accessor getFlag MUTABLE | LAZY)
| (if (accessor.hasStableFlag) 0 else MUTABLE)
)
// TODO: filter getter's annotations to exclude those only meant for the field
// we must keep them around long enough to see them here, though, when we create the field
field setAnnotations (accessor.annotations filter AnnotationInfo.mkFilter(FieldTargetClass, defaultRetention = true))
field setFlag newFlags
List(cloneAccessor(), field)
} else List(cloneAccessor())
}
// println(s"new decls for $clazz: $mixedInAccessorAndFields")
// omit fields that are not memoized, retain all other members
def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && fieldMemoizationIn(sym, clazz).effectOnly // TODO: not yet `needsField`, to produce same bytecode as M2
val newDecls =
if (mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField)
else { // must not alter `decls` directly
val newDecls = newScope
oldDecls foreach { d => if (!omittableField(d)) newDecls.enter(d) }
val enter = { mixedin: Symbol => newDecls enter mixedin }
mixedInAccessorAndFields foreach { _ foreach enter }
// subst from accessors to corresponding clonedAccessors in types in newDecls
val (origs, mixedins) = (accessorsMaybeNeedingImpl, mixedInAccessorAndFields).zipped.collect {
case (traitAccessor, mixedin :: _) => (traitAccessor, mixedin)
}.unzip
newDecls foreach { sym => sym.substInfo(origs.toList, mixedins.toList) }
newDecls
}
// println(s"new decls: $newDecls")
if (newDecls eq oldDecls) tp
else ClassInfoType(parents, newDecls, clazz)
case tp => tp
}
}
private def accessorImplementedInSubclass(accessor: Symbol) =
accessor hasAllFlags (ACCESSOR | SYNTHESIZE_IMPL_IN_SUBCLASS)
class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
def mkTypedUnit(pos: Position) = localTyper.typedPos(pos)(CODE.UNIT)
def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos))
// synth trees for accessors/fields and trait setters when they are mixed into a class
def fieldsAndAccessors(templateSym: Symbol): List[ValOrDefDef] = {
val clazz = templateSym.owner
def fieldAccess(accessor: Symbol) = {
val fieldName = accessor.localName
val field = clazz.info.decl(fieldName)
assert(field.exists, s"Field '$fieldName' not found in ${clazz.info.decls} of $clazz for $accessor")
Select(This(clazz), field)
}
val accessorsAndFieldsNeedingTrees = clazz.info.decls.toList.filter(_ hasFlag NEEDS_TREES)
accessorsAndFieldsNeedingTrees foreach (_ resetFlag NEEDS_TREES) // emitting the needed trees now
// println(s"accessorsAndFieldsNeedingTrees: $accessorsAndFieldsNeedingTrees")
def setterBody(setter: Symbol): Tree = {
// trait setter in trait
if (clazz.isTrait) EmptyTree
// trait setter for overridden val in class
else if (setter hasFlag OVERRIDDEN_TRAIT_SETTER) { setter resetFlag OVERRIDDEN_TRAIT_SETTER ; mkTypedUnit(setter.pos) }
// trait val/var setter mixed into class
else Assign(fieldAccess(setter), Ident(setter.firstParam))
}
accessorsAndFieldsNeedingTrees map {
case setter if setter.isSetter => localTyper.typedPos(setter.pos)(DefDef(setter, setterBody(setter))).asInstanceOf[DefDef]
case getter if getter.isAccessor => localTyper.typedPos(getter.pos)(DefDef(getter, fieldAccess(getter))).asInstanceOf[DefDef]
case field => assert(field.isValue && !field.isMethod, s"Expecting field, got $field")
localTyper.typedPos(field.pos)(ValDef(field)).asInstanceOf[ValDef]
}
}
private def transformStat(templateSym: Symbol)(stat: Tree): List[Tree] = {
val clazz = templateSym.owner
// TODO do we need to .changeOwner(statSym -> owner)) `rhs` before transforming?
// changing owners as proposed above causes a stackoverflow in uncurry when bootstrapping
// maybe go from statSym -> statSym.owner?
// there's another (conflicting) issue when compiling scala.util.Properties,
// where some references from the impl class are still pointing to the trait interface, not the impl class
def initEffect(rhs: Tree, assignSym: Symbol) =
if (assignSym eq NoSymbol) rhs
else localTyper.typedPos(rhs.pos) {
val qual = Select(This(clazz), assignSym)
if (assignSym.isSetter) Apply(qual, List(rhs))
else Assign(qual, rhs)
}
val statSym = stat.symbol
// println(s"transformStat $statSym in ${templateSym.ownerChain}")
// `clazz` is not the right owner, local dummy is more accurate at this phase,
// since it's the symbol of the template and thus the owner of template statements
// currentRun.trackerFactory.snapshot()
def lifted(rhs: Tree) = super.transform(rhs.changeOwner(statSym -> templateSym))
stat match {
case DefDef(_, _, _, _, _, rhs) if (rhs ne EmptyTree) && (statSym hasFlag ACCESSOR) && !(statSym hasFlag LAZY) =>
val fieldMemoization = fieldMemoizationIn(statSym, clazz)
def getterRhs(x: Tree) = if (fieldMemoization.effectOnly) mkTypedUnit(statSym.pos) else EmptyTree
// TODO: consolidate with ValDef case
if (clazz.isTrait) {
// there's a synthetic setter if val is not mutable (symbol is created in info transform)
if (fieldMemoization.effectful) deriveDefDef(stat)(getterRhs) :: initEffect(lifted(rhs), fieldMemoization.assignSym) :: Nil
else stat :: Nil
} else (
// regular getter -- field will be preserved (see case ValDef)
if (fieldMemoization.needsField) stat
// getter for Unit-typed val, or setter for var that isn't stored (of type Unit)
else if (fieldMemoization.effectOnly) deriveUnitDef(stat)
// getter for constant, emit literal for its body
else deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe))
) :: Nil
// If a val needs a field, an empty valdef and an assignment to its rhs go into the template
case ValDef(mods, _, _, rhs) if (rhs ne EmptyTree) && !(statSym hasFlag LAZY) && !(mods hasFlag PRESUPER) =>
val fieldMemoization = fieldMemoizationIn(statSym, clazz)
if (fieldMemoization.needsField) deriveValDef(stat)(_ => EmptyTree) :: initEffect(lifted(rhs), fieldMemoization.assignSym) :: Nil
else if (fieldMemoization.effectOnly) initEffect(lifted(rhs), NoSymbol) :: Nil // drop the val entirely -- it could not have been referenced outside accesors
else Nil
case tree => List(super.transform(tree))
}
}
override def transformTemplate(tree: Template): Template = {
// println(s"transforming stats in ${currentOwner}")
// Skip interfaces (they have no concrete methods, so no work to be done)
if (!currentOwner.isClass || currentOwner.isPackageClass || currentOwner.isInterface) super.transformTemplate(tree)
else afterOwnPhase {
currentOwner.info // TODO remove -- for debugging
deriveTemplate(tree)(stats => {
val templateSym = tree.symbol
fieldsAndAccessors(templateSym) ++ stats.flatMap(transformStat(templateSym))
})
}
}
}
}