From eb3df0db4a57d89c0d7370a9180742273db166b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 31 Mar 2014 15:42:52 +0200 Subject: [PATCH 01/10] Corrected computations of union denotations. Cannot discard a symbol simply because the other side's type is weaker. If in (A | B)#m A and B resolve to different symbols `m`, the resulting denotation cannot have either `m` as symbol; it must be NoSymbol instead. --- src/dotty/tools/dotc/core/Denotations.scala | 51 +++++++++---------- src/dotty/tools/dotc/typer/Applications.scala | 4 +- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index e9b9b20c4f28..0622829c52e2 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -273,35 +273,34 @@ object Denotations { def unionDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = if (denot1.signature matches denot2.signature) { + val sym1 = denot1.symbol + val sym2 = denot2.symbol val info1 = denot1.info val info2 = denot2.info - val sym2 = denot2.symbol - def sym2Accessible = sym2.isAccessibleFrom(pre) - if (info1 <:< info2 && sym2Accessible) denot2 + val sameSym = sym1 eq sym2 + if (sameSym && info1 <:< info2) denot2 + else if (sameSym && info2 <:< info1) denot1 else { - val sym1 = denot1.symbol - def sym1Accessible = sym1.isAccessibleFrom(pre) - if (info2 <:< info1 && sym1Accessible) denot1 - else { - val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol - /** Determine a symbol which is overridden by both sym1 and sym2. - * Preference is given to accessible symbols. - */ - def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = - if (!overrides.hasNext) previous - else { - val candidate = overrides.next - if (owner2 derivesFrom candidate.owner) - if (candidate isAccessibleFrom pre) candidate - else lubSym(overrides, previous orElse candidate) - else - lubSym(overrides, previous) - } - new JointRefDenotation( - lubSym(sym1.allOverriddenSymbols, NoSymbol), - info1 | info2, - denot1.validFor & denot2.validFor) - } + val jointSym = + if (sameSym) sym1 + else { + val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol + /** Determine a symbol which is overridden by both sym1 and sym2. + * Preference is given to accessible symbols. + */ + def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = + if (!overrides.hasNext) previous + else { + val candidate = overrides.next + if (owner2 derivesFrom candidate.owner) + if (candidate isAccessibleFrom pre) candidate + else lubSym(overrides, previous orElse candidate) + else + lubSym(overrides, previous) + } + lubSym(sym1.allOverriddenSymbols, NoSymbol) + } + new JointRefDenotation(jointSym, info1 | info2, denot1.validFor & denot2.validFor) } } else NoDenotation diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 57f11480cae4..0f47336fcedb 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -794,8 +794,8 @@ trait Applications extends Compatibility { self: Typer => tp } - val owner1 = alt1.symbol.owner - val owner2 = alt2.symbol.owner + val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol + val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol val tp1 = stripImplicit(alt1.widen) val tp2 = stripImplicit(alt2.widen) From dd5341106d9b57a99316a26a1f0c7e195b6debf1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 31 Mar 2014 17:20:02 +0200 Subject: [PATCH 02/10] Fleshed out Splitter phase Implemented splitting operations As a side effect, this contains a test ruling out structural term member dispatch. Tests 0586 and 0625 which used structural dispatch got moved to neg. --- src/dotty/tools/dotc/Run.scala | 2 +- src/dotty/tools/dotc/transform/Splitter.scala | 106 +++++++++++++++++- test/dotc/tests.scala | 2 + tests/{pos => neg}/t0586.scala | 0 tests/{pos => neg}/t0625.scala | 0 tests/pos/unions.scala | 11 ++ 6 files changed, 117 insertions(+), 4 deletions(-) rename tests/{pos => neg}/t0586.scala (100%) rename tests/{pos => neg}/t0625.scala (100%) diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index 264373baff93..247fa43365e1 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -51,7 +51,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { private def printTree(ctx: Context) = { val unit = ctx.compilationUnit - println(s"result of $unit after ${ctx.phase}:") + println(s"result of $unit after ${ctx.phase.prev}:") println(unit.tpdTree.show(ctx)) } diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index 43a91a058c51..978a9cce44d1 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -3,10 +3,10 @@ package transform import TreeTransforms._ import ast.Trees._ -import core.Contexts._ -import core.Types._ +import core._ +import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ -/** This transform makes usre every identifier and select node +/** This transform makes sure every identifier and select node * carries a symbol. To do this, certain qualifiers with a union type * have to be "splitted" with a type test. * @@ -24,4 +24,104 @@ class Splitter extends TreeTransform { This(cls) withPos tree.pos case _ => tree } + + /** If we select a name, make sure the node has a symbol. + * If necessary, split the qualifier with type tests. + * Example: Assume: + * + * class A { def f(x: S): T } + * class B { def f(x: S): T } + * def p(): A | B + * + * Then p().f(a) translates to + * + * val ev$1 = p() + * if (ev$1.isInstanceOf[A]) ev$1.asInstanceOf[A].f(a) + * else ev$1.asInstanceOf[B].f(a) + */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = { + val Select(qual, name) = tree + + def memberDenot(tp: Type): SingleDenotation = { + val mbr = tp.member(name) + if (!mbr.isOverloaded) mbr.asSingleDenotation + else tree.tpe match { + case tref: TermRefWithSignature => mbr.atSignature(tref.sig) + case _ => ctx.error(s"cannot disambiguate overloaded member $mbr"); NoDenotation + } + } + + def candidates(tp: Type): List[Symbol] = { + val mbr = memberDenot(tp) + if (mbr.symbol.exists) mbr.symbol :: Nil + else tp.widen match { + case tref: TypeRef => + tref.info match { + case TypeBounds(_, hi) => candidates(hi) + case _ => Nil + } + case OrType(tp1, tp2) => + candidates(tp1) | candidates(tp2) + case AndType(tp1, tp2) => + candidates(tp1) & candidates(tp2) + case tpw => + Nil + } + } + + def isStructuralSelect(tp: Type): Boolean = tp.stripTypeVar match { + case tp: RefinedType => tp.refinedName == name || isStructuralSelect(tp) + case tp: TypeProxy => isStructuralSelect(tp.underlying) + case AndType(tp1, tp2) => isStructuralSelect(tp1) || isStructuralSelect(tp2) + case _ => false + } + + if (tree.symbol.exists) tree + else { + def choose(qual: Tree, syms: List[Symbol]): Tree = { + def testOrCast(which: Symbol, mbr: Symbol) = + TypeApply(Select(qual, which), TypeTree(mbr.owner.typeRef) :: Nil) + def select(sym: Symbol) = { + val qual1 = + if (qual.tpe derivesFrom sym.owner) qual + else testOrCast(defn.Any_asInstanceOf, sym) + Select(qual1, sym) withPos tree.pos + } + syms match { + case Nil => + def msg = + if (isStructuralSelect(qual.tpe)) + s"cannot access member '$name' from structural type ${qual.tpe.widen.show}; use Dynamic instead" + else + s"no candidate symbols for ${tree.tpe.show} found in ${qual.tpe.show}" + ctx.error(msg, tree.pos) + tree + case sym :: Nil => + select(sym) + case sym :: syms1 => + If(testOrCast(defn.Any_isInstanceOf, sym), select(sym), choose(qual, syms1)) + } + } + evalOnce(qual)(qual => choose(qual, candidates(qual.tpe))) + } + } + + /** Distribute arguments among splitted branches */ + def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { + def recur(fn: Tree): Tree = fn match { + case Block(stats, expr) => Block(stats, recur(expr)) + case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) + case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos + } + recur(tree.fun) + } + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, typeApply) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, apply) + + private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) + private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) } \ No newline at end of file diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index dcd5c67c8649..9665ec206282 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -65,6 +65,8 @@ class tests extends CompilerTest { @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1) @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4) @Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1) + @Test def neg_t0586_structural = compileFile(negDir, "t0586", xerrors = 1) + @Test def neg_t0625_structural = compileFile(negDir, "t0625", xerrors = 1) @Test def neg_t0654_polyalias = compileFile(negDir, "t0654", xerrors = 2) @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1) diff --git a/tests/pos/t0586.scala b/tests/neg/t0586.scala similarity index 100% rename from tests/pos/t0586.scala rename to tests/neg/t0586.scala diff --git a/tests/pos/t0625.scala b/tests/neg/t0625.scala similarity index 100% rename from tests/pos/t0625.scala rename to tests/neg/t0625.scala diff --git a/tests/pos/unions.scala b/tests/pos/unions.scala index 779d1847e97a..f358d6df9ad7 100644 --- a/tests/pos/unions.scala +++ b/tests/pos/unions.scala @@ -2,13 +2,24 @@ object unions { class A { def f: String = "abc" + + def g(x: Int): Int = x + def g(x: Double): Double = x } class B { def f: String = "bcd" + + def g(x: Int) = -x + def g(x: Double): Double = -x } val x: A | B = if (true) new A else new B + def y: B | A = if (true) new A else new B println(x.f) + println(x.g(2)) + println(y.f) + println(y.g(1.0)) + } From f573a56ddc2b4f3c55c3ce1d92903a239896b950 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Apr 2014 09:51:41 +0200 Subject: [PATCH 03/10] Better printing of denotations. Used to print for denotations with a symbol, now prints "some I" where I is the denotation's info.Reworked phases. --- src/dotty/tools/dotc/core/Contexts.scala | 3 +-- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 4f95ff247209..b662326c5ef2 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -189,7 +189,6 @@ object Contexts { private var phasedCtx: Context = _ private var phasedCtxs: Array[Context] = _ - /** This context at given phase. * This method will always return a phase period equal to phaseId, thus will never return squashed phases */ @@ -211,7 +210,7 @@ object Contexts { final def withPhase(phase: Phase): Context = withPhase(phase.id) - /** If -Ydebug is on, the top of the stack trace where this context + /** If -Ydebug is on, the top of the stack trace where this context * was created, otherwise `null`. */ private var creationTrace: Array[StackTraceElement] = _ diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 557a2b5db6f8..f9cd9ec727ce 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -3,7 +3,7 @@ package printing import core._ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._ -import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation +import Contexts.Context, Scopes.Scope, Denotations._, Annotations.Annotation import StdNames.nme import ast.{Trees, untpd} import typer.Namer @@ -475,7 +475,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(flags.flagStrings.filterNot(_.startsWith("<")) map stringToText, " ") } - override def toText(denot: Denotation): Text = toText(denot.symbol) + override def toText(denot: Denotation): Text = denot match { + case denot: MultiDenotation => denot.toString + case _ => + if (denot.symbol.exists) toText(denot.symbol) + else "some " ~ toText(denot.info) + } override def plain = new PlainPrinter(_ctx) } From 879a0b25f2129a1f723953f3cd8f8c82d8ff7f62 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Apr 2014 18:06:25 +0200 Subject: [PATCH 04/10] TreeCheckers use re-typing... ... to check whether tree types make sense. Still produces errors when enabled. --- .../tools/dotc/transform/TreeChecker.scala | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 5913875b832e..f52c5bc1c94d 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -12,8 +12,10 @@ import core.Constants._ import core.StdNames._ import core.Decorators._ import core.transform.Erasure.isUnboundedGeneric +import typer._ import typer.ErrorReporting._ import ast.Trees._ +import ast.{tpd, untpd} /** This transform eliminates patterns. Right now it's a dummy. * Awaiting the real pattern matcher. @@ -23,14 +25,37 @@ class TreeChecker { def check(ctx: Context) = { println(s"checking ${ctx.compilationUnit} after phase ${ctx.phase.prev}") - Checker.transform(ctx.compilationUnit.tpdTree)(ctx) + Checker.typedExpr(ctx.compilationUnit.tpdTree)(ctx) } - object Checker extends TreeMap { - override def transform(tree: Tree)(implicit ctx: Context) = { - println(i"checking $tree") - assert(tree.isEmpty || tree.hasType, tree.show) - super.transform(tree) + object Checker extends ReTyper { + override def typed(tree: untpd.Tree, pt: Type)(implicit ctx: Context) = + if (tree.isEmpty) tree.asInstanceOf[Tree] + else { + assert(tree.hasType, tree.show) + val tree1 = super.typed(tree, pt) + def sameType(tp1: Type, tp2: Type) = + (tp1 eq tp2) || // accept NoType / NoType + (tp1 =:= tp2) + def divergenceMsg = + s"""Types differ + |Original type : ${tree.typeOpt.show} + |After checking: ${tree1.tpe.show} + |Original tree : ${tree.show} + |After checking: ${tree1.show} + """.stripMargin + assert(sameType(tree1.tpe, tree.typeOpt), divergenceMsg) + tree1 + } + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.isTerm, tree.show) + super.typedIdent(tree, pt) + } + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.isTerm, tree.show) + super.typedSelect(tree, pt) } } } From 3486eb0088f78e8ca93c0bc8f883c2af53ee7d3d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Apr 2014 17:57:23 +0200 Subject: [PATCH 05/10] Avoid creating a local dummy when retyping. Retyping should not create new symbols and that includes local dummys. --- src/dotty/tools/dotc/typer/ReTyper.scala | 4 +++- src/dotty/tools/dotc/typer/Typer.scala | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 896dbba7dc2f..c2f627b1e7b9 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -3,7 +3,7 @@ package typer import core.Contexts._ import core.Types._ -import core.Symbols.Symbol +import core.Symbols._ import typer.ProtoTypes._ import ast.{tpd, untpd} import ast.Trees._ @@ -48,6 +48,8 @@ class ReTyper extends Typer { untpd.cpy.Bind(tree, tree.name, body1).withType(tree.typeOpt) } + override def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context) = impl.symbol + override def retrieveSym(tree: untpd.Tree)(implicit ctx: Context): Symbol = tree.symbol override def localTyper(sym: Symbol) = this diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 60acda4b7a84..6e613975b295 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -809,11 +809,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val parents1 = ensureConstrCall(ensureFirstIsClass( parents mapconserve typedParent, cdef.pos.toSynthetic)) val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class memebers are not visible - val localDummy = ctx.newLocalDummy(cls, impl.pos) - val body1 = typedStats(body, localDummy)(inClassContext(self1.symbol)) + val dummy = localDummy(cls, impl) + val body1 = typedStats(body, dummy)(inClassContext(self1.symbol)) checkNoDoubleDefs(cls) val impl1 = cpy.Template(impl, constr1, parents1, self1, body1) - .withType(localDummy.termRef) + .withType(dummy.termRef) assignType(cpy.TypeDef(cdef, mods1, name, impl1), cls) // todo later: check that @@ -825,6 +825,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // 4. Polymorphic type defs override nothing. } + def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context): Symbol = + ctx.newLocalDummy(cls, impl.pos) + def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) checkStable(expr1.tpe, imp.expr.pos) From 09a4bc5d099de71de824a35a67a26e7091e3bb5a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Apr 2014 10:18:33 +0200 Subject: [PATCH 06/10] Reworked TermRef handling The main problem with TermRef handling was that signatures were not always tracked correctly. New invariant: A TermRef that points to a symbol is always a TermRefWithSig, and the signature is the one of the corresponding member. We achieve this by sometimes generating a new TermRefWithSig if a TermRef gets a denotation. One possible simplification would be to always store a signature in a TermRef. There's still a problem in TermRefWithSig#newLikeThis, which currently works only if the previously stored denotation references a symbol. We will need to generalize JointRefDenotation to contain multiple symbols for a complete fix. --- src/dotty/tools/dotc/ast/tpd.scala | 6 +- src/dotty/tools/dotc/config/Config.scala | 7 + src/dotty/tools/dotc/core/Denotations.scala | 2 +- src/dotty/tools/dotc/core/Signature.scala | 6 +- src/dotty/tools/dotc/core/StdNames.scala | 1 + src/dotty/tools/dotc/core/Symbols.scala | 4 +- src/dotty/tools/dotc/core/Types.scala | 138 +++++++++++++++++--- 7 files changed, 137 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 3664263e6d90..9a9ffe243dec 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -29,7 +29,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { untpd.Select(qualifier, tp.name).withType(tp) def Select(qualifier: Tree, sym: Symbol)(implicit ctx: Context): Select = - untpd.Select(qualifier, sym.name).withType(qualifier.tpe select sym) + untpd.Select(qualifier, sym.name).withType( + TermRef.withSig(qualifier.tpe, sym.name.asTermName, sym.signature, sym.denot)) def SelectWithSig(qualifier: Tree, name: Name, sig: Signature)(implicit ctx: Context) = untpd.SelectWithSig(qualifier, name, sig) @@ -259,10 +260,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** new C(args) */ def New(tp: Type, args: List[Tree])(implicit ctx: Context): Apply = { val targs = tp.argTypes + val constr = tp.typeSymbol.primaryConstructor.asTerm Apply( Select( New(tp withoutArgs targs), - TermRef(tp.normalizedPrefix, tp.typeSymbol.primaryConstructor.asTerm)) + TermRef.withSig(tp.normalizedPrefix, constr)) .appliedToTypes(targs), args) } diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index f69566733f1c..9b8bd27ecf71 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -13,6 +13,13 @@ object Config { final val checkConstraintsNonCyclic = true final val flagInstantiationToNothing = false + + /** Enable noDoubleDef checking if option "-YnoDoubleDefs" is set. + * The reason to have an option as well as the present global switch is + * that the noDoubleDef checking is done in a hotspot, and we do not + * want to incur the overhead of checking an option each time. + */ + final val checkTermRefs = false /** Throw an exception if a deep subtype recursion is detected */ final val flagDeepSubTypeRecursions = true diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 0622829c52e2..63b94efbd89f 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -334,7 +334,7 @@ object Denotations { final def info(implicit ctx: Context) = infoOrCompleter final def validFor = denot1.validFor & denot2.validFor final def isType = false - def signature(implicit ctx: Context) = multiHasNot("signature") + final def signature(implicit ctx: Context) = Signature.OverloadedSignature def atSignature(sig: Signature)(implicit ctx: Context): SingleDenotation = denot1.atSignature(sig) orElse denot2.atSignature(sig) def current(implicit ctx: Context): Denotation = diff --git a/src/dotty/tools/dotc/core/Signature.scala b/src/dotty/tools/dotc/core/Signature.scala index 02b91b18e42c..eb85fbb99e08 100644 --- a/src/dotty/tools/dotc/core/Signature.scala +++ b/src/dotty/tools/dotc/core/Signature.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc package core -import Names._, Types._, Contexts._ +import Names._, Types._, Contexts._, StdNames._ import transform.Erasure.sigName /** The signature of a denotation. @@ -49,6 +49,10 @@ object Signature { * a type different from PolyType, MethodType, or ExprType. */ val NotAMethod = Signature(List(), EmptyTypeName) + + /** The signature of an overloaded denotation. + */ + val OverloadedSignature = Signature(List(tpnme.OVERLOADED), EmptyTypeName) /** The signature of a method with no parameters and result type `resultType`. */ def apply(resultType: Type, isJava: Boolean)(implicit ctx: Context): Signature = diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 3982c51f00ee..d01971982ec6 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -109,6 +109,7 @@ object StdNames { val MODULE_VAR_SUFFIX: N = "$module" val NAME_JOIN: N = NameTransformer.NAME_JOIN_STRING val USCORE_PARAM_PREFIX: N = "_$" + val OVERLOADED: N = "" val PACKAGE: N = "package" val PACKAGE_CLS: N = "package$" val PROTECTED_PREFIX: N = "protected$" diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index b4db26ae6cf8..8f66830e828c 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -245,7 +245,7 @@ trait Symbols { this: Context => for (name <- names) { val tparam = newNakedSymbol[TypeName](NoCoord) tparamBuf += tparam - trefBuf += TypeRef(owner.thisType, name) withSym tparam + trefBuf += TypeRef(owner.thisType, name).withSym(tparam, Signature.NotAMethod) } val tparams = tparamBuf.toList val bounds = boundsFn(trefBuf.toList) @@ -319,7 +319,7 @@ object Symbols { type ThisName <: Name private[this] var _id: Int = nextId - //assert(_id != 5859) + //assert(_id != 12325) /** The unique id of this symbol */ def id = _id diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 8799f9fd0474..7561abb100ee 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -656,6 +656,15 @@ object Types { if (res.exists) res else TypeRef(this, name, denot) } + /** The type , reduced if possible, with given denotation if unreduced */ + def selectNonMember(name: Name, denot: Denotation)(implicit ctx: Context): Type = name match { + case name: TermName => + TermRef(this, name, denot) + case name: TypeName => + val res = lookupRefined(name) + if (res.exists) res else TypeRef(this, name, denot) + } + /** The type with given symbol, reduced if possible */ def select(sym: Symbol)(implicit ctx: Context): Type = if (sym.isTerm) TermRef(this, sym.asTerm) @@ -991,6 +1000,8 @@ object Types { val prefix: Type val name: Name + type ThisType >: this.type <: NamedType + assert(prefix.isValueType || (prefix eq NoPrefix), s"invalid prefix $prefix") private[this] var lastDenotation: Denotation = _ @@ -1049,19 +1060,48 @@ object Types { if (owner.isTerm) d else d.asSeenFrom(prefix) } - private[dotc] final def withDenot(denot: Denotation): this.type = { + private def checkSymAssign(sym: Symbol) = + assert( + (lastSymbol eq sym) || + (lastSymbol eq null) || + (lastSymbol.defRunId != sym.defRunId) || + (lastSymbol.defRunId == NoRunId), + s"data race? overwriting symbol of $this / ${this.getClass} / ${lastSymbol.id} / ${sym.id}") + + protected def sig: Signature = Signature.NotAMethod + + private[dotc] def withDenot(denot: Denotation)(implicit ctx: Context): ThisType = + if (sig != denot.signature) + withSig(denot.signature).withDenot(denot).asInstanceOf[ThisType] + else { + setDenot(denot) + this + } + + private[dotc] final def setDenot(denot: Denotation): Unit = { + if (Config.checkTermRefs) checkSymAssign(denot.symbol) lastDenotation = denot lastSymbol = denot.symbol - this } - private[dotc] final def withSym(sym: Symbol): this.type = { + private[dotc] def withSym(sym: Symbol, signature: Signature)(implicit ctx: Context): ThisType = + if (sig != signature) + withSig(signature).withSym(sym, signature).asInstanceOf[ThisType] + else { + setSym(sym) + this + } + + private[dotc] final def setSym(sym: Symbol): Unit = { + if (Config.checkTermRefs) checkSymAssign(sym) lastDenotation = null lastSymbol = sym checkedPeriod = Nowhere - this } + private def withSig(sig: Signature)(implicit ctx: Context): NamedType = + TermRef.withSig(prefix, name.asTermName, sig) + protected def loadDenot(implicit ctx: Context) = { val d = prefix.member(name) if (d.exists || ctx.phaseId == FirstPhaseId) @@ -1136,6 +1176,9 @@ object Types { abstract case class TermRef(override val prefix: Type, name: TermName) extends NamedType with SingletonType { + type ThisType = TermRef + + //assert(name.toString != "") override def underlying(implicit ctx: Context): Type = { val d = denot if (d.isOverloaded) NoType else d.info @@ -1156,16 +1199,31 @@ object Types { } abstract case class TypeRef(override val prefix: Type, name: TypeName) extends NamedType { + + type ThisType = TypeRef + override def underlying(implicit ctx: Context): Type = info } - final class TermRefWithSignature(prefix: Type, name: TermName, val sig: Signature) extends TermRef(prefix, name) { + final class TermRefWithSignature(prefix: Type, name: TermName, override val sig: Signature) extends TermRef(prefix, name) { assert(prefix ne NoPrefix) override def signature(implicit ctx: Context) = sig - override def loadDenot(implicit ctx: Context): Denotation = - super.loadDenot.atSignature(sig) - override def newLikeThis(prefix: Type)(implicit ctx: Context): TermRef = - TermRef.withSig(prefix, name, sig) + override def loadDenot(implicit ctx: Context): Denotation = { + val d = super.loadDenot + if (sig eq Signature.OverloadedSignature) d + else d.atSignature(sig) + } + + override def newLikeThis(prefix: Type)(implicit ctx: Context): TermRef = { + if (sig != Signature.NotAMethod && + sig != Signature.OverloadedSignature && + symbol.exists) { + val ownSym = symbol + TermRef(prefix, name).withDenot(prefix.member(name).disambiguate(_ eq ownSym)) + } + else TermRef.withSig(prefix, name, sig) + } + override def equals(that: Any) = that match { case that: TermRefWithSignature => this.prefix == that.prefix && @@ -1177,15 +1235,25 @@ object Types { override def computeHash = doHash((name, sig), prefix) } - trait WithNoPrefix extends NamedType { + trait WithNonMemberSym extends NamedType { def fixedSym: Symbol assert(fixedSym ne NoSymbol) - withSym(fixedSym) + setSym(fixedSym) + + override def withDenot(denot: Denotation)(implicit ctx: Context): ThisType = { + assert(denot.symbol eq fixedSym) + setDenot(denot) + this + } + + override def withSym(sym: Symbol, signature: Signature)(implicit ctx: Context): ThisType = + unsupported("withSym") + override def equals(that: Any) = that match { - case that: WithNoPrefix => this.fixedSym eq that.fixedSym + case that: WithNonMemberSym => this.prefix == that.prefix && (this.fixedSym eq that.fixedSym) case _ => false } - override def computeHash = doHash(fixedSym) + override def computeHash = doHash(fixedSym, prefix) } final class CachedTermRef(prefix: Type, name: TermName, hc: Int) extends TermRef(prefix, name) { @@ -1200,8 +1268,8 @@ object Types { override def computeHash = unsupported("computeHash") } - final class NoPrefixTermRef(name: TermName, val fixedSym: TermSymbol) extends TermRef(NoPrefix, name) with WithNoPrefix - final class NoPrefixTypeRef(name: TypeName, val fixedSym: TypeSymbol) extends TypeRef(NoPrefix, name) with WithNoPrefix + final class NonMemberTermRef(prefix: Type, name: TermName, val fixedSym: TermSymbol) extends TermRef(prefix, name) with WithNonMemberSym + final class NonMemberTypeRef(prefix: Type, name: TypeName, val fixedSym: TypeSymbol) extends TypeRef(prefix, name) with WithNonMemberSym object NamedType { def apply(prefix: Type, name: Name)(implicit ctx: Context) = @@ -1210,20 +1278,42 @@ object Types { def apply(prefix: Type, name: Name, denot: Denotation)(implicit ctx: Context) = if (name.isTermName) TermRef(prefix, name.asTermName, denot) else TypeRef(prefix, name.asTypeName, denot) + def withNonMemberSym(prefix: Type, sym: Symbol)(implicit ctx: Context) = + if (sym.isType) TypeRef.withNonMemberSym(prefix, sym.name.asTypeName, sym.asType) + else TermRef.withNonMemberSym(prefix, sym.name.asTermName, sym.asTerm) } object TermRef { def apply(prefix: Type, name: TermName)(implicit ctx: Context): TermRef = ctx.uniqueNamedTypes.enterIfNew(prefix, name).asInstanceOf[TermRef] + def apply(prefix: Type, sym: TermSymbol)(implicit ctx: Context): TermRef = withSymAndName(prefix, sym, sym.name) + + def apply(prefix: Type, name: TermName, denot: Denotation)(implicit ctx: Context): TermRef = { + if (prefix eq NoPrefix) apply(prefix, denot.symbol.asTerm) + else denot match { + case denot: SymDenotation if denot.isCompleted => withSig(prefix, name, denot.signature) + case _ => apply(prefix, name) + } + } withDenot denot + + def withNonMemberSym(prefix: Type, name: TermName, sym: TermSymbol)(implicit ctx: Context): TermRef = + unique(new NonMemberTermRef(prefix, name, sym)) + def withSymAndName(prefix: Type, sym: TermSymbol, name: TermName)(implicit ctx: Context): TermRef = - if (prefix eq NoPrefix) unique(new NoPrefixTermRef(name, sym)) - else apply(prefix, name) withSym sym - def apply(prefix: Type, name: TermName, denot: Denotation)(implicit ctx: Context): TermRef = - (if (prefix eq NoPrefix) apply(prefix, denot.symbol.asTerm) else apply(prefix, name)) withDenot denot + if (prefix eq NoPrefix) withNonMemberSym(prefix, name, sym) + else { + if (sym.defRunId != NoRunId && sym.isCompleted) withSig(prefix, name, sym.signature) + else apply(prefix, name) + } withSym (sym, Signature.NotAMethod) + + def withSig(prefix: Type, sym: TermSymbol)(implicit ctx: Context): TermRef = + unique(withSig(prefix, sym.name, sym.signature).withSym(sym, sym.signature)) + def withSig(prefix: Type, name: TermName, sig: Signature)(implicit ctx: Context): TermRef = unique(new TermRefWithSignature(prefix, name, sig)) + def withSig(prefix: Type, name: TermName, sig: Signature, denot: Denotation)(implicit ctx: Context): TermRef = (if (prefix eq NoPrefix) apply(prefix, denot.symbol.asTerm) else withSig(prefix, name, sig)) withDenot denot @@ -1232,11 +1322,17 @@ object Types { object TypeRef { def apply(prefix: Type, name: TypeName)(implicit ctx: Context): TypeRef = ctx.uniqueNamedTypes.enterIfNew(prefix, name).asInstanceOf[TypeRef] + def apply(prefix: Type, sym: TypeSymbol)(implicit ctx: Context): TypeRef = withSymAndName(prefix, sym, sym.name) + + def withNonMemberSym(prefix: Type, name: TypeName, sym: TypeSymbol)(implicit ctx: Context): TypeRef = + unique(new NonMemberTypeRef(prefix, name, sym)) + def withSymAndName(prefix: Type, sym: TypeSymbol, name: TypeName)(implicit ctx: Context): TypeRef = - if (prefix eq NoPrefix) unique(new NoPrefixTypeRef(name, sym)) - else apply(prefix, name) withSym sym + if (prefix eq NoPrefix) withNonMemberSym(prefix, name, sym) + else apply(prefix, name).withSym(sym, Signature.NotAMethod) + def apply(prefix: Type, name: TypeName, denot: Denotation)(implicit ctx: Context): TypeRef = (if (prefix eq NoPrefix) apply(prefix, denot.symbol.asType) else apply(prefix, name)) withDenot denot } From d079c0291289ad9f6517b0b929c4f03ef6b9f082 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Apr 2014 17:55:20 +0200 Subject: [PATCH 07/10] Flag self names that conflict with parameters or members A self name may no longer have the same name as a parameterless class member (or param accessor). The restriction makes sense because otherwise scoping is confusing. It's needed because otherwise we get TermRefs that have the same name and prefix but denote different things. Moved some code which exercises this from pos/typers to neg/typers --- src/dotty/tools/dotc/typer/Checking.scala | 11 ++- src/dotty/tools/dotc/typer/Namer.scala | 2 +- tests/neg/typers.scala | 62 ++++++++++------- tests/pos/typers.scala | 84 ++++++++++++----------- 4 files changed, 92 insertions(+), 67 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 287e93c7af23..ae325af2a449 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -102,13 +102,14 @@ trait Checking { tp } - /** Check that class does not define */ + /** Check that class does not define same symbol twice */ def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = { val seen = new mutable.HashMap[Name, List[Symbol]] { override def default(key: Name) = Nil } typr.println(i"check no double defs $cls") - for (decl <- cls.info.decls) { + + def checkDecl(decl: Symbol): Unit = { for (other <- seen(decl.name)) { typr.println(i"conflict? $decl $other") if (decl.signature matches other.signature) { @@ -129,6 +130,12 @@ trait Checking { } seen(decl.name) = decl :: seen(decl.name) } + + cls.info.decls.foreach(checkDecl) + cls.info match { + case ClassInfo(_, _, _, _, selfSym: Symbol) => checkDecl(selfSym) + case _ => + } } def checkInstantiatable(cls: ClassSymbol, pos: Position): Unit = { diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 2425bea5024a..cce2f73183d9 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -209,7 +209,7 @@ class Namer { typer: Typer => ctx.error(s"${preExisting.showLocated} is compiled twice, runid = ${ctx.runId}", tree.pos) } else if ((!ctx.owner.isClass || name.isTypeName) && preExisting.exists) { - ctx.error(d"$name is already defined as $preExisting") + ctx.error(i"$name is already defined as $preExisting", tree.pos) } } diff --git a/tests/neg/typers.scala b/tests/neg/typers.scala index d25a8911e893..2f1cf40c4500 100644 --- a/tests/neg/typers.scala +++ b/tests/neg/typers.scala @@ -1,40 +1,56 @@ object typers { - + + class A(x: Int) { + val x: String = "a" // error: double def + + { val y: String = "" + val y: Int = 0 // error: double def + y + } + } + + class B { self => // error: double def + def self: Int = 0 + def self(x: Int): Int = x + } + class C { val x: Int - val x: String + val x: String // error: double def val y: Int - def y: String + def y: String // error: double def val z: Int - def z(): String - - def f(x: Any) = () + def z(): String // error: double def + + def f(x: Any) = () // error: double def def f(x: AnyRef): AnyRef - + def g(x: Object): Unit - def g[T](x: T): T = x + def g[T](x: T): T = x // error: double def } - - + + + + object returns { - - def foo(x: Int) = { + + def foo(x: Int) = { // error: has return; needs result type return 3 } - - return 4 + + return 4 // error: return outside method definition } - + object cyclic { - def factorial(acc: Int, n: Int) = + def factorial(acc: Int, n: Int) = if (n == 0) acc - else factorial(acc * n, n - 1) - - def foo(x: Int) = x + else factorial(acc * n, n - 1) // error: cyclic reference + + def foo(x: Int) = x // error: cyclic reference def foo() = foo(1) - + } - + object tries { val x = try { @@ -46,6 +62,6 @@ object typers { } class Refinements { - val y: C { val x: T; type T } + val y: C { val x: T; type T } // error: illegal forward reference in refinement } -} \ No newline at end of file +} diff --git a/tests/pos/typers.scala b/tests/pos/typers.scala index cb3bbc590e44..4f012e7bf306 100644 --- a/tests/pos/typers.scala +++ b/tests/pos/typers.scala @@ -1,61 +1,61 @@ -package test +package test import annotation.{tailrec, switch} import collection.mutable._ object typers { - + val names = List("a", "b", "c") val ints = List(1, 2, 3) - + object Inference { for ((name, n) <- (names, ints).zipped) println(name.length + n) - + def double(x: Char): String = s"$x$x" - + "abc" flatMap double - + } object Eta { - + def fun(x: Int): Int = x + 1 val foo = fun(_) } - + case class DefaultParams(init: String => String = identity) object DefaultParams { def foo(x: String => String = identity) = x("abc") - + foo() } - + class List[+T] { def :: (x: T) = new :: (x, this) - + def len: Int = this match { case x :: xs1 => 1 + xs1.len case Nil => 0 } } - + object Nil extends List[Nothing] - + case class :: [+T] (hd: T, tl: List[T]) extends List[T] - + def len[U](xs: List[U]): Int = xs match { case x :: xs1 => 1 + len(xs1) case Nil => 0 } - + object returns { - + def foo(x: Int): Int = { return 3 } } - + object tries { val x = try { @@ -70,69 +70,71 @@ object typers { val y = try 2 catch Predef.identity val z = try 3 finally "abc" - + println("abc".toString) } class C { - + @tailrec def factorial(acc: Int, n: Int): Int = (n: @switch) match { case 0 => acc case _ => factorial(acc * n, n - 1) } - + println(factorial(1, 10)) - - + + } - + class Refinements { val y: C { type T; val key: T; def process(x: T): Int } } - + object Accessibility { - + class A { val x: String = "abc" } - + class B extends A { private def x: Int = 1 } - + val b: B = new B val y = b.x val z: String = y - + } - + object Self { - - class A(self: Int) { self => - + + class A(self1: Int) { self => + + def self1(x: Int) = x + class B { val b = self val c: A = b } - + val a = self val c: A = a } - - + + } - + object Arrays { - + val arr = List("a", "b", "c").toArray val i = 2 arr(i).charAt(0) - + val x = new ArrayBuffer[String] // testing overloaded polymorphic constructors val entries = Array("abc", "def") - + for ((x, i) <- entries.zipWithIndex) println(x) } @@ -145,6 +147,6 @@ object typers { } val yy: String = y } - -} \ No newline at end of file + +} From c6af272d6d68c708cb87b12f25e61dd0a9717f09 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Apr 2014 18:12:53 +0200 Subject: [PATCH 08/10] Option for testing for double bindings A double binding is if a named type gets assigned two denotations in the same period. This is a side-effect and also a race condition, so it's very bad. I am trying to eliminate all causes of this. But one cause which will likely remain are double defitions in a prgram, if a user writes class C { val x: Int val x: Int } Then it's really hard to avoid setting two meanings of C.this.x! That's why the testing against double bindings is enabled by a -YnoDoubleBindings option. The understanding is that -YnoDoubleBindings should be set only if there are no double def errors anticipated. Otherwise the program might fail with an assertion error before the double def error is reported. --- .../tools/dotc/config/ScalaSettings.scala | 1 + src/dotty/tools/dotc/core/Types.scala | 18 +++++++++++++----- test/dotc/tests.scala | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 144e146c155c..aab2942bdfe4 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -163,6 +163,7 @@ class ScalaSettings extends Settings.SettingGroup { val Ytyperdebug = BooleanSetting("-Ytyper-debug", "Trace all type assignments.") val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.") val Yexplainlowlevel = BooleanSetting("-Yexplainlowlevel", "When explaining type errors, show types at a lower level.") + val YnoDoubleBindings = BooleanSetting("-YnoDoubleBindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 7561abb100ee..1dba728bb438 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1078,8 +1078,10 @@ object Types { this } - private[dotc] final def setDenot(denot: Denotation): Unit = { - if (Config.checkTermRefs) checkSymAssign(denot.symbol) + private[dotc] final def setDenot(denot: Denotation)(implicit ctx: Context): Unit = { + if (Config.checkTermRefs) + if (ctx.settings.YnoDoubleBindings.value) + checkSymAssign(denot.symbol) lastDenotation = denot lastSymbol = denot.symbol } @@ -1092,8 +1094,14 @@ object Types { this } - private[dotc] final def setSym(sym: Symbol): Unit = { - if (Config.checkTermRefs) checkSymAssign(sym) + private[dotc] final def setSym(sym: Symbol)(implicit ctx: Context): Unit = { + if (Config.checkTermRefs) + if (ctx.settings.YnoDoubleBindings.value) + checkSymAssign(sym) + uncheckedSetSym(sym) + } + + private[dotc] final def uncheckedSetSym(sym: Symbol): Unit = { lastDenotation = null lastSymbol = sym checkedPeriod = Nowhere @@ -1238,7 +1246,7 @@ object Types { trait WithNonMemberSym extends NamedType { def fixedSym: Symbol assert(fixedSym ne NoSymbol) - setSym(fixedSym) + uncheckedSetSym(fixedSym) override def withDenot(denot: Denotation)(implicit ctx: Context): ThisType = { assert(denot.symbol eq fixedSym) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 9665ec206282..3228c3474c28 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,7 +14,7 @@ class tests extends CompilerTest { // "-Yshow-suppressed-errors", "-pagewidth", "160" ) - val twice = List("#runs", "2") + val twice = List("#runs", "2", "-YnoDoubleBindings") val doErase = List("-Ystop-before:terminal") val posDir = "./tests/pos/" @@ -55,7 +55,7 @@ class tests extends CompilerTest { @Test def neg_typedapply() = compileFile(negDir, "typedapply", xerrors = 4) @Test def neg_typedidents() = compileFile(negDir, "typedIdents", xerrors = 2) @Test def neg_assignments() = compileFile(negDir, "assignments", xerrors = 3) - @Test def neg_typers() = compileFile(negDir, "typers", xerrors = 10) + @Test def neg_typers() = compileFile(negDir, "typers", xerrors = 13) @Test def neg_privates() = compileFile(negDir, "privates", xerrors = 2) @Test def neg_rootImports = compileFile(negDir, "rootImplicits", xerrors = 2) @Test def neg_templateParents() = compileFile(negDir, "templateParents", xerrors = 3) From ebd7df51e5b20e2c42717e216cc6be996d12c52a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Apr 2014 18:24:35 +0200 Subject: [PATCH 09/10] Cleanups Conflicts: src/dotty/tools/dotc/transform/Splitter.scala --- src/dotty/tools/dotc/typer/Namer.scala | 1 - src/dotty/tools/dotc/typer/TypeAssigner.scala | 3 ++- tests/pos/alias.scala | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 tests/pos/alias.scala diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index cce2f73183d9..2d4bf809951c 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -474,7 +474,6 @@ class Namer { typer: Typer => index(rest)(inClassContext(selfInfo)) denot.info = ClassInfo(cls.owner.thisType, cls, parentRefs, decls, selfInfo) - // make sure constr parameters are all entered because we refer to them in desugarings: } } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 5821b58b2699..b194bdd608c9 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -109,7 +109,8 @@ trait TypeAssigner { // it could be that we found an inaccessbile private member, but there is // an inherited non-private member with the same name and signature. val d2 = pre.nonPrivateMember(name) - if (reallyExists(d2) && firstTry) test(pre.select(name, d2), false) + if (reallyExists(d2) && firstTry) + test(pre.select(name, d2), false) else { val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) val what = alts match { diff --git a/tests/pos/alias.scala b/tests/pos/alias.scala new file mode 100644 index 000000000000..a66edc73a121 --- /dev/null +++ b/tests/pos/alias.scala @@ -0,0 +1,3 @@ +class A(val x: Int) + +class B(x: Int) extends A(x) From dcfd96328f350a6265d7aac55c411ab798e93e77 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Apr 2014 10:24:25 +0200 Subject: [PATCH 10/10] Shadowed references In TypeAssigner#ensureAccible we sometimes pick an inherited public member as the denotation of a NamedType instead of an inaccessible private one. The problem is that both are denotations for the same type, which caused a noDoubleBindings assert failure. We now solve this problem by creating a "shadowed" named type to hold the inherited member. The shadowed named type is distinguished by its name, which reads (inherited)originalName In the future, we should make this more robust by using a general tagging scheme to create shadowed names. Another fix is about import symbols. They are now referenced with NonMemberTermRefs. With this fix, the test suite passes with no double def violations. --- src/dotty/tools/dotc/config/Config.scala | 4 ++-- src/dotty/tools/dotc/core/NameOps.scala | 9 ++++++++ src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 6 ++++- src/dotty/tools/dotc/core/Types.scala | 22 +++++++++---------- src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 ++-- tests/pos/unions.scala | 7 ++++++ 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 9b8bd27ecf71..e77b10bfbaec 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -13,13 +13,13 @@ object Config { final val checkConstraintsNonCyclic = true final val flagInstantiationToNothing = false - + /** Enable noDoubleDef checking if option "-YnoDoubleDefs" is set. * The reason to have an option as well as the present global switch is * that the noDoubleDef checking is done in a hotspot, and we do not * want to incur the overhead of checking an option each time. */ - final val checkTermRefs = false + final val checkNoDoubleBindings = true /** Throw an exception if a deep subtype recursion is detected */ final val flagDeepSubTypeRecursions = true diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 35f94aac7ddf..22a7e5734048 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -50,6 +50,9 @@ object NameOps { implicit class NameDecorator[N <: Name](val name: N) extends AnyVal { import nme._ + def likeTyped(n: Name): N = + (if (name.isTermName) n.toTermName else n.toTypeName).asInstanceOf[N] + def isConstructorName = name == CONSTRUCTOR || name == IMPLCLASS_CONSTRUCTOR def isExceptionResultName = name startsWith EXCEPTION_RESULT_PREFIX def isImplClassName = name endsWith IMPL_CLASS_SUFFIX @@ -62,6 +65,8 @@ object NameOps { def isTraitSetterName = isSetterName && (name containsSlice TRAIT_SETTER_SEPARATOR) def isSingletonName = name endsWith SINGLETON_SUFFIX def isModuleClassName = name endsWith MODULE_SUFFIX + def isImportName = name startsWith IMPORT + def isInheritedName = name.head == '(' && name.startsWith(nme.INHERITED) def isModuleVarName(name: Name): Boolean = name.stripAnonNumberSuffix endsWith MODULE_VAR_SUFFIX @@ -132,6 +137,10 @@ object NameOps { if (idx < 0) name else (name drop (idx + separator.length)).asInstanceOf[N] } + def inheritedName: N = likeTyped(nme.INHERITED ++ name) + + def revertInherited: N = likeTyped(name.drop(nme.INHERITED.length)) + /** Translate a name into a list of simple TypeNames and TermNames. * In all segments before the last, type/term is determined by whether * the following separator char is '.' or '#'. The last segment diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index d01971982ec6..cb4272f7a267 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -100,6 +100,7 @@ object StdNames { val EXPAND_SEPARATOR: N = "$$" val IMPL_CLASS_SUFFIX: N = "$class" val IMPORT: N = "" + val INHERITED: N = "(inherited)" // tag to be used until we have proper name kinds val INTERPRETER_IMPORT_WRAPPER: N = "$iw" val INTERPRETER_LINE_PREFIX: N = "line" val INTERPRETER_VAR_PREFIX: N = "res" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 7762f61dd7c5..dc76999f61b2 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -684,7 +684,8 @@ object SymDenotations { /** The symbol, in class `inClass`, that is overridden by this denotation. */ final def overriddenSymbol(inClass: ClassSymbol)(implicit ctx: Context): Symbol = - matchingSymbol(inClass, owner.thisType) + if ((this is Private) && (owner ne inClass)) NoSymbol + else matchingSymbol(inClass, owner.thisType) /** All symbols overriden by this denotation. */ final def allOverriddenSymbols(implicit ctx: Context): Iterator[Symbol] = @@ -731,6 +732,9 @@ object SymDenotations { override def termRefWithSig(implicit ctx: Context): TermRef = TermRef.withSig(owner.thisType, name.asTermName, signature, this) + def nonMemberTermRef(implicit ctx: Context): TermRef = + TermRef.withNonMemberSym(owner.thisType, name.asTermName, symbol.asTerm) + /** The variance of this type parameter or type member as an Int, with * +1 = Covariant, -1 = Contravariant, 0 = Nonvariant, or not a type parameter */ diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 1dba728bb438..c348e246c646 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -656,15 +656,6 @@ object Types { if (res.exists) res else TypeRef(this, name, denot) } - /** The type , reduced if possible, with given denotation if unreduced */ - def selectNonMember(name: Name, denot: Denotation)(implicit ctx: Context): Type = name match { - case name: TermName => - TermRef(this, name, denot) - case name: TypeName => - val res = lookupRefined(name) - if (res.exists) res else TypeRef(this, name, denot) - } - /** The type with given symbol, reduced if possible */ def select(sym: Symbol)(implicit ctx: Context): Type = if (sym.isTerm) TermRef(this, sym.asTerm) @@ -1079,7 +1070,7 @@ object Types { } private[dotc] final def setDenot(denot: Denotation)(implicit ctx: Context): Unit = { - if (Config.checkTermRefs) + if (Config.checkNoDoubleBindings) if (ctx.settings.YnoDoubleBindings.value) checkSymAssign(denot.symbol) lastDenotation = denot @@ -1095,7 +1086,7 @@ object Types { } private[dotc] final def setSym(sym: Symbol)(implicit ctx: Context): Unit = { - if (Config.checkTermRefs) + if (Config.checkNoDoubleBindings) if (ctx.settings.YnoDoubleBindings.value) checkSymAssign(sym) uncheckedSetSym(sym) @@ -1111,7 +1102,9 @@ object Types { TermRef.withSig(prefix, name.asTermName, sig) protected def loadDenot(implicit ctx: Context) = { - val d = prefix.member(name) + val d = + if (name.isInheritedName) prefix.nonPrivateMember(name.revertInherited) + else prefix.member(name) if (d.exists || ctx.phaseId == FirstPhaseId) d else {// name has changed; try load in earlier phase and make current @@ -1172,6 +1165,11 @@ object Types { protected def newLikeThis(prefix: Type)(implicit ctx: Context): NamedType = NamedType(prefix, name) + /** Create a NamedType of the same kind as this type, but with a new name. + */ + final def shadowed(implicit ctx: Context): NamedType = + NamedType(prefix, name.inheritedName) + override def equals(that: Any) = that match { case that: NamedType => this.name == that.name && diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index b194bdd608c9..12f2415d89da 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -110,7 +110,7 @@ trait TypeAssigner { // an inherited non-private member with the same name and signature. val d2 = pre.nonPrivateMember(name) if (reallyExists(d2) && firstTry) - test(pre.select(name, d2), false) + test(tpe.shadowed.withDenot(d2), false) else { val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) val what = alts match { @@ -326,7 +326,7 @@ trait TypeAssigner { tree.withType(sym.typeRef) def assignType(tree: untpd.Import, sym: Symbol)(implicit ctx: Context) = - tree.withType(sym.termRef) + tree.withType(sym.nonMemberTermRef) def assignType(tree: untpd.Annotated, annot: Tree, arg: Tree)(implicit ctx: Context) = tree.withType(AnnotatedType(Annotation(annot), arg.tpe)) diff --git a/tests/pos/unions.scala b/tests/pos/unions.scala index f358d6df9ad7..e57a96fb9010 100644 --- a/tests/pos/unions.scala +++ b/tests/pos/unions.scala @@ -21,5 +21,12 @@ object unions { println(y.f) println(y.g(1.0)) + class C { + private def foo = 0 + class D extends C { + private def foo = 1 + def test(cd: C | D, dc: D | C) = (cd.foo, dc.foo) + } + } }