summaryrefslogtreecommitdiff
path: root/src/compiler/scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala')
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala18
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala7
-rw-r--r--src/compiler/scala/tools/nsc/transform/AddInterfaces.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala197
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Fields.scala449
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala132
-rw-r--r--src/compiler/scala/tools/nsc/transform/OverridingPairs.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/Statics.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala23
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala60
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala395
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala84
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala16
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala8
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
}