diff options
Diffstat (limited to 'src/compiler/scala/tools')
10 files changed, 333 insertions, 407 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 26e517743a..6339f0002d 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -176,9 +176,9 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // can't use the referenced field since it already tracks the module's moduleClass private[this] val moduleVarOf = perRunCaches.newMap[Symbol, Symbol] - private def newModuleVarSymbol(site: Symbol, module: Symbol, tp: Type, extraFlags: Long): TermSymbol = { + private def newModuleVarSymbol(owner: Symbol, module: Symbol, tp: Type, extraFlags: Long): TermSymbol = { // println(s"new module var in $site for $module of type $tp") - val moduleVar = site.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | extraFlags) setInfo tp addAnnotation VolatileAttr + val moduleVar = owner.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | extraFlags) setInfo tp addAnnotation VolatileAttr moduleVarOf(module) = moduleVar moduleVar @@ -191,6 +191,37 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } + private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0, localLazyVal: Boolean = false): TermSymbol = { + val flags = member.flags | extraFlags + val name = member.name.toTermName + val pos = member.pos + + // If the owner is not a class, this is a lazy val from a method, + // with no associated field. It has an accessor with $lzy appended to its name and + // its flags are set differently. The implicit flag is reset because otherwise + // a local implicit "lazy val x" will create an ambiguity with itself + // via "x$lzy" as can be seen in test #3927. + val nameSuffix = + if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING + else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING + + // TODO: should end up final in bytecode + val fieldFlags = + if (!localLazyVal) flags & FieldFlags | PrivateLocal | MUTABLE + else (flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) + +// println(s"new lazy var sym in $owner for $member ${symtab.Flags.flagsToString(fieldFlags)}") + val sym = owner.newValue(name.append(nameSuffix), pos.focus, fieldFlags | extraFlags) setInfo tp + moduleVarOf(member) = sym + sym + } + + private def lazyValInit(member: Symbol, rhs: Tree) = { + val lazyVar = moduleVarOf(member) + assert(lazyVar.isMutable, lazyVar) + gen.mkAssignAndReturn(lazyVar, rhs) + } + private object synthFieldsAndAccessors extends TypeMap { private def newTraitSetter(getter: Symbol, clazz: Symbol) = { // Add setter for an immutable, memoizing getter @@ -221,7 +252,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor accessor } - // needed for the following scenario (T could be trait or class) // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O. // marking it as an ACCESSOR so that it will get to `getterBody` when synthesizing trees below @@ -233,6 +263,18 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } + private def newSuperLazy(lazyCallingSuper: Symbol, site: Type, lazyVar: Symbol) = { + lazyCallingSuper.asTerm.referenced = lazyVar + + val tp = site.memberInfo(lazyCallingSuper) + + lazyVar setInfo tp.resultType + lazyCallingSuper setInfo tp + } + + // make sure they end up final in bytecode + final private val fieldFlags = PrivateLocal | FINAL | SYNTHETIC | NEEDS_TREES + def apply(tp0: Type): Type = 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) @@ -246,7 +288,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (member hasFlag ACCESSOR) { val fieldMemoization = fieldMemoizationIn(member, clazz) // check flags before calling makeNotPrivate - val accessorUnderConsideration = !(member hasFlag (DEFERRED | LAZY)) + val accessorUnderConsideration = !(member hasFlag DEFERRED) // destructively mangle accessor's name (which may cause rehashing of decls), also sets flags // this accessor has to be implemented in a subclass -- can't be private @@ -265,7 +307,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (accessorUnderConsideration && fieldMemoization.stored) { synthesizeImplInSubclasses(member) - if (member hasFlag STABLE) // TODO: check isGetter? + if ((member hasFlag STABLE) && !(member hasFlag LAZY)) newDecls += newTraitSetter(member, clazz) } } else if (member hasFlag MODULE) { @@ -296,27 +338,36 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor (existingGetter ne NoSymbol) && (tp matches (site memberInfo existingGetter).resultType) // !existingGetter.isDeferred && -- see (3) } - def newModuleVar(member: Symbol): TermSymbol = - newModuleVarSymbol(clazz, member, site.memberType(member).resultType, PrivateLocal | SYNTHETIC | NEEDS_TREES) + def newModuleVarMember(member: Symbol): TermSymbol = + newModuleVarSymbol(clazz, member, site.memberType(member).resultType, fieldFlags) + + def newLazyVarMember(member: Symbol): TermSymbol = + newLazyVarSymbol(clazz, member, site.memberType(member).resultType, fieldFlags) // a module does not need treatment here if it's static, unless it has a matching member in a superclass // a non-static method needs a module var - val modulesNeedingExpansion = - oldDecls.toList.filter(m => m.isModule && (!m.isStatic || m.isOverridingSymbol)) + val modulesAndLazyValsNeedingExpansion = + oldDecls.toList.filter(m => + (m.isModule && (!m.isStatic || m.isOverridingSymbol)) + || (m.isLazy && !(m.info.isInstanceOf[ConstantType] || isUnitType(m.info))) // no need for ASF since we're in the defining class + ) // expand module def in class/object (if they need it -- see modulesNeedingExpansion above) - val expandedModules = - modulesNeedingExpansion map { module => + val expandedModulesAndLazyVals = + modulesAndLazyValsNeedingExpansion map { member => + if (member.isLazy) { + newLazyVarMember(member) + } // expanding module def (top-level or nested in static module) - if (module.isStatic) { // implies m.isOverridingSymbol as per above filter + else if (member.isStatic) { // implies m.isOverridingSymbol as per above filter // Need a module accessor, to implement/override a matching member in a superclass. // Never a need for a module var if the module is static. - newMatchingModuleAccessor(clazz, module) + newMatchingModuleAccessor(clazz, member) } else { - nonStaticModuleToMethod(module) + nonStaticModuleToMethod(member) // must reuse symbol instead of creating an accessor - module setFlag NEEDS_TREES - newModuleVar(module) + member setFlag NEEDS_TREES + newModuleVarMember(member) } } @@ -344,13 +395,9 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor clonedAccessor } - if (member hasFlag MODULE) { - val moduleVar = newModuleVar(member) - List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) - } // when considering whether to mix in the trait setter, forget about conflicts -- they are reported for the getter // a trait setter for an overridden val will receive a unit body in the tree transform - else if (nme.isTraitSetterName(member.name)) { + if (nme.isTraitSetterName(member.name)) { val getter = member.getterIn(member.owner) val clone = cloneAccessor() @@ -362,6 +409,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // don't cause conflicts, skip overridden accessors contributed by supertraits (only act on the last overriding one) // see pos/trait_fields_dependent_conflict.scala and neg/t1960.scala else if (accessorConflictsExistingVal(member) || isOverriddenAccessor(member, clazz)) Nil + else if (member hasFlag MODULE) { + val moduleVar = newModuleVarMember(member) + List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) + } + else if (member hasFlag LAZY) { + val mixedinLazy = cloneAccessor() + val lazyVar = newLazyVarMember(mixedinLazy) + List(lazyVar, newSuperLazy(mixedinLazy, site, lazyVar)) + } else if (member.isGetter && fieldMemoizationIn(member, clazz).stored) { // add field if needed val field = clazz.newValue(member.localName, member.pos) setInfo fieldTypeForGetterIn(member, clazz.thisType) @@ -382,15 +438,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && !fieldMemoizationIn(sym, clazz).stored val newDecls = - if (expandedModules.isEmpty && mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) + if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) else { // must not alter `decls` directly val newDecls = newScope val enter = newDecls enter (_: Symbol) val enterAll = (_: List[Symbol]) foreach enter + expandedModulesAndLazyVals foreach enter oldDecls foreach { d => if (!omittableField(d)) enter(d) } - expandedModules foreach enter mixedInAccessorAndFields foreach enterAll newDecls @@ -421,6 +477,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def mkField(sym: Symbol) = localTyper.typedPos(sym.pos)(ValDef(sym)).asInstanceOf[ValDef] + // synth trees for accessors/fields and trait setters when they are mixed into a class def fieldsAndAccessors(clazz: Symbol): List[ValOrDefDef] = { def fieldAccess(accessor: Symbol): Option[Tree] = { @@ -462,8 +519,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else moduleInit(module) ) + def superLazy(getter: Symbol): Some[Tree] = { + assert(!clazz.isTrait) + // this contortion was the only way I can get the super select to be type checked correctly.. TODO: why does SelectSuper not work? + Some(gen.mkAssignAndReturn(moduleVarOf(getter), Apply(Select(Super(This(clazz), tpnme.EMPTY), getter.name), Nil))) + } + clazz.info.decls.toList.filter(checkAndClearNeedsTrees) flatMap { case module if module hasAllFlags (MODULE | METHOD) => moduleAccessorBody(module) map mkAccessor(module) + case getter if getter hasAllFlags (LAZY | METHOD) => superLazy(getter) map mkAccessor(getter) case setter if setter.isSetter => setterBody(setter) map mkAccessor(setter) case getter if getter.hasFlag(ACCESSOR) => getterBody(getter) map mkAccessor(getter) case field if !(field hasFlag METHOD) => Some(mkField(field)) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES) @@ -474,7 +538,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def rhsAtOwner(stat: ValOrDefDef, newOwner: Symbol): Tree = atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner))) - private def Thicket(trees: List[Tree]) = Block(trees, EmptyTree) override def transform(stat: Tree): Tree = { val clazz = currentOwner @@ -502,7 +565,31 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor && (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) && !clazz.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo && fieldMemoizationIn(statSym, clazz).pureConstant => - deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) // TODO: recurse? + deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) + + /** Normalize ValDefs to corresponding accessors + field + * + * ValDef in trait --> getter DefDef + * Lazy val receives field with a new symbol (if stored) and the ValDef's symbol is moved to a DefDef (the lazy accessor): + * - for lazy values of type Unit and all lazy fields inside traits, + * the rhs is the initializer itself, because we'll just "compute" the result on every access + * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap) + * - for all other lazy values z the accessor is a block of this form: + * { z = <rhs>; z } where z can be an identifier or a field. + */ + case vd@ValDef(mods, name, tpt, rhs) if vd.symbol.hasFlag(ACCESSOR) && treeInfo.noFieldFor(vd, clazz) => + def notStored = {val resultType = statSym.info.resultType ; (resultType.isInstanceOf[ConstantType] || isUnitType(resultType))} + val transformedRhs = atOwner(statSym)(transform(rhs)) + + if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) + else if (clazz.isTrait || notStored) mkAccessor(statSym)(transformedRhs) + else if (clazz.isClass) mkAccessor(statSym)(gen.mkAssignAndReturn(moduleVarOf(vd.symbol), transformedRhs)) + else { + // local lazy val (same story as modules: info transformer doesn't get here, so can't drive tree synthesis) + val lazyVar = newLazyVarSymbol(currentOwner, statSym, statSym.info.resultType, extraFlags = 0, localLazyVal = true) + val lazyValInit = gen.mkAssignAndReturn(lazyVar, transformedRhs) + Thicket(mkField(lazyVar) :: mkAccessor(statSym)(lazyValInit) :: Nil) + } // drop the val for (a) constant (pure & not-stored) and (b) not-stored (but still effectful) fields case ValDef(mods, _, _, rhs) if (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) @@ -524,6 +611,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } } + def transformTermsAtExprOwner(exprOwner: Symbol)(stat: Tree) = if (stat.isTerm) atOwner(exprOwner)(transform(stat)) else transform(stat) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index f781426f1a..4be611b747 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -11,6 +11,7 @@ import Flags._ import scala.annotation.tailrec import scala.collection.mutable +// TODO: move lazy vals bitmap creation to lazy vals phase now that lazy vals are mixed in during fields abstract class Mixin extends InfoTransform with ast.TreeDSL { import global._ import definitions._ @@ -57,8 +58,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { */ private val treatedClassInfos = perRunCaches.newMap[Symbol, Type]() withDefaultValue NoType - /** Map a lazy, mixedin field accessor to its trait member accessor */ - private val initializer = perRunCaches.newMap[Symbol, Symbol]() // --------- helper functions ----------------------------------------------- @@ -295,9 +294,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - /* Mix in members of trait mixinClass into class clazz. Also, - * for each lazy field in mixinClass, add a link from its mixed in member to its - * initializer method inside the implclass. + /* Mix in members of trait mixinClass into class clazz. */ def mixinTraitMembers(mixinClass: Symbol) { // For all members of a trait's interface do: @@ -319,28 +316,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember) - && (mixinMember hasFlag (LAZY | PARAMACCESSOR)) + && (mixinMember hasFlag PARAMACCESSOR) && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { - // pick up where `fields` left off -- it already mixed in fields and accessors for regular vals. - // but has ignored lazy vals and constructor parameter accessors - // TODO: captures added by lambdalift for local traits? - // - // mixin accessor for lazy val or constructor parameter + // mixin accessor for constructor parameter // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) val name = mixinMember.name - if (mixinMember.isLazy) - initializer(mixedInAccessor) = - (mixinClass.info.decl(name) orElse abort(s"Could not find initializer for lazy val $name!")) - - // Add field while we're mixing in the getter (unless it's a Unit-typed lazy val) - // - // lazy val of type Unit doesn't need a field -- the bitmap is enough. - // TODO: constant-typed lazy vals... it's an extreme corner case, but we could also suppress the field in: - // `trait T { final lazy val a = "a" }; class C extends T`, but who writes code like that!? :) - // we'd also have to change the lazyvals logic if we do this - if (!nme.isSetterName(name) && !(mixinMember.isLazy && isUnitGetter(mixinMember))) { + if (!nme.isSetterName(name)) { // enteringPhase: the private field is moved to the implementation class by erasure, // so it can no longer be found in the mixinMember's owner (the trait) val accessed = enteringPickler(mixinMember.accessed) @@ -354,7 +337,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val newFlags = ( (PrivateLocal) - | (mixinMember getFlag MUTABLE | LAZY) + | (mixinMember getFlag MUTABLE) | (if (mixinMember.hasStableFlag) 0 else MUTABLE) ) @@ -499,7 +482,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else if (needsInitFlag(field) && !field.isDeferred) false else return NO_NAME ) - if (field.accessed hasAnnotation TransientAttr) { + if (field.accessedOrSelf hasAnnotation TransientAttr) { if (isNormal) BITMAP_TRANSIENT else BITMAP_CHECKINIT_TRANSIENT } else { @@ -862,17 +845,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def getterBody(getter: Symbol) = { assert(getter.isGetter) - val readValue = - if (getter.isLazy) { - getter.tpe.resultType match { - case ConstantType(c) => Literal(c) - case _ => - val initCall = Apply(SuperSelect(clazz, initializer(getter)), Nil) - val offset = fieldOffset(getter) - if (isUnitGetter(getter)) mkLazyDef(clazz, getter, List(initCall), UNIT, offset) - else mkLazyDef(clazz, getter, List(atPos(getter.pos)(Assign(fieldAccess(getter), initCall))), fieldAccess(getter), offset) - } - } else { + val readValue = { assert(getter.hasFlag(PARAMACCESSOR)) fieldAccess(getter) } @@ -902,7 +875,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (clazz.isTrait || sym.isSuperAccessor) addDefDef(sym) // implement methods mixed in from a supertrait (the symbols were created by mixinTraitMembers) else if (sym.hasFlag(ACCESSOR) && !sym.hasFlag(DEFERRED)) { - assert(sym hasFlag (LAZY | PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/param?!?") + assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/param?!?") // add accessor definitions addDefDef(sym, if (sym.isSetter) setterBody(sym) else getterBody(sym)) @@ -919,7 +892,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (clazz.isTrait) stats1 = stats1.filter { case vd: ValDef => - assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR | LAZY), s"unexpected valdef $vd in trait $clazz") + assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz") false case _ => true } diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index fa7c503213..744b9c8a8e 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -274,10 +274,8 @@ abstract class TailCalls extends Transform { import runDefinitions.{Boolean_or, Boolean_and} tree match { - case ValDef(_, _, _, _) => - if (tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass)) - reporter.error(tree.pos, "lazy vals are not tailcall transformed") - + case dd: DefDef if tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass) => + reporter.error(tree.pos, "lazy vals are not tailcall transformed") super.transform(tree) case dd @ DefDef(_, name, _, vparamss0, _, rhs0) if isEligible(dd) => diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 6ade45c41c..f6c667353f 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -439,9 +439,9 @@ abstract class UnCurry extends InfoTransform super.transform(treeCopy.DefDef(dd, mods, name, tparams, vparamssNoRhs, tpt, rhs)) } } - case ValDef(_, _, _, rhs) => + case ValDef(mods, _, _, rhs) => if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit) - if (!sym.owner.isSourceMethod) + if (!sym.owner.isSourceMethod || mods.isLazy) withNeedLift(needLift = true) { super.transform(tree) } else super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 78e72cf771..df014b5161 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -151,8 +151,12 @@ abstract class Duplicators extends Analyzer { ldef.symbol = newsym debuglog("newsym: " + newsym + " info: " + newsym.info) - case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) => - debuglog("ValDef " + name + " sym.info: " + vdef.symbol.info) + // don't retypecheck val members or local lazy vals -- you'll end up with duplicate symbols because + // entering a valdef results in synthesizing getters etc + // TODO: why retype check any valdefs?? I checked and the rhs is specialized just fine this way + // (and there are no args/type params/... to warrant full type checking?) + case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) || owner.isClass => + debuglog(s"ValDef $name in $owner sym.info: ${vdef.symbol.info}") invalidSyms(vdef.symbol) = vdef val newowner = owner orElse context.owner val newsym = vdef.symbol.cloneSymbol(newowner) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index e0b64a7600..d11417192d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -5,6 +5,7 @@ package scala.tools.nsc package typechecker +import scala.reflect.NameTransformer import symtab.Flags._ import scala.reflect.internal.util.StringOps.ojoin import scala.reflect.internal.util.ListOfNil @@ -116,38 +117,53 @@ trait MethodSynthesis { import NamerErrorGen._ - def enterImplicitWrapper(tree: ClassDef): Unit = { - enterSyntheticSym(ImplicitClassWrapper(tree).derivedTree) - } - // trees are later created by addDerivedTrees (common logic is encapsulated in field/standardAccessors/beanAccessors) + import treeInfo.noFieldFor + + // populate synthetics for this unit with trees that will later be added by the typer + // we get here when entering the symbol for the valdef, so its rhs has not yet been type checked def enterGetterSetter(tree: ValDef): Unit = { + val fieldSym = + if (noFieldFor(tree, owner)) NoSymbol + else owner.newValue(tree.name append NameTransformer.LOCAL_SUFFIX_STRING, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal) + val getter = Getter(tree) val getterSym = getter.createSym - val setterSym = if (getter.needsSetter) Setter(tree).createSym else NoSymbol - - // a lazy field is linked to its lazy accessor (TODO: can we do the same for field -> getter -> setter) - val fieldSym = if (Field.noFieldFor(tree)) NoSymbol else Field(tree).createSym(getterSym) // only one symbol can have `tree.pos`, the others must focus their position // normally the field gets the range position, but if there is none, give it to the getter tree.symbol = fieldSym orElse (getterSym setPos tree.pos) + val namer = namerOf(tree.symbol) + + // the valdef gets the accessor symbol for a lazy val (too much going on in its RHS) + // the fields phase creates the field symbol + if (!tree.mods.isLazy) { + // if there's a field symbol, the getter is considered a synthetic that must be added later + // if there's no field symbol, the ValDef tree receives the getter symbol and thus is not a synthetic + if (fieldSym != NoSymbol) { + context.unit.synthetics(getterSym) = getter.derivedTree(getterSym) + getterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = false) + } else getterSym setInfo namer.valTypeCompleter(tree) + + enterInScope(getterSym) + + if (getter.needsSetter) { + val setter = Setter(tree) + val setterSym = setter.createSym + context.unit.synthetics(setterSym) = setter.derivedTree(setterSym) + setterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = true) + enterInScope(setterSym) + } - val namer = if (fieldSym != NoSymbol) namerOf(fieldSym) else namerOf(getterSym) - - // There's no reliable way to detect all kinds of setters from flags or name!!! - // A BeanSetter's name does not end in `_=` -- it does begin with "set", but so could the getter - // for a regular Scala field... TODO: can we add a flag to distinguish getter/setter accessors? - val getterCompleter = namer.accessorTypeCompleter(tree, isSetter = false) - val setterCompleter = namer.accessorTypeCompleter(tree, isSetter = true) - - getterSym setInfo getterCompleter - setterSym andAlso (_ setInfo setterCompleter) - fieldSym andAlso (_ setInfo namer.valTypeCompleter(tree)) - - enterInScope(getterSym) - setterSym andAlso (enterInScope(_)) - fieldSym andAlso (enterInScope(_)) + // TODO: delay emitting the field to the fields phase (except for private[this] vals, which only get a field and no accessors) + if (fieldSym != NoSymbol) { + fieldSym setInfo namer.valTypeCompleter(tree) + enterInScope(fieldSym) + } + } else { + getterSym setInfo namer.valTypeCompleter(tree) + enterInScope(getterSym) + } deriveBeanAccessors(tree, namer) } @@ -188,242 +204,82 @@ trait MethodSynthesis { sym } - val getterCompleter = namer.beanAccessorTypeCompleter(tree, missingTpt, isSetter = false) + val getterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = false) enterInScope(deriveBeanAccessor(if (hasBeanProperty) "get" else "is") setInfo getterCompleter) if (tree.mods.isMutable) { - val setterCompleter = namer.beanAccessorTypeCompleter(tree, missingTpt, isSetter = true) + val setterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = true) enterInScope(deriveBeanAccessor("set") setInfo setterCompleter) } } } - import AnnotationInfo.{mkFilter => annotationFilter} - def addDerivedTrees(typer: Typer, stat: Tree): List[Tree] = stat match { - case vd @ ValDef(mods, name, tpt, rhs) if deriveAccessors(vd) && !vd.symbol.isModuleVar && !vd.symbol.isJava => - stat.symbol.initialize // needed! - - val getter = Getter(vd) - getter.validate() - val accessors = getter :: (if (getter.needsSetter) Setter(vd) :: Nil else Nil) - (Field(vd) :: accessors).map(_.derivedTree).filter(_ ne EmptyTree) - - case cd @ ClassDef(mods, _, _, _) if mods.isImplicit => - val annotations = stat.symbol.initialize.annotations - // TODO: need to shuffle annotations between wrapper and class. - val wrapper = ImplicitClassWrapper(cd) - val meth = wrapper.derivedSym - context.unit.synthetics get meth match { - case Some(mdef) => - context.unit.synthetics -= meth - meth setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) - cd.symbol setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) - List(cd, mdef) - case _ => - // Shouldn't happen, but let's give ourselves a reasonable error when it does - context.error(cd.pos, s"Internal error: Symbol for synthetic factory method not found among ${context.unit.synthetics.keys.mkString(", ")}") - // Soldier on for the sake of the presentation compiler - List(cd) - } - case _ => - stat :: Nil - } - - - sealed trait Derived { - /** The derived symbol. It is assumed that this symbol already exists and has been - * entered in the parent scope when derivedSym is called - */ - def derivedSym: Symbol - - /** The definition tree of the derived symbol. */ - def derivedTree: Tree + def enterImplicitWrapper(classDef: ClassDef): Unit = { + val methDef = factoryMeth(classDef.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef) + val methSym = assignAndEnterSymbol(methDef) + context.unit.synthetics(methSym) = methDef + methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol, completerOf(methDef).asInstanceOf[LockingTypeCompleter]) } - /** A synthetic method which performs the implicit conversion implied by - * the declaration of an implicit class. - */ - case class ImplicitClassWrapper(tree: ClassDef) extends Derived { - def derivedSym = { - val enclClass = tree.symbol.owner.enclClass - // Only methods will do! Don't want to pick up any stray - // companion objects of the same name. - val result = enclClass.info decl derivedName filter (x => x.isMethod && x.isSynthetic) - if (result == NoSymbol || result.isOverloaded) - context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $derivedName in $enclClass / ${enclClass.info.decls}") - result - } - - def derivedTree = factoryMeth(derivedMods, derivedName, tree) - - def derivedName = tree.name.toTermName - def derivedMods = tree.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC - } - - trait DerivedAccessor extends Derived { + trait DerivedAccessor { def tree: ValDef def derivedName: TermName def derivedFlags: Long + def derivedTree(sym: Symbol): Tree def derivedPos = tree.pos.focus def createSym = createMethod(tree, derivedName, derivedPos, derivedFlags) } case class Getter(tree: ValDef) extends DerivedAccessor { - def derivedName = tree.name - - def derivedSym = - if (tree.mods.isLazy) tree.symbol.lazyAccessor - else if (Field.noFieldFor(tree)) tree.symbol - else tree.symbol.getterIn(tree.symbol.enclClass) - + def derivedName = tree.name def derivedFlags = tree.mods.flags & GetterFlags | ACCESSOR.toLong | ( if (needsSetter) 0 else STABLE ) + def needsSetter = tree.mods.isMutable // implies !lazy - def needsSetter = tree.mods.isMutable // implies !lazy - - override def derivedTree = - if (tree.mods.isLazy) deriveLazyAccessor - else newDefDef(derivedSym, if (Field.noFieldFor(tree)) tree.rhs else Select(This(tree.symbol.enclClass), tree.symbol))(tpt = derivedTpt) - - /** Implements lazy value accessors: - * - for lazy values of type Unit and all lazy fields inside traits, - * the rhs is the initializer itself, because we'll just "compute" the result on every access - * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap) - * - for all other lazy values z the accessor is a block of this form: - * { z = <rhs>; z } where z can be an identifier or a field. - */ - private def deriveLazyAccessor: DefDef = { - val ValDef(_, _, tpt0, rhs0) = tree - val rhs1 = context.unit.transformed.getOrElse(rhs0, rhs0) - val body = - if (tree.symbol.owner.isTrait || Field.noFieldFor(tree)) rhs1 // TODO move tree.symbol.owner.isTrait into noFieldFor - else gen.mkAssignAndReturn(tree.symbol, rhs1) - - derivedSym setPos tree.pos // TODO: can we propagate `tree.pos` to `derivedSym` when the symbol is created? - val ddefRes = DefDef(derivedSym, new ChangeOwnerTraverser(tree.symbol, derivedSym)(body)) - // ValDef will have its position focused whereas DefDef will have original correct rangepos - // ideally positions would be correct at the creation time but lazy vals are really a special case - // here so for the sake of keeping api clean we fix positions manually in LazyValGetter - ddefRes.tpt.setPos(tpt0.pos) - tpt0.setPos(tpt0.pos.focus) - ddefRes - } + override def derivedTree(derivedSym: Symbol) = { + val missingTpt = tree.tpt.isEmpty + val tpt = if (missingTpt) TypeTree() else tree.tpt.duplicate - // TODO: more principled approach -- this is a bit bizarre - private def derivedTpt = { - // For existentials, don't specify a type for the getter, even one derived - // from the symbol! This leads to incompatible existentials for the field and - // the getter. Let the typer do all the work. You might think "why only for - // existentials, why not always," and you would be right, except: a single test - // fails, but it looked like some work to deal with it. Test neg/t0606.scala - // starts compiling (instead of failing like it's supposed to) because the typer - // expects to be able to identify escaping locals in typedDefDef, and fails to - // spot that brand of them. In other words it's an artifact of the implementation. - // - // JZ: ... or we could go back to uniformly using explicit result types in all cases - // if we fix `dropExistential`. More details https://github.com/scala/scala-dev/issues/165 - val getterTp = derivedSym.tpe_*.finalResultType - // Range position errors ensue if we don't duplicate this in some - // circumstances (at least: concrete vals with existential types.) - def inferredTpt = TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) - val tpt = getterTp match { - case _: ExistentialType => inferredTpt - case _ => getterTp.widen match { - case _: ExistentialType => inferredTpt - case _ if tree.mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field - case _ => TypeTree(getterTp) - } - } - tpt setPos tree.tpt.pos.focus - } + val rhs = + if (noFieldFor(tree, owner)) tree.rhs // context.unit.transformed.getOrElse(tree.rhs, tree.rhs) + else Select(This(tree.symbol.enclClass), tree.symbol) - def validate() = { - assert(derivedSym != NoSymbol, tree) - if (derivedSym.isOverloaded) - GetterDefinedTwiceError(derivedSym) + newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = Nil, tpt = tpt) } +// derivedSym setPos tree.pos +// // ValDef will have its position focused whereas DefDef will have original correct rangepos +// // ideally positions would be correct at the creation time but lazy vals are really a special case +// // here so for the sake of keeping api clean we fix positions manually in LazyValGetter +// tpt.setPos(tree.tpt.pos) +// tree.tpt.setPos(tree.tpt.pos.focus) + } case class Setter(tree: ValDef) extends DerivedAccessor { def derivedName = tree.setterName - def derivedSym = tree.symbol.setterIn(tree.symbol.enclClass) def derivedFlags = tree.mods.flags & SetterFlags | ACCESSOR - def derivedTree = - derivedSym.paramss match { - case (setterParam :: Nil) :: _ => - // assert(!derivedSym.isOverloaded, s"Unexpected overloaded setter $derivedSym for ${tree.symbol} in ${tree.symbol.enclClass}") - val rhs = - if (Field.noFieldFor(tree) || derivedSym.isOverloaded) EmptyTree - else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam)) - - DefDef(derivedSym, rhs) - case _ => EmptyTree - } - } - - object Field { - // No field for these vals (either never emitted or eliminated later on): - // - abstract vals have no value we could store (until they become concrete, potentially) - // - lazy vals of type Unit - // - concrete vals in traits don't yield a field here either (their getter's RHS has the initial value) - // Constructors will move the assignment to the constructor, abstracting over the field using the field setter, - // and Fields will add a field to the class that mixes in the trait, implementing the accessors in terms of it - // - [Emitted, later removed during Constructors] a concrete val with a statically known value (ConstantType) - // performs its side effect according to lazy/strict semantics, but doesn't need to store its value - // each access will "evaluate" the RHS (a literal) again - // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer. - // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does. - // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`), - // similarly, the `def field` call-site breaks when you add `|| vd.symbol.owner.isTrait` (detected in test suite) - // as the symbol info is in the process of being created then. - // TODO: harmonize tree & symbol creation - // the middle `&& !owner.isTrait` is needed after `isLazy` because non-unit-typed lazy vals in traits still get a field -- see neg/t5455.scala - def noFieldFor(vd: ValDef) = (vd.mods.isDeferred - || (vd.mods.isLazy && !owner.isTrait && isUnitType(vd.symbol.info)) - || (owner.isTrait && !traitFieldFor(vd))) - - // TODO: never emit any fields in traits -- only use getter for lazy/presuper ones as well - private def traitFieldFor(vd: ValDef): Boolean = vd.mods.hasFlag(PRESUPER | LAZY) - } + def derivedTree(derivedSym: Symbol) = { + val setterParam = nme.syntheticParamName(1) - case class Field(tree: ValDef) extends Derived { - private val isLazy = tree.mods.isLazy - - // If the owner is not a class, this is a lazy val from a method, - // with no associated field. It has an accessor with $lzy appended to its name and - // its flags are set differently. The implicit flag is reset because otherwise - // a local implicit "lazy val x" will create an ambiguity with itself - // via "x$lzy" as can be seen in test #3927. - private val localLazyVal = isLazy && !owner.isClass - private val nameSuffix = - if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING - else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING - - def derivedName = tree.name.append(nameSuffix) - - def createSym(getter: MethodSymbol) = { - val sym = owner.newValue(derivedName, tree.pos, derivedMods.flags) - if (isLazy) sym setLazyAccessor getter - sym - } + // note: tree.tpt may be EmptyTree, which will be a problem when use as the tpt of a parameter + // the completer will patch this up (we can't do this now without completing the field) + val missingTpt = tree.tpt.isEmpty + val tptToPatch = if (missingTpt) TypeTree() else tree.tpt.duplicate - def derivedSym = tree.symbol + val vparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), setterParam, tptToPatch, EmptyTree)) - def derivedMods = - if (!localLazyVal) tree.mods & FieldFlags | PrivateLocal | (if (isLazy) MUTABLE else 0) - else (tree.mods | ARTIFACT | MUTABLE) & ~IMPLICIT + val tpt = TypeTree(UnitTpe) - // TODO: why is this different from the symbol!? - private def derivedModsForTree = tree.mods | PrivateLocal + val rhs = + if (noFieldFor(tree, owner)) EmptyTree + else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam)) - def derivedTree = - if (Field.noFieldFor(tree)) EmptyTree - else if (isLazy) copyValDef(tree)(mods = derivedModsForTree, name = derivedName, rhs = EmptyTree).setPos(tree.pos.focus) - else copyValDef(tree)(mods = derivedModsForTree, name = derivedName) + newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = List(vparams), tpt = tpt) + } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 99c1b6991e..033b6cc0cd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -129,6 +129,7 @@ trait Namers extends MethodSynthesis { !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter !isEnumConstant(vd) // enums can only occur in classes, so only check here + /** Determines whether this field holds an enum constant. * To qualify, the following conditions must be met: * - The field's class has the ENUM flag set @@ -803,86 +804,89 @@ trait Namers extends MethodSynthesis { import AnnotationInfo.{mkFilter => annotationFilter} - def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { sym => - val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter annotationFilter(FieldTargetClass, !tree.mods.isParamAccessor) + def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) - sym setInfo typeSig(tree, annots) + val annotations = classSym.initialize.annotations - validate(sym) + methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def accessorTypeCompleter(tree: ValDef, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - // println(s"triaging for ${sym.debugFlagString} $sym from $valAnnots to $annots") - - // typeSig calls valDefSig (because tree: ValDef) - // sym is an accessor, while tree is the field (which may have the same symbol as the getter, or maybe it's the field) - // TODO: can we make this work? typeSig is called on same tree (valdef) to complete info for field and all its accessors - // reuse work done in valTypeCompleter if we already computed the type signature of the val - // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) -// val valSig = -// if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info -// else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig - - val valSig = typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig - - val sig = accessorSigFromFieldTp(sym, isSetter, valSig) + // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, + // as specified by Field.noFieldFor) + def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { fieldOrGetterSym => val mods = tree.mods - if (mods.annotations.nonEmpty) { - val annotSigs = annotSig(mods.annotations) - - // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis - // (TODO: can we look at symbols earlier?) - if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) - && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) - BeanPropertyAnnotationLimitationError(tree) + val isGetter = fieldOrGetterSym.isMethod + val annots = + if (mods.annotations.isEmpty) Nil + else { + val annotSigs = annotSig(mods.annotations) + if (isGetter) filterAccessorAnnots(annotSigs, tree) // if this is really a getter, retain annots targeting either field/getter + else annotSigs filter annotationFilter(FieldTargetClass, !mods.isParamAccessor) + } - sym setAnnotations (annotSigs filter filterAccessorAnnotations(isSetter)) - } + // must use typeSig, not memberSig (TODO: when do we need to switch namers?) + val sig = typeSig(tree, annots) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig) - validate(sym) + validate(fieldOrGetterSym) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def beanAccessorTypeCompleter(tree: ValDef, missingTpt: Boolean, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - context.unit.synthetics get sym match { + // knowing `isBean`, we could derive `isSetter` from `valDef.name` + def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = mkTypeCompleter(valDef) { accessorSym => + context.unit.synthetics get accessorSym match { case Some(ddef: DefDef) => - // sym is an accessor, while tree is the field (for traits it's actually the getter, and we're completing the setter) + // `accessorSym` is the accessor for which we're completing the info (tree == ddef), + // while `valDef` is the field definition that spawned the accessor + // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol + // reuse work done in valTypeCompleter if we already computed the type signature of the val // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) val valSig = - if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info - else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info + else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig) // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized - if (missingTpt) { // can't look at tree.tpt here because it may have been completed by now + // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`) + // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back... + // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed) + if (missingTpt || accessorSym.isParamAccessor) { if (!isSetter) ddef.tpt setType valSig else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig - else throw new TypeError(tree.pos, s"Internal error: could not complete parameter/return type for $ddef from $sym") + else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym") } + val mods = valDef.mods val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter filterBeanAccessorAnnotations(isSetter) + if (mods.annotations.isEmpty) Nil + else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean) - val sig = typeSig(ddef, annots) + // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types) + // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig) + val sig = + if (isSetter || isBean) typeSig(ddef, annots) + else { + if (annots.nonEmpty) annotate(accessorSym, annots) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + NullaryMethodType(valSig) + } + + accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) - validate(sym) + if (!isBean && accessorSym.isOverloaded) + if (isSetter) ddef.rhs.setType(ErrorType) + else GetterDefinedTwiceError(accessorSym) + + validate(accessorSym) case _ => - throw new TypeError(tree.pos, s"Internal error: no synthetic tree found for bean accessor $sym") + throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym") } - } - // see scala.annotation.meta's package class for more info // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. // The defaults are: @@ -893,24 +897,33 @@ trait Namers extends MethodSynthesis { // // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit? // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter) - private def filterAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) - - private def filterBeanAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) - - - private def accessorSigFromFieldTp(sym: Symbol, isSetter: Boolean, tp: Type): Type = - if (isSetter) MethodType(List(sym.newSyntheticValueParam(tp)), UnitTpe) - else NullaryMethodType(tp) + private def filterAccessorAnnots(annotSigs: List[global.AnnotationInfo], tree: global.ValDef, isSetter: Boolean = false, isBean: Boolean = false): List[AnnotationInfo] = { + val mods = tree.mods + if (!isBean) { + // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis + // (TODO: can we look at symbols earlier?) + if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) + && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) + BeanPropertyAnnotationLimitationError(tree) + } + + def filterAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) + + def filterBeanAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) + + annotSigs filter (if (isBean) filterBeanAccessorAnnotations else filterAccessorAnnotations) + } + def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => val selftpe = typer.typedType(tree).tpe diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 8449260fe6..8034d056d7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -450,9 +450,9 @@ abstract class RefChecks extends Transform { } else if (other.isStable && !member.isStable) { // (1.4) overrideError("needs to be a stable, immutable value") } else if (member.isValue && member.isLazy && - other.isValue && !other.isSourceMethod && !other.isDeferred && !other.isLazy) { + other.isValue && other.hasFlag(STABLE) && !(other.isDeferred || other.isLazy)) { overrideError("cannot override a concrete non-lazy value") - } else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred && // !(other.hasFlag(MODULE) && other.hasFlag(PACKAGE | JAVA)) && other.hasFlag(LAZY) && (!other.isMethod || other.hasFlag(STABLE)) && !other.hasFlag(DEFERRED) + } else if (other.isValue && other.isLazy && member.isValue && !member.isLazy) { overrideError("must be declared lazy to override a concrete lazy value") } else if (other.isDeferred && member.isTermMacro && member.extendedOverriddenSymbols.forall(_.isDeferred)) { // (1.9) @@ -609,7 +609,7 @@ abstract class RefChecks extends Transform { val (missing, rest) = memberList partition (m => m.isDeferred && !ignoreDeferred(m)) // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. - val grouped = missing groupBy (sym => analyzer.underlyingSymbol(sym).name) + val grouped = missing groupBy (_.name.getterName) val missingMethods = grouped.toList flatMap { case (name, syms) => if (syms exists (_.isSetter)) syms filterNot (_.isGetter) @@ -651,15 +651,16 @@ abstract class RefChecks extends Transform { // Give a specific error message for abstract vars based on why it fails: // It could be unimplemented, have only one accessor, or be uninitialized. - if (underlying.isVariable) { - val isMultiple = grouped.getOrElse(underlying.name, Nil).size > 1 + val groupedAccessors = grouped.getOrElse(member.name.getterName, Nil) + val isMultiple = groupedAccessors.size > 1 + if (groupedAccessors.exists(_.isSetter) || (member.isGetter && !isMultiple && member.setterIn(member.owner).exists)) { // If both getter and setter are missing, squelch the setter error. if (member.isSetter && isMultiple) () else undefined( if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" - else analyzer.abstractVarMessage(member) + else "\n(Note that variables need to be initialized to be defined)" ) } else if (underlying.isMethod) { @@ -919,17 +920,11 @@ abstract class RefChecks extends Transform { var index = -1 for (stat <- stats) { index = index + 1 - def enterSym(sym: Symbol) = if (sym.isLocalToBlock) { - currentLevel.scope.enter(sym) - symIndex(sym) = index - } stat match { - case DefDef(_, _, _, _, _, _) if stat.symbol.isLazy => - enterSym(stat.symbol) - case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) | ValDef(_, _, _, _) => - //assert(stat.symbol != NoSymbol, stat);//debug - enterSym(stat.symbol.lazyAccessorOrSelf) + case _ : MemberDef if stat.symbol.isLocalToBlock => + currentLevel.scope.enter(stat.symbol) + symIndex(stat.symbol) = index case _ => } } @@ -1180,10 +1175,10 @@ abstract class RefChecks extends Transform { val tree1 = transform(tree) // important to do before forward reference check if (tree1.symbol.isLazy) tree1 :: Nil else { - val lazySym = tree.symbol.lazyAccessorOrSelf - if (lazySym.isLocalToBlock && index <= currentLevel.maxindex) { + val sym = tree.symbol + if (sym.isLocalToBlock && index <= currentLevel.maxindex) { debuglog("refsym = " + currentLevel.refsym) - reporter.error(currentLevel.refpos, "forward reference extends over definition of " + lazySym) + reporter.error(currentLevel.refpos, "forward reference extends over definition of " + sym) } tree1 :: Nil } @@ -1451,9 +1446,9 @@ abstract class RefChecks extends Transform { ) } - sym.isSourceMethod && + sym.name == nme.apply && + !(sym hasFlag STABLE) && // ??? sym.isCase && - sym.name == nme.apply && isClassTypeAccessible(tree) && !tree.tpe.finalResultType.typeSymbol.primaryConstructor.isLessAccessibleThan(tree.symbol) } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index bee327c760..b66dbf21c0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -97,7 +97,7 @@ trait TypeDiagnostics { /** An explanatory note to be added to error messages * when there's a problem with abstract var defs */ def abstractVarMessage(sym: Symbol): String = - if (underlyingSymbol(sym).isVariable) + if (sym.isSetter || sym.isGetter && sym.setterIn(sym.owner).exists) "\n(Note that variables need to be initialized to be defined)" else "" @@ -140,7 +140,7 @@ trait TypeDiagnostics { * TODO: is it wise to create new symbols simply to generate error message? is this safe in interactive/resident mode? */ def underlyingSymbol(member: Symbol): Symbol = - if (!member.hasAccessorFlag || member.owner.isTrait) member + if (!member.hasAccessorFlag || member.accessed == NoSymbol) member else if (!member.isDeferred) member.accessed else { val getter = if (member.isSetter) member.getterIn(member.owner) else member diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a95ecd360c..3360599c1b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -128,6 +128,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def canTranslateEmptyListToNil = true def missingSelectErrorTree(tree: Tree, qual: Tree, name: Name): Tree = tree + // used to exempt synthetic accessors (i.e. those that are synthesized by the compiler to access a field) + // from skolemization because there's a weird bug that causes spurious type mismatches + // (it seems to have something to do with existential abstraction over values + // https://github.com/scala/scala-dev/issues/165 + // when we're past typer, lazy accessors are synthetic, but before they are user-defined + // to make this hack less hacky, we could rework our flag assignment to allow for + // requiring both the ACCESSOR and the SYNTHETIC bits to trigger the exemption + private def isSyntheticAccessor(sym: Symbol) = sym.isAccessor && (!sym.isLazy || isPastTyper) + // when type checking during erasure, generate erased types in spots that aren't transformed by erasure // (it erases in TypeTrees, but not in, e.g., the type a Function node) def phasedAppliedType(sym: Symbol, args: List[Type]) = { @@ -1159,7 +1168,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper adapt(tree setType restpe, mode, pt, original) case TypeRef(_, ByNameParamClass, arg :: Nil) if mode.inExprMode => // (2) adapt(tree setType arg, mode, pt, original) - case tp if mode.typingExprNotLhs && isExistentialType(tp) => + case tp if mode.typingExprNotLhs && isExistentialType(tp) && !isSyntheticAccessor(context.owner) => adapt(tree setType tp.dealias.skolemizeExistential(context.owner, tree), mode, pt, original) case PolyType(tparams, restpe) if mode.inNone(TAPPmode | PATTERNmode) && !context.inTypeConstructorAllowed => // (3) // assert((mode & HKmode) == 0) //@M a PolyType in HKmode represents an anonymous type function, @@ -1373,13 +1382,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper notAllowed(s"redefinition of $name method. See SIP-15, criterion 4.") else if (stat.symbol != null && stat.symbol.isParamAccessor) notAllowed("additional parameter") - // concrete accessor (getter) in trait corresponds to a field definition (neg/anytrait.scala) - // TODO: only reject accessors that actually give rise to field (e.g., a constant-type val is fine) - else if (!isValueClass && stat.symbol.isAccessor && !stat.symbol.isDeferred) - notAllowed("field definition") checkEphemeralDeep.traverse(rhs) - // for value class or "exotic" vals in traits - // (traits don't receive ValDefs for regular vals until fields phase -- well, except for early initialized/lazy vals) case _: ValDef => notAllowed("field definition") case _: ModuleDef => @@ -1956,11 +1959,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType]) - val body2 = { - val body2 = - if (isPastTyper || reporter.hasErrors) body1 - else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _)) - val primaryCtor = treeInfo.firstConstructor(body2) + val bodyWithPrimaryCtor = { + val primaryCtor = treeInfo.firstConstructor(body1) val primaryCtor1 = primaryCtor match { case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) => val argss = superArgs(parents1.head) getOrElse Nil @@ -1969,10 +1969,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos case _ => primaryCtor } - body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } + body1 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } } - val body3 = typedStats(body2, templ.symbol) + val body3 = typedStats(bodyWithPrimaryCtor, templ.symbol) if (clazz.info.firstParent.typeSymbol == AnyValClass) validateDerivedValueClass(clazz, body3) @@ -2436,13 +2436,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => } } - val stats1 = if (isPastTyper) block.stats else - block.stats.flatMap { - case vd@ValDef(_, _, _, _) if vd.symbol.isLazy => - namer.addDerivedTrees(Typer.this, vd) - case stat => stat::Nil - } - val stats2 = typedStats(stats1, context.owner, warnPure = false) + val statsTyped = typedStats(block.stats, context.owner, warnPure = false) val expr1 = typed(block.expr, mode &~ (FUNmode | QUALmode), pt) // sanity check block for unintended expr placement @@ -2456,18 +2450,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def checkPure(t: Tree, supple: Boolean): Unit = if (treeInfo.isPureExprForWarningPurposes(t)) { val msg = "a pure expression does nothing in statement position" - val parens = if (stats2.length + count > 1) "multiline expressions might require enclosing parentheses" else "" + val parens = if (statsTyped.length + count > 1) "multiline expressions might require enclosing parentheses" else "" val discard = if (adapted) "; a value can be silently discarded when Unit is expected" else "" val text = if (supple) s"${parens}${discard}" else if (!parens.isEmpty) s"${msg}; ${parens}" else msg context.warning(t.pos, text) } - stats2.foreach(checkPure(_, supple = false)) + statsTyped.foreach(checkPure(_, supple = false)) if (result0.nonEmpty) checkPure(result0, supple = true) } - treeCopy.Block(block, stats2, expr1) + treeCopy.Block(block, statsTyped, expr1) .setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst) } finally { // enable escaping privates checking from the outside and recycle @@ -3171,6 +3165,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case (ClassDef(cmods, cname, _, _), DefDef(dmods, dname, _, _, _, _)) => cmods.isImplicit && dmods.isImplicit && cname.toTermName == dname + // ValDef and Accessor + case (ValDef(_, cname, _, _), DefDef(_, dname, _, _, _, _)) => + cname.getterName == dname.getterName + case _ => false } @@ -4455,8 +4453,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def narrowRhs(tp: Type) = { val sym = context.tree.symbol context.tree match { case ValDef(mods, _, _, Apply(Select(`tree`, _), _)) if !mods.isMutable && sym != null && sym != NoSymbol => - val sym1 = if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) - else sym.lazyAccessorOrSelf + val sym1 = + if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) + else sym val pre = if (sym1.owner.isClass) sym1.owner.thisType else NoPrefix intersectionType(List(tp, singleType(pre, sym1))) case _ => tp |