@@ -176,14 +176,25 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
176176 // NOTE: this only considers type, filter on flags first!
177177 def fieldMemoizationIn (accessorOrField : Symbol , site : Symbol ) = new FieldMemoization (accessorOrField, site)
178178
179- // drop field-targeting annotations from getters
179+ // drop field-targeting annotations from getters (done during erasure because we first need to create the field symbol)
180180 // (in traits, getters must also hold annotations that target the underlying field,
181181 // because the latter won't be created until the trait is mixed into a class)
182182 // TODO do bean getters need special treatment to suppress field-targeting annotations in traits?
183183 def dropFieldAnnotationsFromGetter (sym : Symbol ) =
184- if (sym.isGetter && sym.owner.isTrait) {
185- sym setAnnotations (sym.annotations filter AnnotationInfo .mkFilter(GetterTargetClass , defaultRetention = false ))
186- }
184+ sym setAnnotations (sym.annotations filter AnnotationInfo .mkFilter(GetterTargetClass , defaultRetention = false ))
185+
186+ def symbolAnnotationsTargetFieldAndGetter (sym : Symbol ): Boolean = sym.isGetter && (sym.isLazy || sym.owner.isTrait)
187+
188+ // A trait val/var or a lazy val does not receive an underlying field symbol until this phase.
189+ // Since annotations need a carrier symbol from the beginning, both field- and getter-targeting annotations
190+ // are kept on the getter symbol for these until they are dropped by dropFieldAnnotationsFromGetter
191+ def getterTreeAnnotationsTargetFieldAndGetter (owner : Symbol , mods : Modifiers ) = mods.isLazy || owner.isTrait
192+
193+ // Propagate field-targeting annotations from getter to field.
194+ // By the way, we must keep them around long enough to see them here (now that we have created the field),
195+ // which is why dropFieldAnnotationsFromGetter is not called until erasure.
196+ private def propagateFieldAnnotations (getter : Symbol , field : TermSymbol ): Unit =
197+ field setAnnotations (getter.annotations filter AnnotationInfo .mkFilter(FieldTargetClass , defaultRetention = true ))
187198
188199
189200 // can't use the referenced field since it already tracks the module's moduleClass
@@ -241,6 +252,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
241252 sym
242253 }
243254
255+
244256 private object synthFieldsAndAccessors extends TypeMap {
245257 private def newTraitSetter (getter : Symbol , clazz : Symbol ) = {
246258 // Add setter for an immutable, memoizing getter
@@ -388,10 +400,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
388400 val accessorSymbolSynth = checkedAccessorSymbolSynth(tp.typeSymbol)
389401
390402 // expand module def in class/object (if they need it -- see modulesNeedingExpansion above)
391- val expandedModulesAndLazyVals = (
403+ val expandedModulesAndLazyVals =
392404 modulesAndLazyValsNeedingExpansion flatMap { member =>
393405 if (member.isLazy) {
394- List (newLazyVarMember(member), accessorSymbolSynth.newSlowPathSymbol(member))
406+ val lazyVar = newLazyVarMember(member)
407+ propagateFieldAnnotations(member, lazyVar)
408+ List (lazyVar, accessorSymbolSynth.newSlowPathSymbol(member))
395409 }
396410 // expanding module def (top-level or nested in static module)
397411 else List (if (member.isStatic) { // implies m.isOverridingSymbol as per above filter
@@ -404,7 +418,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
404418 member setFlag NEEDS_TREES
405419 newModuleVarMember(member)
406420 })
407- })
421+ }
408422
409423// println(s"expanded modules for $clazz: $expandedModules")
410424
@@ -419,8 +433,9 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
419433 val clonedAccessor = (member cloneSymbol clazz) setPos clazz.pos
420434 setMixedinAccessorFlags(member, clonedAccessor)
421435
422- if (clonedAccessor.isGetter)
423- clonedAccessor setAnnotations (clonedAccessor.annotations filter AnnotationInfo .mkFilter(GetterTargetClass , defaultRetention = false ))
436+ // note: check original member when deciding how to triage annotations, then act on the cloned accessor
437+ if (symbolAnnotationsTargetFieldAndGetter(member)) // this simplifies to member.isGetter, but the full formulation really ties the triage together
438+ dropFieldAnnotationsFromGetter(clonedAccessor)
424439
425440 // if we don't cloneInfo, method argument symbols are shared between trait and subclasses --> lambalift proxy crash
426441 // TODO: use derive symbol variant?
@@ -450,7 +465,11 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
450465 }
451466 else if (member hasFlag LAZY ) {
452467 val mixedinLazy = cloneAccessor()
453- val lazyVar = newLazyVarMember(mixedinLazy)
468+ val lazyVar = newLazyVarMember(mixedinLazy) // link lazy var member to the mixedin lazy accessor
469+
470+ // propagate from original member. since mixed in one has only retained the annotations targeting the getter
471+ propagateFieldAnnotations(member, lazyVar)
472+
454473 // println(s"mixing in lazy var: $lazyVar for $member")
455474 List (lazyVar, accessorSymbolSynth.newSlowPathSymbol(mixedinLazy), newSuperLazy(mixedinLazy, site, lazyVar))
456475 }
@@ -460,9 +479,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
460479
461480 setFieldFlags(member, field)
462481
463- // filter getter's annotations to exclude those only meant for the field
464- // we must keep them around long enough to see them here, though, when we create the field
465- field setAnnotations (member.annotations filter AnnotationInfo .mkFilter(FieldTargetClass , defaultRetention = true ))
482+ propagateFieldAnnotations(member, field)
466483
467484 List (cloneAccessor(), field)
468485 } else List (cloneAccessor()) // no field needed (constant-typed getter has constant as its RHS)
0 commit comments