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