diff options
author | Paul Phillips <paulp@improving.org> | 2011-09-08 18:37:18 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-09-08 18:37:18 +0000 |
commit | f32a32b1b33c9d7ccd62467e3e10cb69930023c8 (patch) | |
tree | 47ca34246fbdd255f2191ced68bd708e609d22d2 /src/compiler/scala/tools | |
parent | 52c1d019d63d2fffadc8f3f159d654187ac61ece (diff) | |
download | scala-f32a32b1b33c9d7ccd62467e3e10cb69930023c8.tar.gz scala-f32a32b1b33c9d7ccd62467e3e10cb69930023c8.tar.bz2 scala-f32a32b1b33c9d7ccd62467e3e10cb69930023c8.zip |
Allow for the overriding of objects.
Various and sundry manipulations to allow for objects to be overridden
when the mood is right. It is not enabled by default.
The major contributor of change turned out to be the decoupling of
the FINAL flag (and the "isFinal" test which examines only that flag)
and the many semantics which were attributed to this interpretation
of finality in different circumstances. Since objects no longer have
the FINAL flag automatically applied (only top-level objects and those
marked final in source code do) we need apply a more nuanced test.
Fortunately there is such a nuanced test: isEffectivelyFinal,
which is always true if the FINAL flag is set but also in various
other circumstances. In almost every case, you should be testing
"isEffectivelyFinal", not "isFinal".
To enable overridable objects, use:
-Yoverride-objects
-Xexperimental // includes the above and others
Remain to be done: working out transition logistics. Most likely this
would involve bumping the scala signature version, and all objects in
versions before that would be assumed final.
Review by moors.
Diffstat (limited to 'src/compiler/scala/tools')
8 files changed, 82 insertions, 40 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index f24b4ac6bb..48b5d7cdce 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -1901,7 +1901,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with // avoid breaking proxy software which depends on subclassing, we avoid // insisting on their finality in the bytecode. val finalFlag = ( - ((sym.rawflags & Flags.FINAL) != 0) + ((sym.rawflags & (Flags.FINAL | Flags.MODULE)) != 0) && !sym.enclClass.isInterface && !sym.isClassConstructor ) diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala index 9522f4b3ea..311a3b916a 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala @@ -270,7 +270,7 @@ abstract class DeadCodeElimination extends SubComponent { } private def isPure(sym: Symbol) = ( - (sym.isGetter && sym.isFinal && !sym.isLazy) + (sym.isGetter && sym.isEffectivelyFinal && !sym.isLazy) || (sym.isPrimaryConstructor && (sym.enclosingPackage == RuntimePackage || inliner.isClosureClass(sym.owner))) ) /** Is 'sym' a side-effecting method? TODO: proper analysis. */ diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index 3bded8cca4..f79cec532d 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -139,13 +139,18 @@ abstract class Inliners extends SubComponent { icodes.load(concreteMethod.enclClass) def isAvailable = icodes available concreteMethod.enclClass - def isCandidate = isClosureClass(receiver) || concreteMethod.isEffectivelyFinal || receiver.isFinal + def isCandidate = ( + isClosureClass(receiver) + || concreteMethod.isEffectivelyFinal + || receiver.isEffectivelyFinal + ) def isApply = concreteMethod.name == nme.apply - def isCountable = !(isClosureClass(receiver) - || isApply - || isMonadicMethod(concreteMethod) - || receiver.enclosingPackage == definitions.RuntimePackage - ) // only count non-closures + def isCountable = !( + isClosureClass(receiver) + || isApply + || isMonadicMethod(concreteMethod) + || receiver.enclosingPackage == definitions.RuntimePackage + ) // only count non-closures debuglog("Treating " + i + "\n\treceiver: " + receiver @@ -260,8 +265,12 @@ abstract class Inliners extends SubComponent { */ def lookupImplFor(sym: Symbol, clazz: Symbol): Symbol = { // TODO: verify that clazz.superClass is equivalent here to clazz.tpe.parents(0).typeSymbol (.tpe vs .info) - def needsLookup = (clazz != NoSymbol) && (clazz != sym.owner) && !sym.isEffectivelyFinal && clazz.isFinal - + def needsLookup = ( + (clazz != NoSymbol) + && (clazz != sym.owner) + && !sym.isEffectivelyFinal + && clazz.isEffectivelyFinal + ) def lookup(clazz: Symbol): Symbol = { // println("\t\tlooking up " + meth + " in " + clazz.fullName + " meth.owner = " + meth.owner) if (sym.owner == clazz || isBottomType(clazz)) sym diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index ca7ca5b41f..68496b61ce 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -93,7 +93,7 @@ trait ScalaSettings extends AbsScalaSettings // Experimental Extensions val Xexperimental = BooleanSetting ("-Xexperimental", "Enable experimental extensions.") . - withPostSetHook(set => List(YdepMethTpes, YmethodInfer) foreach (_.value = set.value)) //YvirtClasses, + withPostSetHook(set => List(YdepMethTpes, YmethodInfer, overrideObjects) foreach (_.value = set.value)) //YvirtClasses, /** Compatibility stubs for options whose value name did * not previously match the option name. @@ -108,6 +108,7 @@ trait ScalaSettings extends AbsScalaSettings /** * -Y "Private" settings */ + val overrideObjects = BooleanSetting ("-Yoverride-objects", "Allow member objects to be overridden.") val Yhelp = BooleanSetting ("-Y", "Print a synopsis of private options.") val browse = PhasesSetting ("-Ybrowse", "Browse the abstract syntax tree after") val check = PhasesSetting ("-Ycheck", "Check the tree at the end of") diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index e29f085de7..dc8585e8cb 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -215,7 +215,7 @@ abstract class Constructors extends Transform with ast.TreeDSL { // the symbol is an outer accessor of a final class which does not override another outer accessor. ) def maybeOmittable(sym: Symbol) = sym.owner == clazz && ( sym.isParamAccessor && sym.isPrivateLocal || - sym.isOuterAccessor && sym.owner.isFinal && !sym.isOverridingSymbol && + sym.isOuterAccessor && sym.owner.isEffectivelyFinal && !sym.isOverridingSymbol && !(clazz isSubClass DelayedInitClass) ) @@ -228,7 +228,7 @@ abstract class Constructors extends Transform with ast.TreeDSL { override def traverse(tree: Tree) = { tree match { case DefDef(_, _, _, _, _, body) - if (tree.symbol.isOuterAccessor && tree.symbol.owner == clazz && clazz.isFinal) => + if (tree.symbol.isOuterAccessor && tree.symbol.owner == clazz && clazz.isEffectivelyFinal) => log("outerAccessors += " + tree.symbol.fullName) outerAccessors ::= ((tree.symbol, body)) case Select(_, _) => diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 992a746e1e..8db759266b 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -150,7 +150,7 @@ abstract class ExplicitOuter extends InfoTransform val restpe = if (clazz.isTrait) clazz.outerClass.tpe else clazz.outerClass.thisType decls1 enter (clazz.newOuterAccessor(clazz.pos) setInfo MethodType(Nil, restpe)) if (hasOuterField(clazz)) { //2 - val access = if (clazz.isFinal) PRIVATE | LOCAL else PROTECTED + val access = if (clazz.isEffectivelyFinal) PRIVATE | LOCAL else PROTECTED decls1 enter ( clazz.newValue(clazz.pos, nme.OUTER_LOCAL) setFlag (SYNTHETIC | PARAMACCESSOR | access) @@ -215,7 +215,7 @@ abstract class ExplicitOuter extends InfoTransform val outerFld = if (outerAcc.owner == currentClass && base.tpe =:= currentClass.thisType && - outerAcc.owner.isFinal) + outerAcc.owner.isEffectivelyFinal) outerField(currentClass) suchThat (_.owner == currentClass) else NoSymbol diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 5a336d9027..c3062b3b2e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -93,7 +93,7 @@ trait Namers { self: Analyzer => else 0l def moduleClassFlags(moduleFlags: Long) = - (moduleFlags & ModuleToClassFlags) | FINAL | inConstructorFlag + (moduleFlags & ModuleToClassFlags) | inConstructorFlag def updatePosFlags(sym: Symbol, pos: Position, flags: Long): Symbol = { debuglog("overwriting " + sym) @@ -244,9 +244,9 @@ trait Namers { self: Analyzer => /** Enter a module symbol. The tree parameter can be either a module definition * or a class definition */ def enterModuleSymbol(tree : ModuleDef): Symbol = { - // .pos, mods.flags | MODULE | FINAL, name + // .pos, mods.flags | MODULE, name var m: Symbol = context.scope.lookup(tree.name) - val moduleFlags = tree.mods.flags | MODULE | FINAL + val moduleFlags = tree.mods.flags | MODULE if (m.isModule && !m.isPackage && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) { updatePosFlags(m, tree.pos, moduleFlags) setPrivateWithin(tree, m, tree.mods) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index c05a5e721e..0ff886dea6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -204,6 +204,10 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R * overrides some other member. */ private def checkAllOverrides(clazz: Symbol, typesOnly: Boolean = false) { + val self = clazz.thisType + def classBoundAsSeen(tp: Type) = { + tp.typeSymbol.classBound.asSeenFrom(self, tp.typeSymbol.owner) + } case class MixinOverrideError(member: Symbol, msg: String) @@ -223,8 +227,17 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } } - val self = clazz.thisType + def isConformingObjectOverride(tp1: Type, tp2: Type) = { + tp1.typeSymbol.isModuleClass && tp2.typeSymbol.isModuleClass && { + val cb1 = classBoundAsSeen(tp1) + val cb2 = classBoundAsSeen(tp2) + (cb1 <:< cb2) && { + log("Allowing %s to override %s because %s <:< %s".format(tp1, tp2, cb1, cb2)) + true + } + } + } def isAbstractTypeWithoutFBound(sym: Symbol) = // (part of DEVIRTUALIZE) sym.isAbstractType && !sym.isFBounded @@ -248,10 +261,10 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R rtp1 <:< rtp2 case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) => rtp1 <:< rtp2 - case (TypeRef(_, sym, _), _) if (sym.isModuleClass) => + case (TypeRef(_, sym, _), _) if sym.isModuleClass => overridesType(NullaryMethodType(tp1), tp2) case _ => - tp1 <:< tp2 + (tp1 <:< tp2) || isConformingObjectOverride(tp1, tp2) } /** Check that all conditions for overriding `other` by `member` @@ -260,24 +273,43 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R def checkOverride(clazz: Symbol, member: Symbol, other: Symbol) { def noErrorType = other.tpe != ErrorType && member.tpe != ErrorType def isRootOrNone(sym: Symbol) = sym == RootClass || sym == NoSymbol + def objectOverrideErrorMsg = ( + "overriding " + other.fullLocationString + " with " + member.fullLocationString + ":\n" + + "an overriding object must conform to the overridden object's class bound" + + analyzer.foundReqMsg(classBoundAsSeen(member.tpe), classBoundAsSeen(other.tpe)) + ) + + def overrideErrorMsg(msg: String): String = { + val isConcreteOverAbstract = + (other.owner isSubClass member.owner) && other.isDeferred && !member.isDeferred + val addendum = + if (isConcreteOverAbstract) + ";\n (Note that %s is abstract,\n and is therefore overridden by concrete %s)".format( + infoStringWithLocation(other), + infoStringWithLocation(member) + ) + else "" + + "overriding %s;\n %s %s%s".format( + infoStringWithLocation(other), infoString(member), msg, addendum + ) + } + def emitOverrideError(fullmsg: String) { + if (member.owner == clazz) unit.error(member.pos, fullmsg) + else mixinOverrideErrors += new MixinOverrideError(member, fullmsg) + } def overrideError(msg: String) { - if (noErrorType) { - val fullmsg = - "overriding "+infoStringWithLocation(other)+";\n "+ - infoString(member)+" "+msg+ - (if ((other.owner isSubClass member.owner) && other.isDeferred && !member.isDeferred) - ";\n (Note that "+infoStringWithLocation(other)+" is abstract,"+ - "\n and is therefore overridden by concrete "+infoStringWithLocation(member)+")" - else "") - if (member.owner == clazz) unit.error(member.pos, fullmsg) - else mixinOverrideErrors += new MixinOverrideError(member, fullmsg) - } + if (noErrorType) + emitOverrideError(overrideErrorMsg(msg)) } def overrideTypeError() { if (noErrorType) { - overrideError("has incompatible type") + emitOverrideError( + if (member.isModule && other.isModule) objectOverrideErrorMsg + else overrideErrorMsg("has incompatible type") + ) } } @@ -331,11 +363,11 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } if (!isOverrideAccessOK) { overrideAccessError() - } else if (other.isClass || other.isModule) { - overrideError("cannot be used here - classes and objects cannot be overridden"); - } else if (!other.isDeferred && (member.isClass || member.isModule)) { - overrideError("cannot be used here - classes and objects can only override abstract types"); - } else if (other hasFlag FINAL) { // (1.2) + } else if (other.isClass) { + overrideError("cannot be used here - class definitions cannot be overridden"); + } else if (!other.isDeferred && member.isClass) { + overrideError("cannot be used here - classes can only override abstract types"); + } else if (other.isFinal) { // (1.2) overrideError("cannot override final member"); } else if (!other.isDeferred && !(member hasFlag (OVERRIDE | ABSOVERRIDE | SYNTHETIC))) { // (1.3), SYNTHETIC because of DEVIRTUALIZE overrideError("needs `override' modifier"); @@ -1006,9 +1038,9 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R else if (isWarnable) { if (isNew(qual)) // new X == y nonSensibleWarning("a fresh object", false) - else if (isNew(args.head) && (receiver.isFinal || isReferenceOp)) // object X ; X == new Y + else if (isNew(args.head) && (receiver.isEffectivelyFinal || isReferenceOp)) // object X ; X == new Y nonSensibleWarning("a fresh object", false) - else if (receiver.isFinal && !(receiver isSubClass actual)) { // object X, Y; X == Y + else if (receiver.isEffectivelyFinal && !(receiver isSubClass actual)) { // object X, Y; X == Y if (isEitherNullable) nonSensible("non-null ", false) else |