diff options
Diffstat (limited to 'src/compiler/scala')
17 files changed, 1004 insertions, 411 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index d4c2896c5c..c2d92ce7f9 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -476,10 +476,22 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = None } with TailCalls + // phaseName = "fields" + object fields extends { + val global: Global.this.type = Global.this + // after refchecks, so it doesn't have to make weird exceptions for synthetic accessors + // after uncurry as it produces more work for the fields phase as well as being confused by it: + // - sam expansion synthesizes classes, which may need trait fields mixed in + // - the fields phase adds synthetic abstract methods to traits that should not disqualify them from being a SAM type + // before erasure: correct signatures & bridges for accessors + val runsAfter = List("uncurry") + val runsRightAfter = None + } with Fields + // phaseName = "explicitouter" object explicitOuter extends { val global: Global.this.type = Global.this - val runsAfter = List("tailcalls") + val runsAfter = List("fields") val runsRightAfter = None } with ExplicitOuter @@ -595,7 +607,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) * This implementation creates a description map at the same time. */ protected def computeInternalPhases(): Unit = { - // Note: this fits -Xshow-phases into 80 column width, which it is + // Note: this fits -Xshow-phases into 80 column width, which is // desirable to preserve. val phs = List( syntaxAnalyzer -> "parse source into ASTs, perform simple desugaring", @@ -608,6 +620,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) pickler -> "serialize symbol tables", refChecks -> "reference/override checking, translate nested objects", uncurry -> "uncurry, translate function values to anonymous classes", + fields -> "synthesize accessors and fields", tailCalls -> "replace tail calls by jumps", specializeTypes -> "@specialized-driven class and method specialization", explicitOuter -> "this refs to outer pointers", @@ -1239,6 +1252,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val picklerPhase = phaseNamed("pickler") val refchecksPhase = phaseNamed("refchecks") val uncurryPhase = phaseNamed("uncurry") + // val fieldsPhase = phaseNamed("fields") // val tailcallsPhase = phaseNamed("tailcalls") val specializePhase = phaseNamed("specialize") val explicitouterPhase = phaseNamed("explicitouter") diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index bb695500cc..5dddf30c96 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -290,6 +290,16 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { } + // the result type of a function or corresponding SAM type + private def functionResultType(tp: Type): Type = { + val dealiased = tp.dealiasWiden + if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.last + else samOf(tp) match { + case samSym if samSym.exists => tp.memberInfo(samSym).resultType.deconst + case _ => NoType + } + } + /** * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R] * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index d779490ba8..27a4cbd134 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -164,7 +164,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None - else if (sym.isMethod) { + else if (sym.isMethod && !sym.isGetter) { if (doesNotExist(sym)) None else Some(sym) } else enclosingMethod(nextEnclosing(sym)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 383347a0d3..836893a98b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -549,7 +549,10 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { if (classSym.isEffectivelyFinal) None else { // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an - // empty parameter list in later phases and would therefore be picked as SAM. + // empty parameter list in uncurry and would therefore be picked as SAM. + // Similarly, the fields phases adds abstract trait setters, which should not be considered + // abstract for SAMs (they do disqualify the SAM from LMF treatment, + // but an anonymous subclasss can be spun up by scalac after making just the single abstract method concrete) val samSym = exitingPickler(definitions.samOf(classSym.tpe)) if (samSym == NoSymbol) None else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor) @@ -724,7 +727,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym)) && !sym.enclClass.isTrait && !sym.isClassConstructor - && !sym.isMutable // lazy vals and vars both + && (!sym.isMutable || nme.isTraitSetterName(sym.name)) // lazy vals and vars and their setters cannot be final, but trait setters are ) // Primitives are "abstract final" to prohibit instantiation diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala index 9a8eca152f..104e2e8c93 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -50,12 +50,6 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => } } - private def mkAssign(clazz: Symbol, assignSym: Symbol, rhs: Tree): Tree = { - val qual = Select(This(clazz), assignSym) - if (assignSym.isSetter) Apply(qual, List(rhs)) - else Assign(qual, rhs) - } - /** Add calls to supermixin constructors * `super[mix].$init$()` * to tree, which is assumed to be the body of a constructor of class clazz. diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 971a55f763..ec8dc68834 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -13,7 +13,7 @@ import symtab.Flags._ /** This phase converts classes with parameters into Java-like classes with * fields, which are assigned to from constructors. */ -abstract class Constructors extends Statics with Transform with ast.TreeDSL { +abstract class Constructors extends Statics with Transform with TypingTransformers with ast.TreeDSL { import global._ import definitions._ @@ -26,7 +26,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { private val guardedCtorStats: mutable.Map[Symbol, List[Tree]] = perRunCaches.newMap[Symbol, List[Tree]]() private val ctorParams: mutable.Map[Symbol, List[Symbol]] = perRunCaches.newMap[Symbol, List[Symbol]]() - class ConstructorTransformer(unit: CompilationUnit) extends Transformer { + class ConstructorTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { /* * Inspect for obvious out-of-order initialization; concrete, eager vals or vars, declared in this class, * for which a reference to the member precedes its definition. @@ -80,7 +80,10 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { else { checkUninitializedReads(cd) val tplTransformer = new TemplateTransformer(unit, impl0) - treeCopy.ClassDef(cd, mods0, name0, tparams0, tplTransformer.transformed) + tplTransformer.localTyper = this.localTyper + tplTransformer.atOwner(impl0, cd.symbol) { + treeCopy.ClassDef(cd, mods0, name0, tparams0, tplTransformer.transformed) + } } case _ => super.transform(tree) @@ -442,13 +445,14 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } // GuardianOfCtorStmts private class TemplateTransformer(val unit: CompilationUnit, val impl: Template) - extends StaticsTransformer + extends TypingTransformer(unit) + with StaticsTransformer with DelayedInitHelper with OmittablesHelper - with GuardianOfCtorStmts { + with GuardianOfCtorStmts + { val clazz = impl.symbol.owner // the transformed class - val localTyper = typer.atOwner(impl, clazz) val isDelayedInitSubclass = clazz isSubClass DelayedInitClass @@ -544,12 +548,15 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { else transform(tree.changeOwner(oldOwner -> newOwner)) } - // Create an assignment to class field `to` with rhs `from` - def mkAssign(to: Symbol, from: Tree): Tree = - localTyper.typedPos(to.pos) { - Assign(Select(This(clazz), to), from) + // Assign `rhs` to class field / trait setter `assignSym` + def mkAssign(assignSym: Symbol, rhs: Tree): Tree = + localTyper.typedPos(assignSym.pos) { + val qual = Select(This(clazz), assignSym) + if (assignSym.isSetter) Apply(qual, List(rhs)) + else Assign(qual, rhs) } + // Create code to copy parameter to parameter accessor field. // If parameter is $outer, check that it is not null so that we NPE // here instead of at some unknown future $outer access. @@ -565,9 +572,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } } - // Constant typed vals are not memoized. - def memoizeValue(sym: Symbol) = !sym.info.resultType.isInstanceOf[ConstantType] - /** Triage definitions and statements in this template into the following categories. * The primary constructor is treated separately, as it is assembled in part from these pieces. * @@ -577,84 +581,113 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * - `constrStats`: statements that go into the constructor after and including the superclass constructor call * - `classInitStats`: statements that go into the class initializer */ - def triageStats = { - val defBuf, auxConstructorBuf, constrPrefixBuf, constrStatBuf, classInitStatBuf = new mutable.ListBuffer[Tree] - - // The early initialized field definitions of the class (these are the class members) - val presupers = treeInfo.preSuperFields(stats) - - // generate code to copy pre-initialized fields - for (stat <- primaryConstrBody.stats) { - constrStatBuf += stat - stat match { - case ValDef(mods, name, _, _) if mods.hasFlag(PRESUPER) => - // stat is the constructor-local definition of the field value - val fields = presupers filter (_.getterName == name) - assert(fields.length == 1, s"expected exactly one field by name $name in $presupers of $clazz's early initializers") - val to = fields.head.symbol - - if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol)) - case _ => + class Triage { + private val defBuf, auxConstructorBuf, constrPrefixBuf, constrStatBuf, classInitStatBuf = new mutable.ListBuffer[Tree] + + triage() + + val defs = defBuf.toList + val auxConstructors = auxConstructorBuf.toList + val constructorPrefix = constrPrefixBuf.toList + val constructorStats = constrStatBuf.toList + val classInitStats = classInitStatBuf.toList + + private def triage() = { + // Constant typed vals are not memoized. + def memoizeValue(sym: Symbol) = !sym.info.resultType.isInstanceOf[ConstantType] + + // The early initialized field definitions of the class (these are the class members) + val presupers = treeInfo.preSuperFields(stats) + + // generate code to copy pre-initialized fields + for (stat <- primaryConstrBody.stats) { + constrStatBuf += stat + stat match { + case ValDef(mods, name, _, _) if mods.hasFlag(PRESUPER) => // TODO trait presupers + // stat is the constructor-local definition of the field value + val fields = presupers filter (_.getterName == name) + assert(fields.length == 1, s"expected exactly one field by name $name in $presupers of $clazz's early initializers") + val to = fields.head.symbol + + if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol)) + case _ => + } } - } - for (stat <- stats) { - val statSym = stat.symbol - - // Move the RHS of a ValDef to the appropriate part of the ctor. - // If the val is an early initialized or a parameter accessor, - // it goes before the superclass constructor call, otherwise it goes after. - // A lazy val's effect is not moved to the constructor, as it is delayed. - // Returns `true` when a `ValDef` is needed. - def moveEffectToCtor(mods: Modifiers, rhs: Tree, assignSym: Symbol): Unit = { - val initializingRhs = - if ((assignSym eq NoSymbol) || statSym.isLazy) EmptyTree // not memoized, or effect delayed (for lazy val) - else if (!mods.hasStaticFlag) intoConstructor(statSym, primaryConstr.symbol)(rhs) - else rhs - - if (initializingRhs ne EmptyTree) { - val initPhase = - if (mods hasFlag STATIC) classInitStatBuf - else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf - else constrStatBuf - - initPhase += mkAssign(assignSym, initializingRhs) + val primaryConstrSym = primaryConstr.symbol + + for (stat <- stats) { + val statSym = stat.symbol + + // Move the RHS of a ValDef to the appropriate part of the ctor. + // If the val is an early initialized or a parameter accessor, + // it goes before the superclass constructor call, otherwise it goes after. + // A lazy val's effect is not moved to the constructor, as it is delayed. + // Returns `true` when a `ValDef` is needed. + def moveEffectToCtor(mods: Modifiers, rhs: Tree, assignSym: Symbol): Unit = { + val initializingRhs = + if ((assignSym eq NoSymbol) || statSym.isLazy) EmptyTree // not memoized, or effect delayed (for lazy val) + else if (!mods.hasStaticFlag) intoConstructor(statSym, primaryConstrSym)(rhs) + else rhs + + if (initializingRhs ne EmptyTree) { + val initPhase = + if (mods hasFlag STATIC) classInitStatBuf + else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf + else constrStatBuf + + initPhase += mkAssign(assignSym, initializingRhs) + } } - } - stat match { - // recurse on class definition, store in defBuf - case _: ClassDef if !stat.symbol.isInterface => defBuf += new ConstructorTransformer(unit).transform(stat) - - // Triage methods -- they all end up in the template -- - // regular ones go to `defBuf`, secondary contructors go to `auxConstructorBuf`. - // The primary constructor is dealt with separately (we're massaging it here). - case _: DefDef if statSym.isPrimaryConstructor || statSym.isMixinConstructor => () - case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat - case _: DefDef => defBuf += stat - - // If a val needs a field, an empty valdef goes into the template. - // Except for lazy and ConstantTyped vals, the field is initialized by an assignment in: - // - the class initializer (static), - // - the constructor, before the super call (early initialized or a parameter accessor), - // - the constructor, after the super call (regular val). - case ValDef(mods, _, _, rhs) => - if (rhs ne EmptyTree) { - val emitField = memoizeValue(statSym) - moveEffectToCtor(mods, rhs, if (emitField) statSym else NoSymbol) - if (emitField) defBuf += deriveValDef(stat)(_ => EmptyTree) - } else defBuf += stat - - // all other statements go into the constructor - case _ => constrStatBuf += intoConstructor(impl.symbol, primaryConstr.symbol)(stat) + stat match { + // recurse on class definition, store in defBuf + case _: ClassDef if !statSym.isInterface => + defBuf += new ConstructorTransformer(unit).transform(stat) + + // primary constructor is already tracked as `primaryConstr` + // non-primary constructors go to auxConstructorBuf + // mixin constructors are suppressed (!?!?) + case _: DefDef if statSym.isConstructor => + if ((statSym ne primaryConstrSym) && !statSym.isMixinConstructor) auxConstructorBuf += stat + + // If a val needs a field, an empty valdef goes into the template. + // Except for lazy and ConstantTyped vals, the field is initialized by an assignment in: + // - the class initializer (static), + // - the constructor, before the super call (early initialized or a parameter accessor), + // - the constructor, after the super call (regular val). + case vd: ValDef => + if (vd.rhs eq EmptyTree) { defBuf += vd } + else { + val emitField = memoizeValue(statSym) + + if (emitField) { + moveEffectToCtor(vd.mods, vd.rhs, statSym) + defBuf += deriveValDef(stat)(_ => EmptyTree) + } + } + + case dd: DefDef => + // either move the RHS to ctor (for getter of stored field) or just drop it (for corresponding setter) + def shouldMoveRHS = + clazz.isTrait && statSym.isAccessor && !statSym.isLazy && (statSym.isSetter || memoizeValue(statSym)) + + if ((dd.rhs eq EmptyTree) || !shouldMoveRHS) { defBuf += dd } + else { + if (statSym.isGetter) moveEffectToCtor(dd.mods, dd.rhs, statSym.asTerm.referenced orElse statSym.setterIn(clazz)) + defBuf += deriveDefDef(stat)(_ => EmptyTree) + } + + // all other statements go into the constructor + case _ => + constrStatBuf += intoConstructor(impl.symbol, primaryConstrSym)(stat) + } } } - - (defBuf.toList, auxConstructorBuf.toList, constrPrefixBuf.toList, constrStatBuf.toList, classInitStatBuf.toList) } def transformed = { - val (defs, auxConstructors, constructorPrefix, constructorStats, classInitStats) = triageStats + val triage = new Triage; import triage._ // omit unused outers val omittableAccessor: Set[Symbol] = diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index db8e203c1c..289ac0cc02 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1156,6 +1156,8 @@ abstract class Erasure extends AddInterfaces treeCopy.ArrayValue( tree1, elemtpt setType specialScalaErasure.applyInArray(elemtpt.tpe), trees map transform).clearType() case DefDef(_, _, _, _, tpt, _) => + fields.dropFieldAnnotationsFromGetter(tree.symbol) // TODO: move this in some post-processing transform in the fields phase? + try super.transform(tree1).clearType() finally tpt setType specialErasure(tree1.symbol)(tree1.symbol.tpe).resultType case ApplyDynamic(qual, Literal(Constant(boostrapMethodRef: Symbol)) :: _) => diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala new file mode 100644 index 0000000000..0dd7b1fee0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -0,0 +1,449 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author + */ + +package scala.tools.nsc +package transform + +import scala.annotation.tailrec +import symtab.Flags._ + + +/** Synthesize accessors and field for each (strict) val owned by a trait. + * + * For traits: + * + * - Namers translates a definition `val x = rhs` into a getter `def x = rhs` -- no underlying field is created. + * - This phase synthesizes accessors and fields for any vals mixed into a non-trait class. + * - Constructors will move the rhs to an assignment in the template body. + * and those statements then move to the template into the constructor, + * which means it will initialize the fields defined in this template (and execute the corresponding side effects). + * We need to maintain the connection between getter and rhs until after specialization so that it can duplicate vals. + * + * Runs before erasure (to get bridges), and thus before lambdalift/flatten, so that nested functions/definitions must be considered. + * We run after uncurry because it can introduce subclasses of traits with fields (SAMs with vals). + * Lambdalift also introduces new fields (paramaccessors for captured vals), but runs too late in the pipeline + * (mixins still synthesizes implementations for accessors that need to be mixed into subclasses of local traits that capture). + * + * In the future, would like to get closer to dotty, which lifts a val's RHS (a similar thing is done for template-level statements) + * to a method `$_initialize_$1$x` instead of a block, which is used in the constructor to initialize the val. + * This makes for a nice unification of strict and lazy vals, in that the RHS is lifted to a method for both, + * with the corresponding compute method called at the appropriate time.) + * + * This only reduces the required number of methods per field declaration in traits, + * if we encode the name (and place in initialisation order) of the field + * in the name of its initializing method, to allow separate compilation. + * (The name mangling must include ordering, and thus complicate incremental compilation: + * ideally, we'd avoid renumbering unchanged methods, but that would result in + * different bytecode between clean recompiles and incremental ones). + * + * In the even longer term (Scala 3?), I agree with @DarkDimius that it would make sense + * to hide the difference between strict and lazy vals. All vals are lazy, + * but the memoization overhead is removed when we statically know they are forced during initialiation. + * We could still expose the low-level field semantics through `private[this] val`s. + * + * In any case, the current behavior of overriding vals is pretty surprising. + * An overridden val's side-effect is still performed. + * The only change due to overriding is that its value is never written to the field + * (the overridden val's value is, of course, stored in the field in addition to its side-effect being performed). + */ +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" + + protected def newTransformer(unit: CompilationUnit): Transformer = new FieldsTransformer(unit) + override def transformInfo(sym: Symbol, tp: Type): Type = + if (sym.isJavaDefined || sym.isPackageClass || !sym.isClass) tp + else synthFieldsAndAccessors(tp) + + // we leave lazy vars/accessors and early-init vals alone for now + private def excludedAccessorOrFieldByFlags(statSym: Symbol): Boolean = statSym hasFlag LAZY | PRESUPER + + // used for internal communication between info and tree transform of this phase -- not pickled, not in initialflags + // TODO: reuse MIXEDIN for NEEDS_TREES? + override def phaseNewFlags: Long = NEEDS_TREES | OVERRIDDEN_TRAIT_SETTER + + private final val OVERRIDDEN_TRAIT_SETTER = TRANS_FLAG + + final val TRAIT_SETTER_FLAGS = NEEDS_TREES | DEFERRED | ProtectedLocal + + private def accessorImplementedInSubclass(accessor: Symbol) = + (accessor hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS) && (accessor hasFlag (ACCESSOR)) + + private def concreteOrSynthImpl(sym: Symbol): Boolean = !(sym hasFlag DEFERRED) || (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS) + + private def synthesizeImplInSubclasses(accessor: Symbol): Unit = + accessor setFlag lateDEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS + + private def setClonedTraitSetterFlags(clazz: Symbol, correspondingGetter: Symbol, cloneInSubclass: Symbol): Unit = { + val overridden = isOverriddenAccessor(correspondingGetter, clazz) + if (overridden) cloneInSubclass setFlag OVERRIDDEN_TRAIT_SETTER + else if (correspondingGetter.isEffectivelyFinal) cloneInSubclass setFlag FINAL + } + + // TODO: add MIXEDIN (see e.g., `accessed` on `Symbol`) + private def setMixedinAccessorFlags(orig: Symbol, cloneInSubclass: Symbol): Unit = + cloneInSubclass setFlag OVERRIDE | NEEDS_TREES resetFlag DEFERRED | lateDEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS + + private def setFieldFlags(accessor: Symbol, fieldInSubclass: TermSymbol): Unit = + fieldInSubclass setFlag (NEEDS_TREES | + PrivateLocal + | (accessor getFlag MUTABLE | LAZY) + | (if (accessor hasFlag STABLE) 0 else MUTABLE) + ) + + + def checkAndClearOverridden(setter: Symbol) = checkAndClear(OVERRIDDEN_TRAIT_SETTER)(setter) + def checkAndClearNeedsTrees(setter: Symbol) = checkAndClear(NEEDS_TREES)(setter) + def checkAndClear(flag: Long)(sym: Symbol) = + sym.hasFlag(flag) match { + case overridden => + sym resetFlag flag + overridden + } + + + 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) + } + + + def matchingAccessor(pre: Type, member: Symbol, clazz: Symbol) = { + val res = member.matchingSymbol(clazz, pre) filter (sym => (sym hasFlag ACCESSOR) && concreteOrSynthImpl(sym)) + // 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 + } + + + class FieldMemoization(accessorOrField: Symbol, site: Symbol) { + val tp = fieldTypeOfAccessorIn(accessorOrField, site.thisType) + // not stored, no side-effect + val pureConstant = tp.isInstanceOf[ConstantType] + + // if !stored, may still have a side-effect + // (currently not distinguished -- used to think we could drop unit-typed vals, + // but the memory model cares about writes to unit-typed fields) + val stored = !pureConstant // || isUnitType(tp)) + } + + 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) + + // TODO: is there a more elegant way? + def fieldTypeOfAccessorIn(accessor: Symbol, pre: Type) = + 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) + + // drop field-targeting annotations from getters + // (in traits, getters must also hold annotations that target the underlying field, + // because the latter won't be created until the trait is mixed into a class) + // TODO do bean getters need special treatment to suppress field-targeting annotations in traits? + def dropFieldAnnotationsFromGetter(sym: Symbol) = + if (sym.isGetter && sym.owner.isTrait) { + sym setAnnotations (sym.annotations filter AnnotationInfo.mkFilter(GetterTargetClass, defaultRetention = false)) + } + + private object synthFieldsAndAccessors extends TypeMap { + 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) + val setterFlags = (getter.flags & ~(STABLE | PrivateLocal | OVERRIDE | IMPLICIT | FINAL)) | MUTABLE | ACCESSOR | TRAIT_SETTER_FLAGS + 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") + + getter.asTerm.referenced = setter + + setter setInfo MethodType(List(setter.newSyntheticValueParam(fieldTp)), UnitTpe) + setter + } + + 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) + case tp@ClassInfoType(parents, decls, clazz) if clazz.isTrait => + // setters for trait vars or module accessor + val newDecls = collection.mutable.ListBuffer[Symbol]() + val origDecls = decls.toList + + // strict, memoized accessors will receive an implementation in first real class to extend this trait + origDecls.foreach { member => + if (member hasFlag ACCESSOR) { + val fieldMemoization = fieldMemoizationIn(member, clazz) + // check flags before calling makeNotPrivate + val accessorUnderConsideration = !(member hasFlag (DEFERRED | LAZY)) + + // destructively mangle accessor's name (which may cause rehashing of decls), also sets flags + if (member hasFlag PRIVATE) member 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 + // TODO: encapsulate as makeNotProtected, similar to makeNotPrivate (also do moduleClass, e.g.) + if (member hasFlag PROTECTED) member setFlag notPROTECTED + + // 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) + + // derive trait setter after calling makeNotPrivate (so that names are mangled consistently) + if (accessorUnderConsideration && fieldMemoization.stored) { + synthesizeImplInSubclasses(member) + + if (member hasFlag STABLE) // TODO: check isGetter? + newDecls += newTraitSetter(member, clazz) + } + } + } + + if (newDecls nonEmpty) { + val allDecls = newScope + origDecls foreach allDecls.enter + newDecls foreach allDecls.enter + ClassInfoType(parents, allDecls, 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) + + val membersNeedingSynthesis = clazz.mixinClasses.flatMap { mixin => + // afterOwnPhase, so traits receive trait setters for vals + afterOwnPhase {mixin.info}.decls.toList.filter(accessorImplementedInSubclass) + } + +// println(s"mixing in for $clazz: $membersNeedingSynthesis 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).resultType) // !existingGetter.isDeferred && -- see (3) + } + + // mixin field accessors -- + // invariant: (accessorsMaybeNeedingImpl, mixedInAccessorAndFields).zipped.forall(case (acc, clone :: _) => `clone` is clone of `acc` case _ => true) + val synthAccessorAndFields = membersNeedingSynthesis map { member => + def cloneAccessor() = { + val clonedAccessor = (member cloneSymbol clazz) setPos clazz.pos + setMixedinAccessorFlags(member, clonedAccessor) + + if (clonedAccessor.isGetter) + clonedAccessor setAnnotations (clonedAccessor.annotations filter AnnotationInfo.mkFilter(GetterTargetClass, defaultRetention = false)) + + // 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 member) 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 + if (nme.isTraitSetterName(member.name)) { + val getter = member.getterIn(member.owner) + val clone = cloneAccessor() + + setClonedTraitSetterFlags(clazz, getter, clone) + // 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(member) || isOverriddenAccessor(member, clazz)) Nil + 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) + + setFieldFlags(member, field) + + // 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 (member.annotations filter AnnotationInfo.mkFilter(FieldTargetClass, defaultRetention = true)) + + 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).stored + + val newDecls = + if (synthAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) + else { + // must not alter `decls` directly + val newDecls = newScope + val enter = newDecls enter (_: Symbol) + val enterAll = (_: List[Symbol]) foreach enter + + oldDecls foreach { d => if (!omittableField(d)) enter(d) } + synthAccessorAndFields foreach enterAll + + newDecls + } + + // println(s"new decls: $newDecls") + + if (newDecls eq oldDecls) tp + else ClassInfoType(parents, newDecls, clazz) + + case tp => mapOver(tp) + } + } + + + + 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)) + + def mkAccessor(accessor: Symbol)(body: Tree) = localTyper.typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + + 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(exprOwner: Symbol): List[ValOrDefDef] = { + if (exprOwner.isLocalDummy) { + val clazz = exprOwner.owner + def fieldAccess(accessor: Symbol): Option[Tree] = { + val fieldName = accessor.localName + val field = clazz.info.decl(fieldName) + // The `None` result denotes an error, but we defer to refchecks to report it. + // This is the result of overriding a val with a def, so that no field is found in the subclass. + if (field.exists) Some(Select(This(clazz), field)) + else None + } + + def getterBody(getter: Symbol): Option[Tree] = { + val fieldMemoization = fieldMemoizationIn(getter, clazz) + if (fieldMemoization.pureConstant) Some(gen.mkAttributedQualifier(fieldMemoization.tp)) // TODO: drop when we no longer care about producing identical bytecode + else fieldAccess(getter) + } + + // println(s"accessorsAndFieldsNeedingTrees for $templateSym: $accessorsAndFieldsNeedingTrees") + def setterBody(setter: Symbol): Option[Tree] = { + // trait setter in trait + if (clazz.isTrait) Some(EmptyTree) + // trait setter for overridden val in class + else if (checkAndClearOverridden(setter)) Some(mkTypedUnit(setter.pos)) + // trait val/var setter mixed into class + else fieldAccess(setter) map (fieldSel => Assign(fieldSel, Ident(setter.firstParam))) + } + + + clazz.info.decls.toList.filter(checkAndClearNeedsTrees) flatMap { + case setter if setter.isSetter => setterBody(setter) map mkAccessor(setter) + case getter if getter.isAccessor => 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) + case _ => None + } + } else { +// println(s"$exprOwner : ${exprOwner.info} --> ${exprOwner.info.decls}") + Nil + } + } + + def rhsAtOwner(stat: ValOrDefDef, newOwner: Symbol): Tree = + atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner))) + + private def transformStat(exprOwner: Symbol)(stat: Tree): List[Tree] = { + val clazz = currentOwner + val statSym = stat.symbol + + // println(s"transformStat $statSym in ${exprOwner.ownerChain}") + // currentRun.trackerFactory.snapshot() + + /* + For traits, the getter has the val's RHS, which is already constant-folded. There is no valdef. + For classes, we still have the classic scheme of private[this] valdef + getter & setter that read/assign to the field. + + There are two axes: (1) is there a side-effect to the val (2) does the val need storage? + For a ConstantType, both answers are "no". (For a unit-typed field, there's a side-effect, but no storage needed.) + + All others (getter for trait field, valdef for class field) have their rhs moved to an initialization statement. + Trait accessors for stored fields are made abstract (there can be no field in a trait). + (In some future version, accessors for non-stored, but effectful fields, + would receive a constant rhs, as the effect is performed by the initialization statement. + We could do this for unit-typed fields, but have chosen not to for backwards compatibility.) + */ + stat match { + // TODO: consolidate with ValDef case + case stat@DefDef(_, _, _, _, _, rhs) if (statSym hasFlag ACCESSOR) && !excludedAccessorOrFieldByFlags(statSym) => + /* TODO: defer replacing ConstantTyped tree by the corresponding constant until erasure + (until then, trees should not be constant-folded -- only their type tracks the resulting constant) + TODO: also remove ACCESSOR flag since there won't be an underlying field to access? + */ + def statInlinedConstantRhs = + if (clazz.isTrait) stat // we've already done this for traits.. the asymmetry will be solved by the above todo + else deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) + + if (rhs ne EmptyTree) { + val fieldMemoization = fieldMemoizationIn(statSym, clazz) + + // if we decide to have non-stored fields with initialization effects, the stat's RHS should be replaced by unit + // if (!fieldMemoization.stored) deriveUnitDef(stat) else stat + + if (fieldMemoization.pureConstant) statInlinedConstantRhs :: Nil + else super.transform(stat) :: Nil + } else { + stat :: Nil + } + + case stat@ValDef(mods, _, _, rhs) if !excludedAccessorOrFieldByFlags(statSym) => + if (rhs ne EmptyTree) { + val fieldMemoization = fieldMemoizationIn(statSym, clazz) + + // drop the val for (a) constant (pure & not-stored) and (b) not-stored (but still effectful) fields + if (fieldMemoization.pureConstant) Nil // (a) + else super.transform(stat) :: Nil // if (fieldMemoization.stored) + // else rhsAtOwner(transformStat, exprOwner) :: Nil // (b) -- not used currently + } else { + stat :: Nil + } + + + case tree => List( + if (exprOwner != currentOwner && tree.isTerm) atOwner(exprOwner)(super.transform(tree)) + else super.transform(tree) + ) + } + } + + // TODO flatMapConserve or something like it + // TODO use thicket encoding of multi-tree transformStat? + // if (!currentOwner.isClass || currentOwner.isPackageClass || currentOwner.isInterface) stats flatMap transformStat(exprOwner) // for the ModuleDef case, the only top-level case in that method + // else + override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = + afterOwnPhase { + fieldsAndAccessors(exprOwner) ++ (stats flatMap transformStat(exprOwner)) + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index b5084cffe1..d98daf0ffb 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -45,8 +45,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * methods in the impl class (because they can have arbitrary initializers) */ private def isImplementedStatically(sym: Symbol) = ( - sym.isMethod - && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED)) + sym.isMethod + && notDeferredOrLate(sym) && sym.owner.isTrait && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) @@ -109,16 +109,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // --------- type transformation ----------------------------------------------- - def isConcreteAccessor(member: Symbol) = - member.hasAccessorFlag && (!member.isDeferred || (member hasFlag lateDEFERRED)) + private def notDeferredOrLate(sym: Symbol) = !sym.hasFlag(DEFERRED) || sym.hasFlag(lateDEFERRED) /** Is member overridden (either directly or via a bridge) in base class sequence `bcs`? */ def isOverriddenAccessor(member: Symbol, bcs: List[Symbol]): Boolean = beforeOwnPhase { def hasOverridingAccessor(clazz: Symbol) = { clazz.info.nonPrivateDecl(member.name).alternatives.exists( sym => - isConcreteAccessor(sym) && + sym.hasFlag(ACCESSOR) && !sym.hasFlag(MIXEDIN) && + notDeferredOrLate(sym) && matchesType(sym.tpe, member.tpe, alwaysMatchSimple = true)) } ( bcs.head != member.owner @@ -126,6 +126,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { ) } + private def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + /** Add given member to given class, and mark member as mixed-in. */ def addMember(clazz: Symbol, member: Symbol): Symbol = { @@ -202,6 +204,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { clazz.info // make sure info is up to date, so that implClass is set. + // TODO: is this needed? can there be fields in a class that don't have accessors yet but need them??? + // can we narrow this down to just getters for lazy vals? param accessors? for (member <- clazz.info.decls) { if (!member.isMethod && !member.isModule && !member.isModuleVar) { assert(member.isTerm && !member.isDeferred, member) @@ -297,49 +301,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def mixinTraitMembers(mixinClass: Symbol) { // For all members of a trait's interface do: for (mixinMember <- mixinClass.info.decls) { - if (isConcreteAccessor(mixinMember)) { - if (isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) - devWarning(s"Overridden concrete accessor: ${mixinMember.fullLocationString}") - else { - // mixin field accessors - val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) - if (mixinMember.isLazy) { - initializer(mixedInAccessor) = ( - mixinClass.info.decl(mixinMember.name) - orElse abort("Could not find initializer for " + mixinMember.name) - ) - } - if (!mixinMember.isSetter) - mixinMember.tpe match { - case MethodType(Nil, ConstantType(_)) => - // mixinMember is a constant; only getter is needed - ; - case MethodType(Nil, TypeRef(_, UnitClass, _)) => - // mixinMember is a value of type unit. No field needed - ; - case _ => // otherwise mixin a field as well - // 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) - // #3857, need to retain info before erasure when cloning (since cloning only - // carries over the current entry in the type history) - val sym = enteringErasure { - // so we have a type history entry before erasure - clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType) - } - sym updateInfo mixinMember.tpe.resultType // info at current phase - - val newFlags = ( - ( PrivateLocal ) - | ( mixinMember getFlag MUTABLE | LAZY) - | ( if (mixinMember.hasStableFlag) 0 else MUTABLE ) - ) - - addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations) - } - } - } - else if (mixinMember.isSuperAccessor) { // mixin super accessors + if (mixinMember.hasFlag(SUPERACCESSOR)) { // mixin super accessors val superAccessor = addMember(clazz, mixinMember.cloneSymbol(clazz)) setPos clazz.pos assert(superAccessor.alias != NoSymbol, superAccessor) @@ -355,10 +317,53 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { superAccessor.asInstanceOf[TermSymbol] setAlias alias1 } } - else if (mixinMember.isMethod && mixinMember.isModule && mixinMember.hasNoFlags(LIFTED | BRIDGE)) { + else if (mixinMember.hasAllFlags(METHOD | MODULE) && mixinMember.hasNoFlags(LIFTED | BRIDGE)) { // mixin objects: todo what happens with abstract objects? addMember(clazz, mixinMember.cloneSymbol(clazz, mixinMember.flags & ~(DEFERRED | lateDEFERRED)) setPos clazz.pos) } + else if (mixinMember.hasFlag(ACCESSOR) && notDeferredOrLate(mixinMember) + && (mixinMember hasFlag (LAZY | 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 + // (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))) { + // 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) + // #3857, need to retain info before erasure when cloning (since cloning only + // carries over the current entry in the type history) + val sym = enteringErasure { + // so we have a type history entry before erasure + clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType) + } + sym updateInfo mixinMember.tpe.resultType // info at current phase + + val newFlags = ( + (PrivateLocal) + | (mixinMember getFlag MUTABLE | LAZY) + | (if (mixinMember.hasStableFlag) 0 else MUTABLE) + ) + + addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations) + } + } } } @@ -478,8 +483,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { tree - case _ => - tree + case _ => tree } } @@ -763,13 +767,12 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def addCheckedGetters(clazz: Symbol, stats: List[Tree]): List[Tree] = { def dd(stat: DefDef) = { val sym = stat.symbol - def isUnit = sym.tpe.resultType.typeSymbol == UnitClass def isEmpty = stat.rhs == EmptyTree if (!clazz.isTrait && sym.isLazy && !isEmpty) { assert(fieldOffset contains sym, sym) deriveDefDef(stat) { - case t if isUnit => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) + case t if isUnitGetter(sym) => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) case Block(stats, res) => mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) @@ -781,8 +784,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { assert(fieldOffset contains sym, sym) deriveDefDef(stat)(rhs => (mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))( - if (sym.tpe.resultType.typeSymbol == UnitClass) UNIT - else rhs + if (isUnitGetter(sym)) UNIT else rhs ) ) } @@ -908,7 +910,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - def isUnitGetter(getter: Symbol) = getter.tpe.resultType.typeSymbol == UnitClass def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed) def isOverriddenSetter(sym: Symbol) = @@ -924,7 +925,12 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { addDefDef(sym) } else { // if class is not a trait add accessor definitions - if (isConcreteAccessor(sym)) { + // used to include `sym` with `sym hasFlag lateDEFERRED` as not deferred, + // but I don't think MIXEDIN members ever get this flag + assert(!sym.hasFlag(lateDEFERRED), s"mixedin $sym from $clazz has lateDEFERRED flag?!") + if (sym.hasFlag(ACCESSOR) && !sym.hasFlag(DEFERRED)) { + assert(sym hasFlag (LAZY | PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/param?!?") + // add accessor definitions addDefDef(sym, { if (sym.isSetter) { @@ -1006,20 +1012,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos) // mark fields which can be nulled afterward lazyValNullables = nullableFields(templ) withDefaultValue Set() - // Remove bodies of accessors in traits - TODO: after PR #5141 (fields refactoring), this might be a no-op - val bodyEmptyAccessors = if (!sym.enclClass.isTrait) body else body mapConserve { - case dd: DefDef if dd.symbol.isAccessor && !dd.symbol.isLazy => - deriveDefDef(dd)(_ => EmptyTree) - case tree => tree - } // add all new definitions to current class or interface - val body1 = addNewDefs(currentOwner, bodyEmptyAccessors) - body1 foreach { + val statsWithNewDefs = addNewDefs(currentOwner, body) + statsWithNewDefs foreach { case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) => dd.symbol.updateAttachment(NeedStaticImpl) case _ => } - treeCopy.Template(tree, parents1, self, body1) + treeCopy.Template(tree, parents1, self, statsWithNewDefs) case Select(qual, name) if sym.owner.isTrait && !sym.isMethod => // refer to fields in some trait an abstract getter in the interface. diff --git a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala index e4082eb376..a861115cab 100644 --- a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala +++ b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala @@ -18,8 +18,6 @@ abstract class OverridingPairs extends SymbolPairs { import global._ class Cursor(base: Symbol) extends super.Cursor(base) { - lazy val relatively = new RelativeTo(base.thisType) - /** Symbols to exclude: Here these are constructors and private/artifact symbols, * including bridges. But it may be refined in subclasses. */ @@ -37,7 +35,7 @@ abstract class OverridingPairs extends SymbolPairs { (lo.owner != high.owner) // don't try to form pairs from overloaded members && !high.isPrivate // private or private[this] members never are overridden && !exclude(lo) // this admits private, as one can't have a private member that matches a less-private member. - && relatively.matches(lo, high) + && ((self memberType lo) matches (self memberType high)) ) // TODO we don't call exclude(high), should we? } } diff --git a/src/compiler/scala/tools/nsc/transform/Statics.scala b/src/compiler/scala/tools/nsc/transform/Statics.scala index 9ab00f1a83..776805fd9f 100644 --- a/src/compiler/scala/tools/nsc/transform/Statics.scala +++ b/src/compiler/scala/tools/nsc/transform/Statics.scala @@ -4,7 +4,7 @@ package transform abstract class Statics extends Transform with ast.TreeDSL { import global._ - class StaticsTransformer extends Transformer { + trait StaticsTransformer extends Transformer { /** generate a static constructor with symbol fields inits, or an augmented existing static ctor */ def staticConstructor(body: List[Tree], localTyper: analyzer.Typer, pos: Position)(newStaticInits: List[Tree]): Tree = diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 81a465ef2f..fcfcc8feb9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -13,6 +13,7 @@ import scala.reflect.macros.runtime.AbortMacroException import scala.util.control.NonFatal import scala.tools.nsc.util.stackTraceString import scala.reflect.io.NoAbstractFile +import scala.reflect.internal.util.NoSourceFile trait ContextErrors { self: Analyzer => @@ -757,22 +758,18 @@ trait ContextErrors { } def DefDefinedTwiceError(sym0: Symbol, sym1: Symbol) = { + val addPref = s";\n the conflicting $sym1 was defined" + val bugNote = "\n Note: this may be due to a bug in the compiler involving wildcards in package objects" + // Most of this hard work is associated with SI-4893. val isBug = sym0.isAbstractType && sym1.isAbstractType && (sym0.name startsWith "_$") - val addendums = List( - if (sym0.associatedFile eq sym1.associatedFile) - Some("conflicting symbols both originated in file '%s'".format(sym0.associatedFile.canonicalPath)) - else if ((sym0.associatedFile ne NoAbstractFile) && (sym1.associatedFile ne NoAbstractFile)) - Some("conflicting symbols originated in files '%s' and '%s'".format(sym0.associatedFile.canonicalPath, sym1.associatedFile.canonicalPath)) - else None , - if (isBug) Some("Note: this may be due to a bug in the compiler involving wildcards in package objects") else None - ) - val addendum = addendums.flatten match { - case Nil => "" - case xs => xs.mkString("\n ", "\n ", "") - } + val addendum = ( + if (sym0.pos.source eq sym1.pos.source) s"$addPref at line ${sym1.pos.line}:${sym1.pos.column}" + else if (sym1.pos.source ne NoSourceFile) s"$addPref at line ${sym1.pos.line}:${sym1.pos.column} of '${sym1.pos.source.path}'" + else if (sym1.associatedFile ne NoAbstractFile) s"$addPref in '${sym1.associatedFile.canonicalPath}'" + else "") + (if (isBug) bugNote else "") - issueSymbolTypeError(sym0, sym1+" is defined twice" + addendum) + issueSymbolTypeError(sym0, s"$sym0 is defined twice$addendum") } // cyclic errors diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index c03094bc6a..408b457d5b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -121,6 +121,7 @@ trait MethodSynthesis { } // TODO: see if we can link symbol creation & tree derivation by sharing the Field/Getter/Setter factories + // maybe we can at least reuse some variant of standardAccessors? def enterGetterSetter(tree: ValDef): Unit = { tree.symbol = if (tree.mods.isLazy) { @@ -131,15 +132,14 @@ trait MethodSynthesis { val getterSym = getter.createAndEnterSymbol() // Create the setter if necessary. - if (getter.needsSetter) - Setter(tree).createAndEnterSymbol() + if (getter.needsSetter) Setter(tree).createAndEnterSymbol() - // If the getter's abstract the tree gets the getter's symbol, - // otherwise, create a field (assume the getter requires storage). + // If the getter's abstract, the tree gets the getter's symbol, + // otherwise, create a field (we have to assume the getter requires storage for now). // NOTE: we cannot look at symbol info, since we're in the process of deriving them // (luckily, they only matter for lazy vals, which we've ruled out in this else branch, // and `doNotDeriveField` will skip them if `!mods.isLazy`) - if (Field.noFieldFor(tree)) getterSym setPos tree.pos + if (Field.noFieldFor(tree)) getterSym setPos tree.pos // TODO: why do setPos? `createAndEnterSymbol` already gave `getterSym` the position `tree.pos.focus` else enterStrictVal(tree) } @@ -282,14 +282,15 @@ trait MethodSynthesis { final def enclClass = basisSym.enclClass - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - final def completer(sym: Symbol) = namerOf(sym).accessorTypeCompleter(tree, isSetter) + // 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? + final def completer(sym: Symbol) = namerOf(sym).accessorTypeCompleter(tree, this.isInstanceOf[DerivedSetter]) final def fieldSelection = Select(This(enclClass), basisSym) def derivedSym: Symbol = tree.symbol def derivedTree: Tree = EmptyTree - def isSetter = false def isDeferred = mods.isDeferred def validate() { } def createAndEnterSymbol(): MethodSymbol = { @@ -304,6 +305,7 @@ trait MethodSynthesis { result } + final def derive(initial: List[AnnotationInfo]): Tree = { validate() @@ -311,7 +313,9 @@ trait MethodSynthesis { // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. // The defaults are: // - (`val`-, `var`- or plain) constructor parameter annotations end up on the parameter, not on any other entity. - // - val/var member annotations solely end up on the underlying field. + // - val/var member annotations solely end up on the underlying field, except in traits (@since 2.12), + // where there is no field, and the getter thus holds annotations targetting both getter & field. + // As soon as there is a field/getter (in subclasses mixing in the trait), we triage the annotations. // // 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) @@ -319,9 +323,11 @@ trait MethodSynthesis { case _: Param => annotationFilter(ParamTargetClass, defaultRetention = true) // By default annotations go to the field, except if the field is generated for a class parameter (PARAMACCESSOR). case _: Field => annotationFilter(FieldTargetClass, defaultRetention = !mods.isParamAccessor) + case _: BaseGetter if owner.isTrait => annotationFilter(List(FieldTargetClass, GetterTargetClass), defaultRetention = true) case _: BaseGetter => annotationFilter(GetterTargetClass, defaultRetention = false) case _: Setter => annotationFilter(SetterTargetClass, defaultRetention = false) case _: BeanSetter => annotationFilter(BeanSetterTargetClass, defaultRetention = false) + // TODO do bean getters need special treatment to collect field-targeting annotations in traits? case _: AnyBeanGetter => annotationFilter(BeanGetterTargetClass, defaultRetention = false) } @@ -329,21 +335,23 @@ trait MethodSynthesis { // should be propagated to this kind of accessor. derivedSym setAnnotations (initial filter annotFilter) + if (derivedSym.isSetter && owner.isTrait && !isDeferred) + derivedSym addAnnotation TraitSetterAnnotationClass + logDerived(derivedTree) } } + sealed trait DerivedGetter extends DerivedFromValDef { - // A getter must be accompanied by a setter if the ValDef is mutable. def needsSetter = mods.isMutable } sealed trait DerivedSetter extends DerivedFromValDef { - override def isSetter = true - private def setterParam = derivedSym.paramss match { + protected def setterParam = derivedSym.paramss match { case (p :: Nil) :: _ => p case _ => NoSymbol } - private def setterRhs = { + protected def setterRhs = { assert(!derivedSym.isOverloaded, s"Unexpected overloaded setter $derivedSym for $basisSym in $enclClass") if (Field.noFieldFor(tree) || derivedSym.isOverloaded) EmptyTree else Assign(fieldSelection, Ident(setterParam)) @@ -390,6 +398,7 @@ trait MethodSynthesis { override def derivedSym = if (Field.noFieldFor(tree)) basisSym else basisSym.getterIn(enclClass) private def derivedRhs = if (Field.noFieldFor(tree)) tree.rhs else fieldSelection + // 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 @@ -457,6 +466,7 @@ trait MethodSynthesis { def flagsMask = SetterFlags def flagsExtra = ACCESSOR + // TODO: double check logic behind need for name expansion in context of new fields phase override def derivedSym = basisSym.setterIn(enclClass) } @@ -464,17 +474,25 @@ trait MethodSynthesis { // 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 - // - [Emitted, later removed during AddInterfaces/Mixins] concrete vals in traits can't have a field - // - [Emitted, later removed during Constructors] a concrete val with a statically known value (Unit / ConstantType) + // - 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 - // TODO: the `def field` call-site breaks when you add `|| vd.symbol.owner.isTrait` (detected in test suite) - def noFieldFor(vd: ValDef) = vd.mods.isDeferred || (vd.mods.isLazy && isUnitType(vd.symbol.info)) + // 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) } case class Field(tree: ValDef) extends DerivedFromValDef { @@ -482,6 +500,9 @@ trait MethodSynthesis { def flagsMask = FieldFlags def flagsExtra = PrivateLocal + // TODO: override def createAndEnterSymbol (currently never called on Field) + // and do `enterStrictVal(tree)`, so that enterGetterSetter and addDerivedTrees can share some logic... + // handle lazy val first for now (we emit a Field even though we probably shouldn't...) override def derivedTree = if (mods.isLazy) copyValDef(tree)(mods = mods | flagsExtra, name = this.name, rhs = EmptyTree).setPos(tree.pos.focus) @@ -528,7 +549,10 @@ trait MethodSynthesis { } case class BooleanBeanGetter(tree: ValDef) extends BeanAccessor("is") with AnyBeanGetter { } case class BeanGetter(tree: ValDef) extends BeanAccessor("get") with AnyBeanGetter { } - case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter + case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter { + // TODO: document, motivate + override protected def setterRhs = Apply(Ident(tree.name.setterName), List(Ident(setterParam))) + } // No Symbols available. private def beanAccessorsFromNames(tree: ValDef) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index caad4a907b..784b43ab84 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -6,6 +6,7 @@ package scala.tools.nsc package typechecker +import scala.annotation.tailrec import scala.collection.mutable import symtab.Flags._ import scala.language.postfixOps @@ -116,10 +117,10 @@ trait Namers extends MethodSynthesis { } // All lazy vals need accessors, including those owned by terms (e.g., in method) or private[this] in a class - def deriveAccessors(vd: ValDef) = vd.mods.isLazy || (owner.isClass && deriveAccessorsInClass(vd)) + def deriveAccessors(vd: ValDef) = (vd.mods.isLazy || owner.isTrait || (owner.isClass && deriveAccessorsInClass(vd))) private def deriveAccessorsInClass(vd: ValDef) = - !vd.mods.isPrivateLocal && // note, private[this] lazy vals do get accessors -- see outer disjunction of deriveAccessors + !vd.mods.isPrivateLocal && // note, private[this] lazy vals do get accessors -- see outer disjunction of deriveAccessors !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter !isEnumConstant(vd) // enums can only occur in classes, so only check here @@ -773,28 +774,31 @@ trait Namers extends MethodSynthesis { // this accomplishes anything, but performance is a non-consideration // on these flag checks so it can't hurt. def needsCycleCheck = sym.isNonClassType && !sym.isParameter && !sym.isExistential - logAndValidate(sym) { - val tp = typeSig(tree) - - findCyclicalLowerBound(tp) andAlso { sym => - if (needsCycleCheck) { - // neg/t1224: trait C[T] ; trait A { type T >: C[T] <: C[C[T]] } - // To avoid an infinite loop on the above, we cannot break all cycles - log(s"Reinitializing info of $sym to catch any genuine cycles") - sym reset sym.info - sym.initialize - } - } - sym setInfo { - if (sym.isJavaDefined) RestrictJavaArraysMap(tp) - else tp - } + + // logDefinition(sym) { + val tp = typeSig(tree) + + findCyclicalLowerBound(tp) andAlso { sym => if (needsCycleCheck) { - log(s"Needs cycle check: ${sym.debugLocationString}") - if (!typer.checkNonCyclic(tree.pos, tp)) - sym setInfo ErrorType + // neg/t1224: trait C[T] ; trait A { type T >: C[T] <: C[C[T]] } + // To avoid an infinite loop on the above, we cannot break all cycles + log(s"Reinitializing info of $sym to catch any genuine cycles") + sym reset sym.info + sym.initialize } } + sym setInfo { + if (sym.isJavaDefined) RestrictJavaArraysMap(tp) + else tp + } + if (needsCycleCheck) { + log(s"Needs cycle check: ${sym.debugLocationString}") + if (!typer.checkNonCyclic(tree.pos, tp)) + sym setInfo ErrorType + } + //} + + validate(sym) } def moduleClassTypeCompleter(tree: ModuleDef) = { @@ -807,15 +811,18 @@ trait Namers extends MethodSynthesis { /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ def accessorTypeCompleter(tree: ValDef, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - logAndValidate(sym) { - sym setInfo { - val tp = if (isSetter) MethodType(List(sym.newSyntheticValueParam(typeSig(tree))), UnitTpe) - else NullaryMethodType(typeSig(tree)) - pluginsTypeSigAccessor(tp, typer, tree, sym) - } - } + // 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) + val sig = accessorSigFromFieldTp(sym, isSetter, typeSig(tree)) + + sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + + validate(sym) } + private def accessorSigFromFieldTp(sym: global.Symbol, isSetter: Boolean, tp: global.Type): global.Type with Product with Serializable = { + if (isSetter) MethodType(List(sym.newSyntheticValueParam(tp)), UnitTpe) else NullaryMethodType(tp) + } def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => val selftpe = typer.typedType(tree).tpe sym setInfo { @@ -992,6 +999,19 @@ trait Namers extends MethodSynthesis { clazz.tpe_* } + + // make a java method type if meth.isJavaDefined + private def methodTypeFor(meth: Symbol, vparamSymss: List[List[Symbol]], restpe: Type) = { + def makeJavaMethodType(vparams: List[Symbol], restpe: Type) = { + vparams foreach (p => p setInfo objToAny(p.tpe)) + JavaMethodType(vparams, restpe) + } + if (vparamSymss.isEmpty) NullaryMethodType(restpe) + else if (meth.isJavaDefined) vparamSymss.foldRight(restpe)(makeJavaMethodType) + else vparamSymss.foldRight(restpe)(MethodType(_, _)) + } + + /** * The method type for `ddef`. * @@ -1009,166 +1029,140 @@ trait Namers extends MethodSynthesis { * to the non-skolems. */ private def methodSig(ddef: DefDef): Type = { - - // DEPMETTODO: do we need to skolemize value parameter symbols? - val DefDef(_, _, tparams, vparamss, tpt, _) = ddef val meth = owner val methOwner = meth.owner - val site = methOwner.thisType /* tparams already have symbols (created in enterDefDef/completerOf), namely the skolemized ones (created * by the PolyTypeCompleter constructor, and assigned to tparams). reenterTypeParams enters the type skolems * into scope and returns the non-skolems. */ val tparamSyms = typer.reenterTypeParams(tparams) - val tparamSkolems = tparams.map(_.symbol) - /* since the skolemized tparams are in scope, the TypeRefs in types of vparamSymss refer to the type skolems - * note that for parameters with missing types, `methodSig` reassigns types of these symbols (the parameter - * types from the overridden method). - */ - var vparamSymss = enterValueParams(vparamss) - /* * Creates a method type using tparamSyms and vparamsSymss as argument symbols and `respte` as result type. * All typeRefs to type skolems are replaced by references to the corresponding non-skolem type parameter, * so the resulting type is a valid external method type, it does not contain (references to) skolems. + * + * tparamSyms are deskolemized symbols -- TODO: check that their infos don't refer to method args? + * vparamss refer (if they do) to skolemized tparams */ - def thisMethodType(restpe: Type) = { - if (vparamSymss.lengthCompare(0) > 0) { // OPT fast path for methods of 0-1 parameter lists - val checkDependencies = new DependentTypeChecker(context)(this) - checkDependencies check vparamSymss - } - - val makeMethodType = (vparams: List[Symbol], restpe: Type) => { - // TODODEPMET: check that we actually don't need to do anything here - // new dependent method types: probably OK already, since 'enterValueParams' above - // enters them in scope, and all have a lazy type. so they may depend on other params. but: need to - // check that params only depend on ones in earlier sections, not the same. (done by checkDependencies, - // so re-use / adapt that) - if (meth.isJavaDefined) - // TODODEPMET necessary?? new dependent types: replace symbols in restpe with the ones in vparams - JavaMethodType(vparams map (p => p setInfo objToAny(p.tpe)), restpe) - else - MethodType(vparams, restpe) - } + def deskolemizedPolySig(vparamSymss: List[List[Symbol]], restpe: Type) = + GenPolyType(tparamSyms, methodTypeFor(meth, vparamSymss, restpe).substSym(tparamSkolems, tparamSyms)) - val res = GenPolyType( - tparamSyms, // deSkolemized symbols -- TODO: check that their infos don't refer to method args? - if (vparamSymss.isEmpty) NullaryMethodType(restpe) - // vparamss refer (if they do) to skolemized tparams - else (vparamSymss :\ restpe) (makeMethodType) - ) - res.substSym(tparamSkolems, tparamSyms) + if (tpt.isEmpty && meth.name == nme.CONSTRUCTOR) { + tpt defineType context.enclClass.owner.tpe_* + tpt setPos meth.pos.focus } + /* since the skolemized tparams are in scope, the TypeRefs in types of vparamSymss refer to the type skolems + * note that for parameters with missing types, `methodSig` reassigns types of these symbols (the parameter + * types from the overridden method). + */ + val vparamSymss: List[List[Symbol]] = enterValueParams(vparamss) + + val resTpGiven = + if (tpt.isEmpty) WildcardType + else typer.typedType(tpt).tpe + + + // ignore missing types unless we can look to overridden method to recover the missing information + val canOverride = methOwner.isClass && !meth.isConstructor + val inferResTp = canOverride && tpt.isEmpty + val inferArgTp = canOverride && settings.YmethodInfer && mexists(vparamss)(_.tpt.isEmpty) + + /* - * Creates a schematic method type which has WildcardTypes for non specified - * return or parameter types. For instance, in `def f[T](a: T, b) = ...`, the - * type schema is + * Find the overridden method that matches a schematic method type, + * which has WildcardTypes for unspecified return or parameter types. + * For instance, in `def f[T](a: T, b) = ...`, the type schema is * * PolyType(T, MethodType(List(a: T, b: WildcardType), WildcardType)) * * where T are non-skolems. + * + * NOTE: mutates info of symbol of vparamss that don't specify a type */ - def methodTypeSchema(resTp: Type) = { - // for all params without type set WildcaradType - mforeach(vparamss)(v => if (v.tpt.isEmpty) v.symbol setInfo WildcardType) - thisMethodType(resTp) - } - - def overriddenSymbol(resTp: Type) = { - lazy val schema: Type = methodTypeSchema(resTp) // OPT create once. Must be lazy to avoid cycles in neg/t5093.scala - intersectionType(methOwner.info.parents).nonPrivateMember(meth.name).filter { sym => - sym != NoSymbol && (site.memberType(sym) matches schema) + val methodSigApproxUnknownArgs: () => Type = + if (!inferArgTp) () => deskolemizedPolySig(vparamSymss, resTpGiven) + else () => { + // for all params without type set WildcardType + mforeach(vparamss)(v => if (v.tpt.isEmpty) v.symbol setInfo WildcardType) + // must wait to call deskolemizedPolySig until we've temporarily set the WildcardType info for the vparamSymss + // (Otherwise, valDefSig will complain about missing argument types.) + deskolemizedPolySig(vparamSymss, resTpGiven) } - } - // TODO: see whether this or something similar would work instead: - // def overriddenSymbol = meth.nextOverriddenSymbol + // Must be lazy about the schema to avoid cycles in neg/t5093.scala + val overridden = + if (!canOverride) NoSymbol + else safeNextOverriddenSymbolLazySchema(meth, methodSigApproxUnknownArgs) /* - * If `meth` doesn't have an explicit return type, extracts the return type from the method - * overridden by `meth` (if there's an unique one). This type is lateron used as the expected + * If `meth` doesn't have an explicit return type, extract the return type from the method + * overridden by `meth` (if there's an unique one). This type is later used as the expected * type for computing the type of the rhs. The resulting type references type skolems for * type parameters (consistent with the result of `typer.typedType(tpt).tpe`). * - * As a first side effect, this method assigns a MethodType constructed using this - * return type to `meth`. This allows omitting the result type for recursive methods. + * If the result type is missing, assign a MethodType to `meth` that's constructed using this return type. + * This allows omitting the result type for recursive methods. * - * As another side effect, this method also assigns parameter types from the overridden - * method to parameters of `meth` that have missing types (the parser accepts missing - * parameter types under -Yinfer-argument-types). + * Missing parameter types are also recovered from the overridden method (by mutating the info of their symbols). + * (The parser accepts missing parameter types under -Yinfer-argument-types.) */ - def typesFromOverridden(methResTp: Type): Type = { - val overridden = overriddenSymbol(methResTp) - if (overridden == NoSymbol || overridden.isOverloaded) { - methResTp - } else { + val resTpFromOverride = + if (!(inferArgTp || inferResTp) || overridden == NoSymbol || overridden.isOverloaded) resTpGiven + else { overridden.cookJavaRawInfo() // #3404 xform java rawtypes into existentials - var overriddenTp = site.memberType(overridden) match { - case PolyType(tparams, rt) => rt.substSym(tparams, tparamSkolems) - case mt => mt + + val (overriddenTparams, overriddenTp) = + methOwner.thisType.memberType(overridden) match { + case PolyType(tparams, mt) => (tparams, mt.substSym(tparams, tparamSkolems)) + case mt => (Nil, mt) } - for (vparams <- vparamss) { - var overriddenParams = overriddenTp.params - for (vparam <- vparams) { + + // try to derive empty parameter types from the overridden method's argument types + if (inferArgTp) { + val overriddenSyms = overriddenTparams ++ overridden.paramss.flatten + val ourSyms = tparamSkolems ++ vparamSymss.flatten + foreach2(vparamss, overridden.paramss) { foreach2(_, _) { (vparam, overriddenParam) => + // println(s"infer ${vparam.symbol} from ${overriddenParam}? ${vparam.tpt}") if (vparam.tpt.isEmpty) { - val overriddenParamTp = overriddenParams.head.tpe + val overriddenParamTp = overriddenParam.tpe.substSym(overriddenSyms, ourSyms) + // println(s"inferred ${vparam.symbol} : $overriddenParamTp") // references to type parameters in overriddenParamTp link to the type skolems, so the // assigned type is consistent with the other / existing parameter types in vparamSymss. vparam.symbol setInfo overriddenParamTp vparam.tpt defineType overriddenParamTp setPos vparam.pos.focus } - overriddenParams = overriddenParams.tail - } - overriddenTp = overriddenTp.resultType + }} } - // SI-7668 Substitute parameters from the parent method with those of the overriding method. - overriddenTp = overriddenTp.substSym(overridden.paramss.flatten, vparamss.flatten.map(_.symbol)) + @tailrec @inline def applyFully(tp: Type, paramss: List[List[Symbol]]): Type = + if (paramss.isEmpty) tp match { + case NullaryMethodType(rtpe) => rtpe + case MethodType(Nil, rtpe) => rtpe + case tp => tp + } + else applyFully(tp.resultType(paramss.head.map(_.tpe)), paramss.tail) - overriddenTp match { - case NullaryMethodType(rtpe) => overriddenTp = rtpe - case MethodType(List(), rtpe) => overriddenTp = rtpe - case _ => - } + if (inferResTp) { + // SI-7668 Substitute parameters from the parent method with those of the overriding method. + val overriddenResTp = applyFully(overriddenTp, vparamSymss).substSym(overriddenTparams, tparamSkolems) - if (tpt.isEmpty) { // provisionally assign `meth` a method type with inherited result type // that way, we can leave out the result type even if method is recursive. - meth setInfo thisMethodType(overriddenTp) - overriddenTp - } else { - methResTp - } + meth setInfo deskolemizedPolySig(vparamSymss, overriddenResTp) + overriddenResTp + } else resTpGiven } - } - - if (tpt.isEmpty && meth.name == nme.CONSTRUCTOR) { - tpt defineType context.enclClass.owner.tpe_* - tpt setPos meth.pos.focus - } - - val methResTp = if (tpt.isEmpty) WildcardType else typer.typedType(tpt).tpe - val resTpFromOverride = if (methOwner.isClass && (tpt.isEmpty || mexists(vparamss)(_.tpt.isEmpty))) { - typesFromOverridden(methResTp) - } else { - methResTp - } - - // Add a () parameter section if this overrides some method with () parameters - if (methOwner.isClass && vparamss.isEmpty && - overriddenSymbol(methResTp).alternatives.exists(_.info.isInstanceOf[MethodType])) { - vparamSymss = ListOfNil - } // issue an error for missing parameter types + // (computing resTpFromOverride may have required inferring some, meanwhile) mforeach(vparamss) { vparam => if (vparam.tpt.isEmpty) { MissingParameterOrValTypeError(vparam) @@ -1176,13 +1170,9 @@ trait Namers extends MethodSynthesis { } } - val overridden = { - val isConstr = meth.isConstructor - if (isConstr || !methOwner.isClass) NoSymbol else overriddenSymbol(methResTp) - } - val hasDefaults = mexists(vparamss)(_.symbol.hasDefault) || mexists(overridden.paramss)(_.hasDefault) - if (hasDefaults) - addDefaultGetters(meth, ddef, vparamss, tparams, overridden) + // If we, or the overridden method has defaults, add getters for them + if (mexists(vparamss)(_.symbol.hasDefault) || mexists(overridden.paramss)(_.hasDefault)) + addDefaultGetters(meth, ddef, vparamss, tparams, overridden) // fast track macros, i.e. macros defined inside the compiler, are hardcoded // hence we make use of that and let them have whatever right-hand side they need @@ -1193,27 +1183,35 @@ trait Namers extends MethodSynthesis { // because @macroImpl annotation only gets assigned during typechecking // otherwise macro defs wouldn't be able to robustly coexist with their clients // because a client could be typechecked before a macro def that it uses - if (meth.isMacro) { - typer.computeMacroDefType(ddef, resTpFromOverride) + if (meth.isMacro) typer.computeMacroDefType(ddef, resTpFromOverride) // note: `pt` argument ignored in `computeMacroDefType` + + if (vparamSymss.lengthCompare(0) > 0) { // OPT fast path for methods of 0-1 parameter lists + val checkDependencies = new DependentTypeChecker(context)(this) + checkDependencies check vparamSymss } - val res = thisMethodType({ - val rt = ( - if (!tpt.isEmpty) { - methResTp - } else { - // return type is inferred, we don't just use resTpFromOverride. Here, C.f has type String: - // trait T { def f: Object }; class C <: T { def f = "" } - // using resTpFromOverride as expected type allows for the following (C.f has type A): - // trait T { def f: A }; class C <: T { implicit def b2a(t: B): A = ???; def f = new B } - assignTypeToTree(ddef, typer, resTpFromOverride) - }) + val resTp = { + // When return type is inferred, we don't just use resTpFromOverride -- it must be packed and widened. + // Here, C.f has type String: + // trait T { def f: Object }; class C extends T { def f = "" } + // using resTpFromOverride as expected type allows for the following (C.f has type A): + // trait T { def f: A }; class C extends T { implicit def b2a(t: B): A = ???; def f = new B } + val resTpComputedUnlessGiven = + if (tpt.isEmpty) assignTypeToTree(ddef, typer, resTpFromOverride) + else resTpGiven + // #2382: return type of default getters are always @uncheckedVariance - if (meth.hasDefault) - rt.withAnnotation(AnnotationInfo(uncheckedVarianceClass.tpe, List(), List())) - else rt - }) - pluginsTypeSig(res, typer, ddef, methResTp) + if (meth.hasDefault) resTpComputedUnlessGiven.withAnnotation(AnnotationInfo(uncheckedVarianceClass.tpe, List(), List())) + else resTpComputedUnlessGiven + } + + // Add a () parameter section if this overrides some method with () parameters + val vparamSymssOrEmptyParamsFromOverride = + if (overridden != NoSymbol && vparamSymss.isEmpty && overridden.alternatives.exists(_.info.isInstanceOf[MethodType])) ListOfNil // NOTEL must check `.info.isInstanceOf[MethodType]`, not `.isMethod`! + else vparamSymss + + val methSig = deskolemizedPolySig(vparamSymssOrEmptyParamsFromOverride, resTp) + pluginsTypeSig(methSig, typer, ddef, resTpGiven) } /** @@ -1369,19 +1367,76 @@ trait Namers extends MethodSynthesis { private def valDefSig(vdef: ValDef) = { val ValDef(_, _, tpt, rhs) = vdef - val result = if (tpt.isEmpty) { - if (rhs.isEmpty) { - MissingParameterOrValTypeError(tpt) - ErrorType - } - else assignTypeToTree(vdef, typer, WildcardType) - } else { - typer.typedType(tpt).tpe - } + val result = + if (tpt.isEmpty) { + if (rhs.isEmpty) { + MissingParameterOrValTypeError(tpt) + ErrorType + } else { + // enterGetterSetter assigns the getter's symbol to a ValDef when there's no underlying field + // (a deferred val or most vals defined in a trait -- see Field.noFieldFor) + val isGetter = vdef.symbol hasFlag ACCESSOR + + val pt = { + val valOwner = owner.owner + // there's no overriding outside of classes, and we didn't use to do this in 2.11, so provide opt-out + if (valOwner.isClass && settings.isScala212) { + // normalize to getter so that we correctly consider a val overriding a def + // (a val's name ends in a " ", so can't compare to def) + val overridingSym = if (isGetter) vdef.symbol else vdef.symbol.getterIn(valOwner) + + // We're called from an accessorTypeCompleter, which is completing the info for the accessor's symbol, + // which may or may not be `vdef.symbol` (see isGetter above) + val overridden = safeNextOverriddenSymbol(overridingSym) + + if (overridden == NoSymbol || overridden.isOverloaded) WildcardType + else valOwner.thisType.memberType(overridden).resultType + } else WildcardType + } + + def patchSymInfo(tp: Type): Unit = + if (pt ne WildcardType) // no patching up to do if we didn't infer a prototype + vdef.symbol setInfo (if (isGetter) NullaryMethodType(tp) else tp) + + patchSymInfo(pt) + + // derives the val's result type from type checking its rhs under the expected type `pt` + // vdef.tpt is mutated, and `vdef.tpt.tpe` is `assignTypeToTree`'s result + val tptFromRhsUnderPt = assignTypeToTree(vdef, typer, pt) + + // need to re-align with assignTypeToTree, as the type we're returning from valDefSig (tptFromRhsUnderPt) + // may actually go to the accessor, not the valdef (and if assignTypeToTree returns a subtype of `pt`, + // we would be out of synch between field and its accessors), and thus the type completer won't + // fix the symbol's info for us -- we set it to tmpInfo above, which may need to be improved to tptFromRhsUnderPt + if (!isGetter) patchSymInfo(tptFromRhsUnderPt) + + tptFromRhsUnderPt + } + } else typer.typedType(tpt).tpe + +// println(s"val: $result / ${vdef.tpt.tpe} / ") + pluginsTypeSig(result, typer, vdef, if (tpt.isEmpty) WildcardType else result) + } + // Pretend we're an erroneous symbol, for now, so that we match while finding the overridden symbol, + // but are not considered during implicit search. + private def safeNextOverriddenSymbol(sym: Symbol, schema: Type = ErrorType): Symbol = { + val savedInfo = sym.rawInfo + val savedFlags = sym.rawflags + try { + sym setInfo schema + sym.nextOverriddenSymbol + } finally { + sym setInfo savedInfo // setInfo resets the LOCKED flag, so restore saved flags as well + sym.rawflags = savedFlags + } } + private def safeNextOverriddenSymbolLazySchema(sym: Symbol, schema: () => Type): Symbol = + safeNextOverriddenSymbol(sym, new LazyType { override def complete(sym: Symbol): Unit = sym setInfo schema() }) + + //@M! an abstract type definition (abstract type member/type parameter) // may take type parameters, which are in scope in its bounds private def typeDefSig(tdef: TypeDef) = { @@ -1560,10 +1615,6 @@ trait Namers extends MethodSynthesis { sym => "[define] >> " + sym.flagString + " " + sym.fullLocationString, sym => "[define] << " + sym ) - private def logAndValidate(sym: Symbol)(body: => Unit) { - logDefinition(sym)(body) - validate(sym) - } /** Convert Java generic array type T[] to (T with Object)[] * (this is necessary because such arrays have a representation which is incompatible diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index d1764ea482..0eae1ce419 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -298,16 +298,29 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def infoString(sym: Symbol) = infoString0(sym, sym.owner != clazz) def infoStringWithLocation(sym: Symbol) = infoString0(sym, true) - def infoString0(sym: Symbol, showLocation: Boolean) = { - val sym1 = analyzer.underlyingSymbol(sym) - sym1.toString() + + def infoString0(member: Symbol, showLocation: Boolean) = { + val underlying = // not using analyzer.underlyingSymbol(member) because we should get rid of it + if (!(member hasFlag ACCESSOR)) member + else member.accessed match { + case field if field.exists => field + case _ if member.isSetter => member.getterIn(member.owner) + case _ => member + } + + def memberInfo = + self.memberInfo(underlying) match { + case getterTp if underlying.isGetter => getterTp.resultType + case tp => tp + } + + underlying.toString() + (if (showLocation) - sym1.locationString + - (if (sym1.isAliasType) ", which equals "+self.memberInfo(sym1) - else if (sym1.isAbstractType) " with bounds"+self.memberInfo(sym1) - else if (sym1.isModule) "" - else if (sym1.isTerm) " of type "+self.memberInfo(sym1) - else "") + underlying.locationString + + (if (underlying.isAliasType) s", which equals $memberInfo" + else if (underlying.isAbstractType) s" with bounds$memberInfo" + else if (underlying.isModule) "" + else if (underlying.isTerm) s" of type $memberInfo" + else "") else "") } @@ -321,7 +334,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def memberTp = lowType def otherTp = highType - debuglog("Checking validity of %s overriding %s".format(member.fullLocationString, other.fullLocationString)) +// debuglog(s"Checking validity of ${member.fullLocationString} overriding ${other.fullLocationString}") def noErrorType = !pair.isErroneous def isRootOrNone(sym: Symbol) = sym != null && sym.isRoot || sym == NoSymbol @@ -346,9 +359,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans analyzer.foundReqMsg(member.tpe, other.tpe) else "" - "overriding %s;\n %s %s%s".format( - infoStringWithLocation(other), infoString(member), msg, addendum - ) + s"overriding ${infoStringWithLocation(other)};\n ${infoString(member)} $msg$addendum" } def emitOverrideError(fullmsg: String) { if (member.owner == clazz) reporter.error(member.pos, fullmsg) @@ -439,9 +450,11 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } else if (other.isAbstractOverride && other.isIncompleteIn(clazz) && !member.isAbstractOverride) { overrideError("needs `abstract override' modifiers") } - else if (member.isAnyOverride && (other hasFlag ACCESSOR) && other.accessed.isVariable && !other.accessed.isLazy) { - // !?! this is not covered by the spec. We need to resolve this either by changing the spec or removing the test here. - // !!! is there a !?! convention? I'm !!!ing this to make sure it turns up on my searches. + else if (member.isAnyOverride && (other hasFlag ACCESSOR) && !(other hasFlag STABLE)) { + // The check above used to look at `field` == `other.accessed`, ensuring field.isVariable && !field.isLazy, + // which I think is identical to the more direct `!(other hasFlag STABLE)` (given that `other` is a method). + // Also, we're moving away from (looking at) underlying fields (vals in traits no longer have them, to begin with) + // TODO: this is not covered by the spec. We need to resolve this either by changing the spec or removing the test here. if (!settings.overrideVars) overrideError("cannot override a mutable variable") } @@ -456,7 +469,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } else if (member.isValue && member.isLazy && other.isValue && !other.isSourceMethod && !other.isDeferred && !other.isLazy) { overrideError("cannot override a concrete non-lazy value") - } else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred && + } 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) 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) @@ -547,7 +560,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } def checkOverrideDeprecated() { - if (other.hasDeprecatedOverridingAnnotation && !member.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) { + if (other.hasDeprecatedOverridingAnnotation && !(member.hasDeprecatedOverridingAnnotation || member.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation))) { val version = other.deprecatedOverridingVersion.getOrElse("") val since = if (version.isEmpty) version else s" (since $version)" val message = other.deprecatedOverridingMessage map (msg => s": $msg") getOrElse "" @@ -651,7 +664,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans for (member <- missing) { def undefined(msg: String) = abstractClassError(false, infoString(member) + " is not defined" + msg) - val underlying = analyzer.underlyingSymbol(member) + val underlying = analyzer.underlyingSymbol(member) // TODO: don't use this method // Give a specific error message for abstract vars based on why it fails: // It could be unimplemented, have only one accessor, or be uninitialized. @@ -1133,22 +1146,16 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans case _ => } - // SI-6276 warn for `def foo = foo` or `val bar: X = bar`, which come up more frequently than you might think. - def checkInfiniteLoop(valOrDef: ValOrDefDef) { - def callsSelf = valOrDef.rhs match { - case t @ (Ident(_) | Select(This(_), _)) => - t hasSymbolWhich (_.accessedOrSelf == valOrDef.symbol) - case _ => false + // SI-6276 warn for trivial recursion, such as `def foo = foo` or `val bar: X = bar`, which come up more frequently than you might think. + // TODO: Move to abide rule. Also, this does not check that the def is final or not overridden, for example + def checkInfiniteLoop(sym: Symbol, rhs: Tree): Unit = + if (!sym.isValueParameter && sym.paramss.isEmpty) { + rhs match { + case t@(Ident(_) | Select(This(_), _)) if t hasSymbolWhich (_.accessedOrSelf == sym) => + reporter.warning(rhs.pos, s"${sym.fullLocationString} does nothing other than call itself recursively") + case _ => + } } - val trivialInfiniteLoop = ( - !valOrDef.isErroneous - && !valOrDef.symbol.isValueParameter - && valOrDef.symbol.paramss.isEmpty - && callsSelf - ) - if (trivialInfiniteLoop) - reporter.warning(valOrDef.rhs.pos, s"${valOrDef.symbol.fullLocationString} does nothing other than call itself recursively") - } // Transformation ------------------------------------------------------------ @@ -1659,16 +1666,19 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // inside annotations. applyRefchecksToAnnotations(tree) var result: Tree = tree match { - case vod: ValOrDefDef => + // NOTE: a val in a trait is now a DefDef, with the RHS being moved to an Assign in Constructors + case tree: ValOrDefDef => checkDeprecatedOvers(tree) - checkInfiniteLoop(vod) + if (!tree.isErroneous) + checkInfiniteLoop(tree.symbol, tree.rhs) + if (settings.warnNullaryUnit) checkNullaryMethodReturnType(sym) if (settings.warnInaccessible) { if (!sym.isConstructor && !sym.isEffectivelyFinalOrNotOverridden && !sym.isSynthetic) checkAccessibilityOfReferencedTypes(tree) } - vod match { + tree match { case dd: DefDef => checkByNameRightAssociativeDef(dd) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 5f2643cb25..bee327c760 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -133,12 +133,14 @@ trait TypeDiagnostics { alternatives(tree) map (x => " " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n") /** The symbol which the given accessor represents (possibly in part). - * This is used for error messages, where we want to speak in terms - * of the actual declaration or definition, not in terms of the generated setters - * and getters. - */ + * This is used for error messages, where we want to speak in terms + * of the actual declaration or definition, not in terms of the generated setters + * and getters. + * + * 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 + if (!member.hasAccessorFlag || member.owner.isTrait) member else if (!member.isDeferred) member.accessed else { val getter = if (member.isSetter) member.getterIn(member.owner) else member @@ -532,8 +534,8 @@ trait TypeDiagnostics { val what = ( if (sym.isDefaultGetter) "default argument" else if (sym.isConstructor) "constructor" - else if (sym.isVar || sym.isGetter && sym.accessed.isVar) "var" - else if (sym.isVal || sym.isGetter && sym.accessed.isVal || sym.isLazy) "val" + else if (sym.isVar || sym.isGetter && (sym.accessed.isVar || (sym.owner.isTrait && !sym.hasFlag(STABLE)))) "var" + else if (sym.isVal || sym.isGetter && (sym.accessed.isVal || (sym.owner.isTrait && sym.hasFlag(STABLE))) || sym.isLazy) "val" else if (sym.isSetter) "setter" else if (sym.isMethod) "method" else if (sym.isModule) "object" diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index ba104fb7a6..2bbf8ed74e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1360,7 +1360,13 @@ 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 => @@ -4219,7 +4225,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // if (varsym.isVariable || // // setter-rewrite has been done above, so rule out methods here, but, wait a minute, why are we assigning to non-variables after erasure?! // (phase.erasedTypes && varsym.isValue && !varsym.isMethod)) { - if (varsym.isVariable || varsym.isValue && phase.erasedTypes) { + if (varsym.isVariable || varsym.isValue && phase.assignsFields) { val rhs1 = typedByValueExpr(rhs, lhs1.tpe) treeCopy.Assign(tree, lhs1, checkDead(rhs1)) setType UnitTpe } |