From f36c21ed9f79a1fc41e36d7c5221a070d83e61a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 May 2016 09:22:17 +0200 Subject: [PATCH 01/32] Avoid merging denotations of different symbols in same class #1240 shows that we need to detect ambiguous overloads of methods coming from the same base class (with different signatures there) that have the same signature in some deriving class. This was undetected before because the two methods were simply merged into one overloaded alternative. --- src/dotty/tools/dotc/core/Denotations.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 946738d73378..8003e68ac960 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -282,8 +282,15 @@ object Denotations { val info2 = denot2.info val sym1 = denot1.symbol val sym2 = denot2.symbol - val sym2Accessible = sym2.isAccessibleFrom(pre) + if (sym1.exists && sym2.exists && + (sym1 ne sym2) && (sym1.owner eq sym2.owner) && + !sym1.is(Bridge) && !sym2.is(Bridge)) + // double definition of two methods with same signature in one class; + // don't merge them. + return NoDenotation + + val sym2Accessible = sym2.isAccessibleFrom(pre) /** Does `sym1` come before `sym2` in the linearization of `pre`? */ def precedes(sym1: Symbol, sym2: Symbol) = { def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match { From cc0f62954e814200f47f978b857abf6ab039c9f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 May 2016 09:23:53 +0200 Subject: [PATCH 02/32] Don't copy Any constructor to Object in Erasure Once we do not merge methods with same signature anymore we got an ambiguous overload between the constructors of Any and Object after erasure (when all Any methods are moved to Object). To avoid this, we map the Any constructor to the Object constructor after erasure. --- src/dotty/tools/dotc/transform/Erasure.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 0b3a07f65833..ee8040168a17 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -41,13 +41,19 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => // Aftre erasure, all former Any members are now Object members val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info val extendedScope = decls.cloneScope - defn.AnyClass.classInfo.decls.foreach(extendedScope.enter) + for (decl <- defn.AnyClass.classInfo.decls) + if (!decl.isConstructor) extendedScope.enter(decl) ref.copySymDenotation( info = transformInfo(ref.symbol, ClassInfo(pre, defn.ObjectClass, ps, extendedScope, selfInfo)) ) } else { + val oldSymbol = ref.symbol + val newSymbol = + if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor) + defn.ObjectClass.primaryConstructor + else oldSymbol val oldOwner = ref.owner val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner val oldInfo = ref.info @@ -55,10 +61,10 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => val oldFlags = ref.flags val newFlags = ref.flags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading // TODO: define derivedSymDenotation? - if ((oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref + if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref else { assert(!ref.is(Flags.PackageClass), s"trans $ref @ ${ctx.phase} oldOwner = $oldOwner, newOwner = $newOwner, oldInfo = $oldInfo, newInfo = $newInfo ${oldOwner eq newOwner} ${oldInfo eq newInfo}") - ref.copySymDenotation(owner = newOwner, initFlags = newFlags, info = newInfo) + ref.copySymDenotation(symbol = newSymbol, owner = newOwner, initFlags = newFlags, info = newInfo) } } case ref => From d0f05ad6c756355bb6ea863e10a554f54f145907 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 May 2016 09:25:01 +0200 Subject: [PATCH 03/32] ResolveOverloaded should handle alternatives that are the same TermRef This happens once we do not merge methods with the same signature coming from the same class. --- src/dotty/tools/dotc/typer/Applications.scala | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 390ecaee97e3..4f7a1c22077d 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -867,8 +867,6 @@ trait Applications extends Compatibility { self: Typer => */ def isAsGood(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Boolean = track("isAsGood") { ctx.traceIndented(i"isAsGood($alt1, $alt2)", overload) { - assert(alt1 ne alt2) - /** Is class or module class `sym1` derived from class or module class `sym2`? * Module classes also inherit the relationship from their companions. */ @@ -975,14 +973,20 @@ trait Applications extends Compatibility { self: Typer => bestSoFar } val best = winner(alt, alts1) - def asGood(alts: List[TermRef]): List[TermRef] = alts match { + // A tricky corner case is where we have two methods that have the same signature + // when seen from a particulatr prefix. In that case the TermRefs of two + // alternatives are identical. Hence, we have to make sure (using `altSeen`) that we + // eliminate only one alternative that's identical to `best`. + // A test case is in neg/i1240.scala + def asGood(alts: List[TermRef], altSeen: Boolean): List[TermRef] = alts match { case alt :: alts1 => - if ((alt eq best) || !isAsGood(alt, best)) asGood(alts1) - else alt :: asGood(alts1) + if (!altSeen && (alt eq best)) asGood(alts1, altSeen = true) + else if (!isAsGood(alt, best)) asGood(alts1, altSeen) + else alt :: asGood(alts1, altSeen) case nil => Nil } - best :: asGood(alts) + best :: asGood(alts, altSeen = false) } } From 2dd6a7ae07cca7b03372d6b9f1fb30e0fcc975b7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 May 2016 09:25:11 +0200 Subject: [PATCH 04/32] Test case --- test/dotc/tests.scala | 3 ++- tests/neg/customArgs/i1240.scala | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/neg/customArgs/i1240.scala diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index b646c72d5573..97c872bb6eb2 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -119,10 +119,11 @@ class tests extends CompilerTest { @Test def neg_typedIdents() = compileDir(negDir, "typedIdents") val negCustomArgs = negDir + "customArgs/" - @Test def neg_typers() = compileFile(negCustomArgs, "typers")(allowDoubleBindings) + @Test def neg_typers = compileFile(negCustomArgs, "typers")(allowDoubleBindings) @Test def neg_overrideClass = compileFile(negCustomArgs, "overrideClass", List("-language:Scala2")) @Test def neg_autoTupling = compileFile(negCustomArgs, "autoTuplingTest", args = "-language:noAutoTupling" :: Nil) @Test def neg_i1050 = compileFile(negCustomArgs, "i1050", List("-strict")) + @Test def neg_i1240 = compileFile(negCustomArgs, "i1240")(allowDoubleBindings) val negTailcallDir = negDir + "tailcall/" @Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b") diff --git a/tests/neg/customArgs/i1240.scala b/tests/neg/customArgs/i1240.scala new file mode 100644 index 000000000000..6235e8815d27 --- /dev/null +++ b/tests/neg/customArgs/i1240.scala @@ -0,0 +1,40 @@ +package test + +class C[T] { + + def foo(x: D) = { System.out.println("D foo"); } + def foo(x: T) = { System.out.println("T foo"); } +} + +object C { + def main(args: Array[String]) = + new C[D]().foo(new D()) // error: ambiguous +} +/* +class C1[T] { + def foo(x: D) = { System.out.println("D foo"); } +} +class C2[T] { + def foo(x: D) = { System.out.println("D foo"); } +} + +class D {} + +// more complicated example +abstract class A { + type C[X] + def foo[B](x: C[B]): C[B] = {println("A.C"); x} + def foo[B](x: List[B]): List[B] = {println("A.List"); x} + def give[X]: C[X] +} + +class B extends A { + type C[X] = List[X] + override def give[X] = Nil + override def foo[B](x: C[B]): C[B] = {println("B.C"); x} + // which method is overriden? + // should any bridges be generated? + val a: A = this + a.foo(a.give[Int]) // what method should be called here in runtime? +} +*/ From 968f1ab0cd706de8833112741407f94a6a0b2677 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 May 2016 11:34:38 +0200 Subject: [PATCH 05/32] Fix test case --- tests/neg/customArgs/i1240.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neg/customArgs/i1240.scala b/tests/neg/customArgs/i1240.scala index 6235e8815d27..438199b4a63b 100644 --- a/tests/neg/customArgs/i1240.scala +++ b/tests/neg/customArgs/i1240.scala @@ -10,7 +10,7 @@ object C { def main(args: Array[String]) = new C[D]().foo(new D()) // error: ambiguous } -/* + class C1[T] { def foo(x: D) = { System.out.println("D foo"); } } @@ -37,4 +37,4 @@ class B extends A { val a: A = this a.foo(a.give[Int]) // what method should be called here in runtime? } -*/ + From 48b716012bd72486dbf4a2bd3b293ef212f4addd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 May 2016 15:51:41 +0200 Subject: [PATCH 06/32] Issue MergeError exception for double def situations When finding two symbols in the same class that have the same signature as seen from some prefix, issue a merge error. This is simpler and more robust than the alternative of producing an overloaded denotation and dealing with it afterwards. --- src/dotty/tools/dotc/core/Denotations.scala | 37 ++++++++--- src/dotty/tools/dotc/transform/Erasure.scala | 68 +++++++++++--------- tests/neg/customArgs/i1240.scala | 20 +----- tests/neg/i1240a.scala | 17 +++++ 4 files changed, 85 insertions(+), 57 deletions(-) create mode 100644 tests/neg/i1240a.scala diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 8003e68ac960..0bcb3198b615 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -283,12 +283,7 @@ object Denotations { val sym1 = denot1.symbol val sym2 = denot2.symbol - if (sym1.exists && sym2.exists && - (sym1 ne sym2) && (sym1.owner eq sym2.owner) && - !sym1.is(Bridge) && !sym2.is(Bridge)) - // double definition of two methods with same signature in one class; - // don't merge them. - return NoDenotation + if (isDoubleDef(sym1, sym2)) doubleDefError(denot1, denot2, pre) val sym2Accessible = sym2.isAccessibleFrom(pre) /** Does `sym1` come before `sym2` in the linearization of `pre`? */ @@ -425,8 +420,12 @@ object Denotations { final def validFor = denot1.validFor & denot2.validFor final def isType = false final def signature(implicit ctx: Context) = Signature.OverloadedSignature - def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): SingleDenotation = - denot1.atSignature(sig, site, relaxed) orElse denot2.atSignature(sig, site, relaxed) + def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): SingleDenotation = { + val atSig1 = denot1.atSignature(sig, site, relaxed) + val atSig2 = denot2.atSignature(sig, site, relaxed) + if (isDoubleDef(atSig1.symbol, atSig2.symbol)) doubleDefError(atSig1, atSig2, site) + atSig1.orElse(atSig2) + } def currentIfExists(implicit ctx: Context): Denotation = derivedMultiDenotation(denot1.currentIfExists, denot2.currentIfExists) def current(implicit ctx: Context): Denotation = @@ -907,6 +906,28 @@ object Denotations { */ case class NoQualifyingRef(alts: List[SingleDenotation])(implicit ctx: Context) extends ErrorDenotation + /** A double defifinition + */ + def isDoubleDef(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Boolean = + (sym1.exists && sym2.exists && + (sym1 ne sym2) && (sym1.owner eq sym2.owner) && + !sym1.is(Bridge) && !sym2.is(Bridge)) + + def doubleDefError(denot1: SingleDenotation, denot2: SingleDenotation, pre: Type)(implicit ctx: Context): Unit = { + val sym1 = denot1.symbol + val sym2 = denot2.symbol + def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre" + throw new MergeError( + i"""cannot merge + | $sym1: ${sym1.info} and + | $sym2: ${sym2.info}; + |they are both defined in ${sym1.owner} but have matching signatures + | ${denot1.info} and + | ${denot2.info}$fromWhere""".stripMargin, + denot2.info, denot2.info) + } + + // --------------- PreDenotations ------------------------------------------------- /** A PreDenotation represents a group of single denotations diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index ee8040168a17..24dea5118e7d 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -559,43 +559,47 @@ object Erasure extends TypeTestsCasts{ before match { case Nil => emittedBridges.toList case (oldMember: untpd.DefDef) :: oldTail => - val oldSymbol = oldMember.symbol(beforeCtx) - val newSymbol = member.symbol(ctx) - assert(oldSymbol.name(beforeCtx) == newSymbol.name, - s"${oldSymbol.name(beforeCtx)} bridging with ${newSymbol.name}") - val newOverridden = oldSymbol.denot.allOverriddenSymbols.toSet // TODO: clarify new <-> old in a comment; symbols are swapped here - val oldOverridden = newSymbol.allOverriddenSymbols(beforeCtx).toSet // TODO: can we find a more efficient impl? newOverridden does not have to be a set! - def stillInBaseClass(sym: Symbol) = ctx.owner derivesFrom sym.owner - val neededBridges = (oldOverridden -- newOverridden).filter(stillInBaseClass) - - var minimalSet = Set[Symbol]() - // compute minimal set of bridges that are needed: - for (bridge <- neededBridges) { - val isRequired = minimalSet.forall(nxtBridge => !(bridge.info =:= nxtBridge.info)) - - if (isRequired) { - // check for clashes - val clash: Option[Symbol] = oldSymbol.owner.info.decls.lookupAll(bridge.name).find { - sym => - (sym.name eq bridge.name) && sym.info.widen =:= bridge.info.widen - }.orElse( + try { + val oldSymbol = oldMember.symbol(beforeCtx) + val newSymbol = member.symbol(ctx) + assert(oldSymbol.name(beforeCtx) == newSymbol.name, + s"${oldSymbol.name(beforeCtx)} bridging with ${newSymbol.name}") + val newOverridden = oldSymbol.denot.allOverriddenSymbols.toSet // TODO: clarify new <-> old in a comment; symbols are swapped here + val oldOverridden = newSymbol.allOverriddenSymbols(beforeCtx).toSet // TODO: can we find a more efficient impl? newOverridden does not have to be a set! + def stillInBaseClass(sym: Symbol) = ctx.owner derivesFrom sym.owner + val neededBridges = (oldOverridden -- newOverridden).filter(stillInBaseClass) + + var minimalSet = Set[Symbol]() + // compute minimal set of bridges that are needed: + for (bridge <- neededBridges) { + val isRequired = minimalSet.forall(nxtBridge => !(bridge.info =:= nxtBridge.info)) + + if (isRequired) { + // check for clashes + val clash: Option[Symbol] = oldSymbol.owner.info.decls.lookupAll(bridge.name).find { + sym => + (sym.name eq bridge.name) && sym.info.widen =:= bridge.info.widen + }.orElse( emittedBridges.find(stat => (stat.name == bridge.name) && stat.tpe.widen =:= bridge.info.widen) - .map(_.symbol) - ) - clash match { - case Some(cl) => - ctx.error(i"bridge for method ${newSymbol.showLocated(beforeCtx)} of type ${newSymbol.info(beforeCtx)}\n" + - i"clashes with ${cl.symbol.showLocated(beforeCtx)} of type ${cl.symbol.info(beforeCtx)}\n" + - i"both have same type after erasure: ${bridge.symbol.info}") - case None => minimalSet += bridge + .map(_.symbol)) + clash match { + case Some(cl) => + ctx.error(i"bridge for method ${newSymbol.showLocated(beforeCtx)} of type ${newSymbol.info(beforeCtx)}\n" + + i"clashes with ${cl.symbol.showLocated(beforeCtx)} of type ${cl.symbol.info(beforeCtx)}\n" + + i"both have same type after erasure: ${bridge.symbol.info}") + case None => minimalSet += bridge + } } } - } - val bridgeImplementations = minimalSet.map { - sym => makeBridgeDef(member, sym)(ctx) + val bridgeImplementations = minimalSet.map { + sym => makeBridgeDef(member, sym)(ctx) + } + emittedBridges ++= bridgeImplementations + } catch { + case ex: MergeError => ctx.error(ex.getMessage, member.pos) } - emittedBridges ++= bridgeImplementations + traverse(newTail, oldTail, emittedBridges) case notADefDef :: oldTail => traverse(after, oldTail, emittedBridges) diff --git a/tests/neg/customArgs/i1240.scala b/tests/neg/customArgs/i1240.scala index 438199b4a63b..3f4d1e210592 100644 --- a/tests/neg/customArgs/i1240.scala +++ b/tests/neg/customArgs/i1240.scala @@ -20,21 +20,7 @@ class C2[T] { class D {} -// more complicated example -abstract class A { - type C[X] - def foo[B](x: C[B]): C[B] = {println("A.C"); x} - def foo[B](x: List[B]): List[B] = {println("A.List"); x} - def give[X]: C[X] +class X { + def foo(x: D): D + def foo(x: D): D // error: already defined } - -class B extends A { - type C[X] = List[X] - override def give[X] = Nil - override def foo[B](x: C[B]): C[B] = {println("B.C"); x} - // which method is overriden? - // should any bridges be generated? - val a: A = this - a.foo(a.give[Int]) // what method should be called here in runtime? -} - diff --git a/tests/neg/i1240a.scala b/tests/neg/i1240a.scala new file mode 100644 index 000000000000..dc711454e0a6 --- /dev/null +++ b/tests/neg/i1240a.scala @@ -0,0 +1,17 @@ + +// more complicated example +abstract class A { + type C[X] + def foo[B](x: C[B]): C[B] = {println("A.C"); x} + def foo[B](x: List[B]): List[B] = {println("A.List"); x} + def give[X]: C[X] +} + +class B extends A { + type C[X] = List[X] + override def give[X] = Nil + override def foo[B](x: C[B]): C[B] = {println("B.C"); x} // error: merge error during erasure + val a: A = this + a.foo(a.give[Int]) // what method should be called here in runtime? +} + From f722de7c6131b544345dbc6745b5d219de5831e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 May 2016 15:56:52 +0200 Subject: [PATCH 07/32] A test case for overloading/overriding interactions This showcases a tricky interaction between overloading and overriding. See discussion of #1240 for context. --- tests/run/i1240.scala | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/run/i1240.scala diff --git a/tests/run/i1240.scala b/tests/run/i1240.scala new file mode 100644 index 000000000000..860806ba5705 --- /dev/null +++ b/tests/run/i1240.scala @@ -0,0 +1,27 @@ +// A tricky case of overriding behavior +// Note: It would be acceptable if this produced an error instead. +// Bit testing this is tricky. +abstract class Base[T] { + def foo(x: T): String +} + +class C[T] extends Base[T] { + + def foo(x: D): String = "D foo" + def foo(x: T): String = "T foo" +} + +object Test { + def main(args: Array[String]) = { + val b1: Base[D] = new C[D] // which of the two foo's in C overrides the one in B? + assert(b1.foo(new D) == "T foo") + val b2: Base[D] = new C[D] {} + // In Java, this gives an error like this: + // methods foo(A) from C[D] and foo(String) from C[D] are inherited with the same signature + // But the analogous example with `b1` compiles OK in Java. + assert(b2.foo(new D) == "T foo") + } +} + +class D + From fe5f4f3963d2d6b2f6514a362fb312bc2e7d7f94 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 May 2016 15:58:44 +0200 Subject: [PATCH 08/32] Revert: ResolveOverloaded should handle alternatives that are the same TermRef This happens once we do not merge methods with the same signature coming from the same class. (reverted from commit 83262d090a98e2374c9b3e5a1480892397d695d3) This case no longer applies as such a situation will now give a MergeError instead. --- src/dotty/tools/dotc/typer/Applications.scala | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 4f7a1c22077d..390ecaee97e3 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -867,6 +867,8 @@ trait Applications extends Compatibility { self: Typer => */ def isAsGood(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Boolean = track("isAsGood") { ctx.traceIndented(i"isAsGood($alt1, $alt2)", overload) { + assert(alt1 ne alt2) + /** Is class or module class `sym1` derived from class or module class `sym2`? * Module classes also inherit the relationship from their companions. */ @@ -973,20 +975,14 @@ trait Applications extends Compatibility { self: Typer => bestSoFar } val best = winner(alt, alts1) - // A tricky corner case is where we have two methods that have the same signature - // when seen from a particulatr prefix. In that case the TermRefs of two - // alternatives are identical. Hence, we have to make sure (using `altSeen`) that we - // eliminate only one alternative that's identical to `best`. - // A test case is in neg/i1240.scala - def asGood(alts: List[TermRef], altSeen: Boolean): List[TermRef] = alts match { + def asGood(alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - if (!altSeen && (alt eq best)) asGood(alts1, altSeen = true) - else if (!isAsGood(alt, best)) asGood(alts1, altSeen) - else alt :: asGood(alts1, altSeen) + if ((alt eq best) || !isAsGood(alt, best)) asGood(alts1) + else alt :: asGood(alts1) case nil => Nil } - best :: asGood(alts, altSeen = false) + best :: asGood(alts) } } From 9aa800f8a9905de059b6e34554cb166a9776dff3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 May 2016 19:57:57 +0200 Subject: [PATCH 09/32] Refined handling of atSignature We cannot throw a merge error if atSignature does not give a unique single denotation. Counter example is compiling dotty itself, where we get a false negative during bridge generation. Instead, atSigature needs to return a normal denotation, and we need to check separately where required that a denotation is in fact a SingleDenotation. --- src/dotty/tools/dotc/core/Denotations.scala | 48 ++++++++++--------- src/dotty/tools/dotc/core/Types.scala | 4 +- src/dotty/tools/dotc/transform/Splitter.scala | 2 +- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 0bcb3198b615..080e8a39ba58 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -122,11 +122,11 @@ object Denotations { /** The signature of the denotation. */ def signature(implicit ctx: Context): Signature - /** Resolve overloaded denotation to pick the one with the given signature + /** Resolve overloaded denotation to pick the ones with the given signature * when seen from prefix `site`. * @param relaxed When true, consider only parameter signatures for a match. */ - def atSignature(sig: Signature, site: Type = NoPrefix, relaxed: Boolean = false)(implicit ctx: Context): SingleDenotation + def atSignature(sig: Signature, site: Type = NoPrefix, relaxed: Boolean = false)(implicit ctx: Context): Denotation /** The variant of this denotation that's current in the given context, or * `NotDefinedHereDenotation` if this denotation does not exist at current phase, but @@ -157,7 +157,10 @@ object Denotations { * or NoDenotation if no satisfying alternative exists. * @throws TypeError if there is at more than one alternative that satisfies `p`. */ - def suchThat(p: Symbol => Boolean): SingleDenotation + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation + + /** If this is a SingleDenotation, return it, otherwise throw a TypeError */ + def checkUnique(implicit ctx: Context): SingleDenotation = suchThat(alwaysTrue) /** Does this denotation have an alternative that satisfies the predicate `p`? */ def hasAltWith(p: SingleDenotation => Boolean): Boolean @@ -227,13 +230,17 @@ object Denotations { /** The alternative of this denotation that has a type matching `targetType` when seen * as a member of type `site`, `NoDenotation` if none exists. */ - def matchingDenotation(site: Type, targetType: Type)(implicit ctx: Context): SingleDenotation = - if (isOverloaded) - atSignature(targetType.signature, site, relaxed = true).matchingDenotation(site, targetType) - else if (exists && !site.memberInfo(symbol).matchesLoosely(targetType)) - NoDenotation - else - asSingleDenotation + def matchingDenotation(site: Type, targetType: Type)(implicit ctx: Context): SingleDenotation = { + def qualifies(sym: Symbol) = site.memberInfo(sym).matchesLoosely(targetType) + if (isOverloaded) { + atSignature(targetType.signature, site, relaxed = true) match { + case sd: SingleDenotation => sd.matchingDenotation(site, targetType) + case md => md.suchThat(qualifies(_)) + } + } + else if (exists && !qualifies(symbol)) NoDenotation + else asSingleDenotation + } /** Form a denotation by conjoining with denotation `that`. * @@ -420,23 +427,21 @@ object Denotations { final def validFor = denot1.validFor & denot2.validFor final def isType = false final def signature(implicit ctx: Context) = Signature.OverloadedSignature - def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): SingleDenotation = { - val atSig1 = denot1.atSignature(sig, site, relaxed) - val atSig2 = denot2.atSignature(sig, site, relaxed) - if (isDoubleDef(atSig1.symbol, atSig2.symbol)) doubleDefError(atSig1, atSig2, site) - atSig1.orElse(atSig2) - } + def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.atSignature(sig, site, relaxed), denot2.atSignature(sig, site, relaxed)) def currentIfExists(implicit ctx: Context): Denotation = derivedMultiDenotation(denot1.currentIfExists, denot2.currentIfExists) def current(implicit ctx: Context): Denotation = derivedMultiDenotation(denot1.current, denot2.current) def altsWith(p: Symbol => Boolean): List[SingleDenotation] = denot1.altsWith(p) ++ denot2.altsWith(p) - def suchThat(p: Symbol => Boolean): SingleDenotation = { + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = { val sd1 = denot1.suchThat(p) val sd2 = denot2.suchThat(p) if (sd1.exists) - if (sd2.exists) throw new TypeError(s"failure to disambiguate overloaded reference $this") + if (sd2.exists) + if (isDoubleDef(denot1.symbol, denot2.symbol)) doubleDefError(denot1, denot2) + else throw new TypeError(s"failure to disambiguate overloaded reference $this") else sd1 else sd2 } @@ -486,7 +491,7 @@ object Denotations { def altsWith(p: Symbol => Boolean): List[SingleDenotation] = if (exists && p(symbol)) this :: Nil else Nil - def suchThat(p: Symbol => Boolean): SingleDenotation = + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = if (exists && p(symbol)) this else NoDenotation def hasAltWith(p: SingleDenotation => Boolean): Boolean = @@ -906,14 +911,14 @@ object Denotations { */ case class NoQualifyingRef(alts: List[SingleDenotation])(implicit ctx: Context) extends ErrorDenotation - /** A double defifinition + /** A double definition */ def isDoubleDef(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Boolean = (sym1.exists && sym2.exists && (sym1 ne sym2) && (sym1.owner eq sym2.owner) && !sym1.is(Bridge) && !sym2.is(Bridge)) - def doubleDefError(denot1: SingleDenotation, denot2: SingleDenotation, pre: Type)(implicit ctx: Context): Unit = { + def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = { val sym1 = denot1.symbol val sym2 = denot2.symbol def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre" @@ -927,7 +932,6 @@ object Denotations { denot2.info, denot2.info) } - // --------------- PreDenotations ------------------------------------------------- /** A PreDenotation represents a group of single denotations diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 8ef0e9fd140a..3ad43a05e519 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1437,7 +1437,7 @@ object Types { asMemberOf(prefix) match { case NoDenotation => d.current case newd: SingleDenotation => newd - case newd => newd.atSignature(d.signature).orElse(d.current) + case newd => newd.atSignature(d.signature).checkUnique.orElse(d.current) } private def denotOfSym(sym: Symbol)(implicit ctx: Context): Denotation = { @@ -1729,7 +1729,7 @@ object Types { override def loadDenot(implicit ctx: Context): Denotation = { val d = super.loadDenot if (sig eq Signature.OverloadedSignature) d - else d.atSignature(sig) + else d.atSignature(sig).checkUnique } override def newLikeThis(prefix: Type)(implicit ctx: Context): TermRef = { diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index 410b412e05d9..efcf95ede016 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -46,7 +46,7 @@ class Splitter extends MiniPhaseTransform { thisTransform => val mbr = tp.member(name) if (!mbr.isOverloaded) mbr.asSingleDenotation else tree.tpe match { - case tref: TermRefWithSignature => mbr.atSignature(tref.sig) + case tref: TermRefWithSignature => mbr.atSignature(tref.sig).checkUnique case _ => def alts = mbr.alternatives.map(alt => i"$alt: ${alt.info}").mkString(", ") ctx.error(s"cannot disambiguate overloaded members $alts", tree.pos) From 3a97b3fb546bc57afb8cbfadb27eb9e66b0842d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 3 May 2016 11:00:29 +0200 Subject: [PATCH 10/32] Another test case involving super accessors --- tests/neg/i1240b.scala | 17 +++++++++++++++++ tests/run/i1240.scala | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tests/neg/i1240b.scala diff --git a/tests/neg/i1240b.scala b/tests/neg/i1240b.scala new file mode 100644 index 000000000000..6987dbc96d06 --- /dev/null +++ b/tests/neg/i1240b.scala @@ -0,0 +1,17 @@ +// yet another variant, testing super accessors + +trait T { + def foo[B](x: C[B]): C[B] +} +abstract class A extends T { + type C[X] + def foo[B](x: C[B]): C[B] = {println("A.C"); x} + def foo[B](x: List[B]): List[B] = {println("A.List"); x} +} +trait U extends T { + def foo[B](x: C[B]): C[B] = super.foo[B](x) +} +object Test extends A with U { + type C[X] = List[X] + def main(args: Array[String]) = foo(List("")) +} diff --git a/tests/run/i1240.scala b/tests/run/i1240.scala index 860806ba5705..7092d91314e4 100644 --- a/tests/run/i1240.scala +++ b/tests/run/i1240.scala @@ -1,6 +1,6 @@ // A tricky case of overriding behavior -// Note: It would be acceptable if this produced an error instead. -// Bit testing this is tricky. +// Note: It might be acceptable if this produced an error instead. +// But testing this is tricky. abstract class Base[T] { def foo(x: T): String } From b26b725ef25a55a325b2c8a44c1ac67272632f14 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 3 May 2016 11:23:24 +0200 Subject: [PATCH 11/32] Handle MergeErrors in RefChecks Used to throw an uncaught merge error in checkAllOverrides when compiling i1240c.scala. --- src/dotty/tools/dotc/typer/RefChecks.scala | 6 +++++- tests/neg/i1240b.scala | 23 +++++++++------------- tests/neg/i1240c.scala | 17 ++++++++++++++++ 3 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 tests/neg/i1240c.scala diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index 37c2fe48f90a..a654bb08f734 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -837,7 +837,7 @@ class RefChecks extends MiniPhase { thisTransformer => if (tree.symbol is Macro) EmptyTree else tree } - override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = try { val cls = ctx.owner checkOverloadedRestrictions(cls) checkParents(cls) @@ -845,6 +845,10 @@ class RefChecks extends MiniPhase { thisTransformer => checkAllOverrides(cls) checkDerivedValueClass(cls, tree.body) tree + } catch { + case ex: MergeError => + ctx.error(ex.getMessage, tree.pos) + tree } override def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo) = { diff --git a/tests/neg/i1240b.scala b/tests/neg/i1240b.scala index 6987dbc96d06..2d23db61470b 100644 --- a/tests/neg/i1240b.scala +++ b/tests/neg/i1240b.scala @@ -1,17 +1,12 @@ -// yet another variant, testing super accessors - -trait T { - def foo[B](x: C[B]): C[B] +// yet another variant, testing type parameters +trait T[X] { + def foo(x: X): X } -abstract class A extends T { - type C[X] - def foo[B](x: C[B]): C[B] = {println("A.C"); x} - def foo[B](x: List[B]): List[B] = {println("A.List"); x} +abstract class A[X] extends T[X] { + def foo(x: X): X = {println("A.X"); x} + def foo(x: String): String = {println("A.String"); x} } -trait U extends T { - def foo[B](x: C[B]): C[B] = super.foo[B](x) -} -object Test extends A with U { - type C[X] = List[X] - def main(args: Array[String]) = foo(List("")) +trait U[X] extends T[X] { + abstract override def foo(x: X): X = super.foo(x) } +object Test extends A[String] with U[String] // error: accidental override diff --git a/tests/neg/i1240c.scala b/tests/neg/i1240c.scala new file mode 100644 index 000000000000..89fded2927ca --- /dev/null +++ b/tests/neg/i1240c.scala @@ -0,0 +1,17 @@ +// yet another variant, testing super accessors +// (but exhibited a crash in RefChecks). + +trait T { + def foo[B](x: C[B]): C[B] +} +abstract class A extends T { + type C[X] + def foo[B](x: C[B]): C[B] = {println("A.C"); x} + def foo[B](x: List[B]): List[B] = {println("A.List"); x} +} +trait U extends T { + abstract override def foo[B](x: C[B]): C[B] = super.foo[B](x) +} +object Test extends A with U { + type C[X] = List[X] +} From c9ac3d7267fe53dfbd3f57658049637ab218b3d5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 16 May 2016 18:47:39 +0200 Subject: [PATCH 12/32] Remove stray test Real test is in neg/customargs --- tests/neg/i1240c.scala | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 tests/neg/i1240c.scala diff --git a/tests/neg/i1240c.scala b/tests/neg/i1240c.scala deleted file mode 100644 index 89fded2927ca..000000000000 --- a/tests/neg/i1240c.scala +++ /dev/null @@ -1,17 +0,0 @@ -// yet another variant, testing super accessors -// (but exhibited a crash in RefChecks). - -trait T { - def foo[B](x: C[B]): C[B] -} -abstract class A extends T { - type C[X] - def foo[B](x: C[B]): C[B] = {println("A.C"); x} - def foo[B](x: List[B]): List[B] = {println("A.List"); x} -} -trait U extends T { - abstract override def foo[B](x: C[B]): C[B] = super.foo[B](x) -} -object Test extends A with U { - type C[X] = List[X] -} From c29e9754b94cc352337791c9e36131f5b8be385d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 11:32:44 +0200 Subject: [PATCH 13/32] Fix dotc bootstrap failure During an attempted dotty bootstrap it was noted that Types.scala did not compile anymore, because `checkUnique` threw a `TypeError` during erasure. The issue was an overloaded member `name` in TermrefWithSig. In NamedType: def name: Name In TermRef: def name: TermName Before erasure, there's one member `name`, after erasure there are two (because after erasure result type counts). The error arose when trying to recompute a member of a `TermRefWithSig` where the name is `name` and the expected signature is `(Nil, ?)`. Since there are two members that match the name and the signature, `checkUnique` triggered a `TypeError`. Before adding `checkUnique`, the previous `atSignature` call would just have returned an arbitrary choice among the two alternative definitions of `name`. The fix is not to use `checkUnique` but to fall back to `d.current` in the case where several alternatives appear. Interestingly, the failure only triggers when -Ycheck options are *disabled*. I added a new test that compiles Types.scala without checks, so we catch this and possibly similar bugs in the future. --- src/dotty/tools/dotc/core/Types.scala | 6 +++++- test/dotc/tests.scala | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 3ad43a05e519..114e6c9082c4 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1437,7 +1437,11 @@ object Types { asMemberOf(prefix) match { case NoDenotation => d.current case newd: SingleDenotation => newd - case newd => newd.atSignature(d.signature).checkUnique.orElse(d.current) + case newd => + newd.atSignature(d.signature) match { + case newd1: SingleDenotation if newd1.exists => newd1 + case _ => d.current + } } private def denotOfSym(sym: Symbol)(implicit ctx: Context): Denotation = { diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 97c872bb6eb2..de35d281b28c 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -157,6 +157,7 @@ class tests extends CompilerTest { @Test def dotc_ast = compileDir(dotcDir, "ast") @Test def dotc_config = compileDir(dotcDir, "config") @Test def dotc_core = compileDir(dotcDir, "core")("-Yno-double-bindings" :: allowDeepSubtypes)// twice omitted to make tests run faster + @Test def dotc_core_Types = compileFile(dotcDir, "core/Types")(noCheckOptions) // This directory doesn't exist anymore // @Test def dotc_core_pickling = compileDir(coreDir, "pickling")(allowDeepSubtypes)// twice omitted to make tests run faster From f1d95c286c802504b00b469bc924a39760fc9e73 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:14:11 +0200 Subject: [PATCH 14/32] Fix test The previous additional test messed up partest in that file Types.scala was copied twice into the partest-generated directory and then the pos/core tests would compile both copies. This gave a double definition which manifested itself under -Yno-double-bindings as an assertion error. Ideally, partest generation would guard against this situation. For now I avoid the problem by compiling the whole of core without -Ycheck, not jst Types.scala. --- test/dotc/tests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index de35d281b28c..3528dfa72edf 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -156,8 +156,8 @@ class tests extends CompilerTest { @Test def dotc_ast = compileDir(dotcDir, "ast") @Test def dotc_config = compileDir(dotcDir, "config") - @Test def dotc_core = compileDir(dotcDir, "core")("-Yno-double-bindings" :: allowDeepSubtypes)// twice omitted to make tests run faster - @Test def dotc_core_Types = compileFile(dotcDir, "core/Types")(noCheckOptions) + @Test def dotc_core = compileDir(dotcDir, "core")(allowDeepSubtypes)// twice omitted to make tests run faster + @Test def dotc_core_nocheck = compileDir(dotcDir, "core")(noCheckOptions) // This directory doesn't exist anymore // @Test def dotc_core_pickling = compileDir(coreDir, "pickling")(allowDeepSubtypes)// twice omitted to make tests run faster From c87c0303e40ef7e0103b2606c744bc7a2b61ece4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:15:18 +0200 Subject: [PATCH 15/32] Better doc comment --- src/dotty/annotation/internal/Child.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dotty/annotation/internal/Child.scala b/src/dotty/annotation/internal/Child.scala index 23ff2a97c00c..4f3ceb6c09c6 100644 --- a/src/dotty/annotation/internal/Child.scala +++ b/src/dotty/annotation/internal/Child.scala @@ -2,5 +2,14 @@ package dotty.annotation.internal import scala.annotation.Annotation -/** An annotation to indicate a child class or object of the annotated class. */ +/** An annotation to indicate a child class or object of the annotated class. + * E.g. if we have + * + * sealed class A + * case class B() extends A + * case class C() extends A + * + * Then `A` would carry the annotations `@Child[B] @Child[C]` where + * `B`, `C` are TypeRefs. + */ class Child[T] extends Annotation From 77642b925cdea436755ce6e26e794e48c37b3b1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:15:51 +0200 Subject: [PATCH 16/32] Two more tests Unrelated to other commits but useful to get in. --- tests/pos/chan.scala | 20 ++++++++++++++++++++ tests/run/t7685-class-simple.scala | 10 ++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/pos/chan.scala create mode 100644 tests/run/t7685-class-simple.scala diff --git a/tests/pos/chan.scala b/tests/pos/chan.scala new file mode 100644 index 000000000000..ea8eb2b547cb --- /dev/null +++ b/tests/pos/chan.scala @@ -0,0 +1,20 @@ +trait Comparator { + type T // abstract type member, to be filled in by concrete classes + def ordering: Ordering[T] + def compare(a: T, b: T): Int = ordering.compare(a, b) +} + +object IntComparator extends Comparator { + type T = Int + def ordering: Ordering[Int] = Ordering.Int +} +object Test { + def process(c: Comparator)(items: Seq[c.T]): Int = { + c.compare(items(0), items(1)) + } +} +class Processor[K](c: Comparator { type T = K }) { + def process(items: Seq[K]): Int = { + c.compare(items(0), items(1)) + } +} diff --git a/tests/run/t7685-class-simple.scala b/tests/run/t7685-class-simple.scala new file mode 100644 index 000000000000..5147a66c0543 --- /dev/null +++ b/tests/run/t7685-class-simple.scala @@ -0,0 +1,10 @@ +object Test { + final class Foo(val x: String) extends AnyVal { override def toString = "" + x } + final class Bar(val f: Foo) extends AnyVal { override def toString = "" + f } + def main(args: Array[String]) = { + val x = "abc" + val f = new Foo(x) + val b = new Bar(f) + assert(b.toString == "abc") + } +} From f4e7f8401a07bf84e480f34ed870542e0432844e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:20:38 +0200 Subject: [PATCH 17/32] Use source module ref as assumed self type when reading Tasty When reading Tasty we need to pre-set the info of a class to some ClassInfoType with (as yet) unknown parents and self type. But for module classes, we need to know the source module at all time, and this gets determined by the self type. So we now produce a TermRef for the assumed self type of a module class. --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 535ddd216693..7cc390ed08bf 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -646,7 +646,11 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val cls = ctx.owner.asClass def setClsInfo(parents: List[TypeRef], selfType: Type) = cls.info = ClassInfo(cls.owner.thisType, cls, parents, cls.unforcedDecls, selfType) - setClsInfo(Nil, NoType) + val assumedSelfType = + if (cls.is(Module) && cls.owner.isClass) + TermRef.withSig(cls.owner.thisType, cls.name.sourceModuleName, Signature.NotAMethod) + else NoType + setClsInfo(Nil, assumedSelfType) val localDummy = ctx.newLocalDummy(cls) assert(readByte() == TEMPLATE) val end = readEnd() @@ -659,7 +663,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } } val parentRefs = ctx.normalizeToClassRefs(parents.map(_.tpe), cls, cls.unforcedDecls) - val self = + val self = if (nextByte == SELFDEF) { readByte() untpd.ValDef(readName(), readTpt(), EmptyTree).withType(NoType) From ad73126c7e61baa911c965237120d57f79318cfe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:24:38 +0200 Subject: [PATCH 18/32] Disable stub checking It caused an assertion error when separately compiling parts of dotty against TASTY information. Not sure the test achieves anything or whether it produces a false negative. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7cc390ed08bf..b8622680b893 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -44,7 +44,16 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { private val unpickledSyms = new mutable.HashSet[Symbol] private val treeAtAddr = new mutable.HashMap[Addr, Tree] private val typeAtAddr = new mutable.HashMap[Addr, Type] // currently populated only for types that are known to be SHAREd. - private var stubs: Set[Symbol] = Set() + + // Currently disabled set used for checking that all + // already encountered symbols are forward refereneces. This + // check fails in more complicated scenarios of separate + // compilation in dotty (for instance: compile all of `core` + // given the TASTY files of everything else in the compiler). + // I did not have the time to track down what caused the failure. + // The testing scheme could well have produced a false negative. + // + // private var stubs: Set[Symbol] = Set() private var roots: Set[SymDenotation] = null @@ -155,7 +164,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { forkAt(addr).createSymbol() val sym = symAtAddr(addr) ctx.log(i"forward reference to $sym") - stubs += sym + // stubs += sym sym } } @@ -405,8 +414,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { else { val sym = symAtAddr.get(start) match { case Some(preExisting) => - assert(stubs contains preExisting) - stubs -= preExisting + //assert(stubs contains preExisting, preExisting) + //stubs -= preExisting preExisting case none => ctx.newNakedSymbol(start.index) From 87489f5ec3875df75042f6643f438e1b24455490 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 08:32:02 +0200 Subject: [PATCH 19/32] Further improve doc comment --- src/dotty/annotation/internal/Child.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotty/annotation/internal/Child.scala b/src/dotty/annotation/internal/Child.scala index 4f3ceb6c09c6..9295de73e2c6 100644 --- a/src/dotty/annotation/internal/Child.scala +++ b/src/dotty/annotation/internal/Child.scala @@ -9,7 +9,8 @@ import scala.annotation.Annotation * case class B() extends A * case class C() extends A * - * Then `A` would carry the annotations `@Child[B] @Child[C]` where - * `B`, `C` are TypeRefs. + * Then the class symbol `A` would carry the annotations + * `@Child[Bref] @Child[Cref]` where `Bref`, `Cref` are TypeRefs + * referring to the class symbols of `B` and `C` */ class Child[T] extends Annotation From 1e3b0e1dbac78063190e7c91e45328e54dd1643d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 08:32:39 +0200 Subject: [PATCH 20/32] Decouple annotation transformers from info transformers --- .../tools/dotc/transform/CheckReentrant.scala | 2 +- .../tools/dotc/transform/CheckStatic.scala | 2 +- .../tools/dotc/transform/FirstTransform.scala | 7 +++-- .../tools/dotc/transform/TreeTransform.scala | 30 +++++++------------ 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/dotty/tools/dotc/transform/CheckReentrant.scala b/src/dotty/tools/dotc/transform/CheckReentrant.scala index 2569b3aaef47..7e0e368b577c 100644 --- a/src/dotty/tools/dotc/transform/CheckReentrant.scala +++ b/src/dotty/tools/dotc/transform/CheckReentrant.scala @@ -3,7 +3,7 @@ package transform import core._ import Names._ -import dotty.tools.dotc.transform.TreeTransforms.{AnnotationTransformer, TransformerInfo, MiniPhaseTransform, TreeTransformer} +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, MiniPhaseTransform, TreeTransformer} import ast.Trees._ import Flags._ import Types._ diff --git a/src/dotty/tools/dotc/transform/CheckStatic.scala b/src/dotty/tools/dotc/transform/CheckStatic.scala index 445e9f83958d..77c6dfc51952 100644 --- a/src/dotty/tools/dotc/transform/CheckStatic.scala +++ b/src/dotty/tools/dotc/transform/CheckStatic.scala @@ -5,7 +5,7 @@ import core._ import Names._ import StdNames.nme import Types._ -import dotty.tools.dotc.transform.TreeTransforms.{AnnotationTransformer, TransformerInfo, MiniPhaseTransform, TreeTransformer} +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, MiniPhaseTransform, TreeTransformer} import ast.Trees._ import Flags._ import Contexts.Context diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index ae72d5f6c7be..6e1fed6078cc 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -30,7 +30,7 @@ import StdNames._ * - stubs out native methods * - eliminate self tree in Template and self symbol in ClassInfo */ -class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer with AnnotationTransformer { thisTransformer => +class FirstTransform extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => import ast.tpd._ override def phaseName = "firstTransform" @@ -46,12 +46,13 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi } /** eliminate self symbol in ClassInfo */ - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case tp@ClassInfo(_, _, _, _, self: Symbol) => + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case tp @ ClassInfo(_, _, _, _, self: Symbol) => tp.derivedClassInfo(selfInfo = self.info) case _ => tp } + /* tp match { //create companions for value classes that are not from currently compiled source file diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index abada9ab47a8..89ae927b78d6 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -174,32 +174,22 @@ object TreeTransforms { } /** A helper trait to transform annotations on MemberDefs */ - trait AnnotationTransformer extends MiniPhaseTransform with InfoTransformer { + trait AnnotationTransformer extends MiniPhaseTransform with DenotTransformer { val annotationTransformer = mkTreeTransformer override final def treeTransformPhase = this // need to run at own phase because otherwise we get ahead of ourselves in transforming denotations - override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = { - val info1 = transformInfo(ref.info, ref.symbol) - - ref match { - case ref: SymDenotation => - val annots1 = - if (!ref.symbol.isDefinedInCurrentRun) ref.annotations // leave annotations read from class files alone - else { - val annotTrees = ref.annotations.map(_.tree) - val annotTrees1 = annotTrees.mapConserve(annotationTransformer.macroTransform) - if (annotTrees eq annotTrees1) ref.annotations - else annotTrees1.map(new ConcreteAnnotation(_)) - } - if ((info1 eq ref.info) && (annots1 eq ref.annotations)) ref - else ref.copySymDenotation(info = info1, annotations = annots1) - case _ => - if (info1 eq ref.info) ref - else ref.derivedSingleDenotation(ref.symbol, info1) + abstract override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = + super.transform(ref) match { + case ref1: SymDenotation if ref1.symbol.isDefinedInCurrentRun => + val annotTrees = ref1.annotations.map(_.tree) + val annotTrees1 = annotTrees.mapConserve(annotationTransformer.macroTransform) + if (annotTrees eq annotTrees1) ref1 + else ref1.copySymDenotation(annotations = annotTrees1.map(new ConcreteAnnotation(_))) + case ref1 => + ref1 } - } } @sharable val NoTransform = new TreeTransform { From 40696ba72d528107e55f3ab8f4ec0aa512a17aac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 10:24:23 +0200 Subject: [PATCH 21/32] Instrument Denotations#current to find CyclicReference Instrument Denotations#current to find CyclicReference errors arising during transforms. --- src/dotty/tools/dotc/core/Denotations.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 080e8a39ba58..5dfc5c17d58c 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -656,7 +656,13 @@ object Denotations { var startPid = nextTransformerId + 1 val transformer = ctx.denotTransformers(nextTransformerId) //println(s"transforming $this with $transformer") - next = transformer.transform(cur)(ctx.withPhase(transformer)).syncWithParents + try { + next = transformer.transform(cur)(ctx.withPhase(transformer)).syncWithParents + } catch { + case ex: CyclicReference => + println(s"error while transforming $this") // DEBUG + throw ex + } if (next eq cur) startPid = cur.validFor.firstPhaseId else { From 2443760b398ba9cd0bd9230339428785d3ab4cf6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 10:27:57 +0200 Subject: [PATCH 22/32] Don't force a symbol's denotation for isTerm/isType Forcing it led to CyclicReferences involving RefChecks.OptLevelInfo when compiling dotc/*.scala against Tasty files. The problem was that when transforming OptLevelInfo the backend forced a transformInfo of RefChecks in TypeErasure which filtered RefCheck's scope to eliminate non-class type definitions. Without the tweak in this commit this tried to make all symbols current, and so came back to OptLevelInfo. --- src/dotty/tools/dotc/core/Symbols.scala | 4 ++-- src/dotty/tools/dotc/core/TypeErasure.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index d40acdfa797b..b9458b1339f5 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -398,10 +398,10 @@ object Symbols { /** Subclass tests and casts */ final def isTerm(implicit ctx: Context): Boolean = - (if(isDefinedInCurrentRun) lastDenot else denot).isTerm + (if (defRunId == ctx.runId) lastDenot else denot).isTerm final def isType(implicit ctx: Context): Boolean = - (if(isDefinedInCurrentRun) lastDenot else denot).isType + (if (defRunId == ctx.runId) lastDenot else denot).isType final def isClass: Boolean = isInstanceOf[ClassSymbol] diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 89077897a3ab..39d02e069ca7 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -374,7 +374,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean tr1 :: trs1.filterNot(_ isRef defn.ObjectClass) case nil => nil } - val erasedDecls = decls.filteredScope(d => !d.isType || d.isClass) + val erasedDecls = decls.filteredScope(sym => !sym.isType || sym.isClass) tp.derivedClassInfo(NoPrefix, parents, erasedDecls, erasedRef(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } From 09eddd01423939db27ee52668eb14e9b87b3350c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 13:08:56 +0200 Subject: [PATCH 23/32] Make sure local data is unpickled at right phase We had a problem where unpickling an annotation containing a class constant had the wrong type. Unpickling was done after erasure. The type given to the constant was an alias but aliases got eliminated during erasure, so the constant was malformed. Unpickling annotation contents at the same phase as unpickling the annotation carrier solves the problem. It seems similar problems can arise when data is unpickled using a LocalUnpickler. So we now make sure local unpickling runs at the latest at phase Pickler. --- .../tools/dotc/core/unpickleScala2/Scala2Unpickler.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index cceaed53d767..71a919ca357e 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -573,7 +573,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas // println(s"unpickled ${denot.debugString}, info = ${denot.info}") !!! DEBUG } atReadPos(startCoord(denot).toIndex, - () => parseToCompletion(denot)(ctx.addMode(Mode.Scala2Unpickling))) + () => parseToCompletion(denot)( + ctx.addMode(Mode.Scala2Unpickling).withPhaseNoLater(ctx.picklerPhase))) } catch { case ex: RuntimeException => handleRuntimeException(ex) } @@ -922,7 +923,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas val start = readIndex val atp = readTypeRef() Annotation.deferred( - atp.typeSymbol, implicit ctx => atReadPos(start, () => readAnnotationContents(end))) + atp.typeSymbol, implicit ctx1 => + atReadPos(start, () => readAnnotationContents(end)(ctx1.withPhase(ctx.phase)))) } /* Read an abstract syntax tree */ From daf736ce103072c3bd388a1cc8b6a9a6c0bab79c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 14:05:19 +0200 Subject: [PATCH 24/32] Replace aliases to Unit by Unit Do this in the inferred (result-)type of ValDefs and DefDefs. Without this fix, TreeTraverser#traverseChildren in Trees.scala gets a result type of BoxedUnit (but only when co-compiled from source, not when unpickled). --- src/dotty/tools/dotc/typer/Namer.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 82b3b56e9714..38e62e9c600b 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -824,13 +824,21 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") def isInline = sym.is(Final, butNot = Method) + + // Widen rhs type and approximate `|' but keep ConstantTypes if + // definition is inline (i.e. final in Scala2). def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { case tp: ConstantType if isInline => tp case _ => tp.widen.approximateUnion } + + // Replace aliases to Unit by Unit itself. If we leave the alias in + // it would be erased to BoxedUnit. + def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp + val rhsCtx = ctx.addMode(Mode.InferringReturnType) def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe - def cookedRhsType = ctx.deskolemize(widenRhs(rhsType)) + def cookedRhsType = ctx.deskolemize(dealiasIfUnit(widenRhs(rhsType))) lazy val lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.pos) //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") if (inherited.exists) From 47555708f47f5049e7b184d05a6acee508534281 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 16:35:51 +0200 Subject: [PATCH 25/32] Fix withPhaseNoLater It's possible that the given phase argument does not exist, in which case we do not want to set the current phase to NoPhase. --- src/dotty/tools/dotc/core/Contexts.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index bbe8e920c60b..e1aeac8c3491 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -248,7 +248,7 @@ object Contexts { withPhase(phase.id) final def withPhaseNoLater(phase: Phase) = - if (ctx.phase.id > phase.id) withPhase(phase) else ctx + if (phase.exists && ctx.phase.id > phase.id) withPhase(phase) else ctx /** If -Ydebug is on, the top of the stack trace where this context * was created, otherwise `null`. From e0c2e4dd8de66c3250c084a0a6a899f8e0dda86f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 15:51:11 +0200 Subject: [PATCH 26/32] Remove fingerprinting Trying the dotc bootstrap revealed that Frozen logic can lead to assertion errors. So we want to remove it, but then taking fingerprints would no longer be cost-effective. Note: In local checking removing fingerprinting did cost some performance: junit test time went from 590s to 604s. We should watch the checkin benchmarks to see whether this gets confirmed. --- src/dotty/tools/dotc/config/Config.scala | 1 - .../tools/dotc/core/SymDenotations.scala | 92 +++++-------------- 2 files changed, 24 insertions(+), 69 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 3cc3091b5a17..7aeae413c180 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -4,7 +4,6 @@ object Config { final val cacheMembersNamed = true final val cacheAsSeenFrom = true - final val useFingerPrints = true // note: it currently seems to be slightly faster not to use them! my junit test: 548s without, 560s with. final val cacheMemberNames = true final val cacheImplicitScopes = true diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 78acd8f1a23f..1e25138083c2 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1299,7 +1299,6 @@ object SymDenotations { override def invalidateInheritedInfo(): Unit = { myBaseClasses = null mySuperClassBits = null - myMemberFingerPrint = FingerPrint.unknown myMemberCache = null myMemberCachePeriod = Nowhere memberNamesCache = SimpleMap.Empty @@ -1418,42 +1417,6 @@ object SymDenotations { final override def typeParamCreationFlags = ClassTypeParamCreationFlags - private[this] var myMemberFingerPrint: FingerPrint = FingerPrint.unknown - - private def computeMemberFingerPrint(implicit ctx: Context): FingerPrint = { - var fp = FingerPrint() - var e = info.decls.lastEntry - while (e != null) { - fp.include(e.name) - e = e.prev - } - var ps = classParents - while (ps.nonEmpty) { - val parent = ps.head.typeSymbol - parent.denot match { - case parentDenot: ClassDenotation => - fp.include(parentDenot.memberFingerPrint) - if (parentDenot.isFullyCompleted) parentDenot.setFlag(Frozen) - case _ => - } - ps = ps.tail - } - fp - } - - /** A bloom filter for the names of all members in this class. - * Makes sense only for parent classes, and should definitely - * not be used for package classes because cache never - * gets invalidated. - */ - def memberFingerPrint(implicit ctx: Context): FingerPrint = - if (myMemberFingerPrint != FingerPrint.unknown) myMemberFingerPrint - else { - val fp = computeMemberFingerPrint - if (isFullyCompleted) myMemberFingerPrint = fp - fp - } - private[this] var myMemberCache: LRUCache[Name, PreDenotation] = null private[this] var myMemberCachePeriod: Period = Nowhere @@ -1499,13 +1462,12 @@ object SymDenotations { /** Enter a symbol in given `scope` without potentially replacing the old copy. */ def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = { - require((sym.denot.flagsUNSAFE is Private) || !(this is Frozen) || (scope ne this.unforcedDecls)) + require((sym.denot.flagsUNSAFE is Private) || + !(this is Frozen) || + (scope ne this.unforcedDecls)) scope.enter(sym) - if (myMemberFingerPrint != FingerPrint.unknown) - myMemberFingerPrint.include(sym.name) - if (myMemberCache != null) - myMemberCache invalidate sym.name + if (myMemberCache != null) myMemberCache invalidate sym.name } /** Replace symbol `prev` (if defined in current class) by symbol `replacement`. @@ -1526,7 +1488,6 @@ object SymDenotations { def delete(sym: Symbol)(implicit ctx: Context) = { require(!(this is Frozen)) info.decls.openForMutations.unlink(sym) - myMemberFingerPrint = FingerPrint.unknown if (myMemberCache != null) myMemberCache invalidate sym.name } @@ -1574,31 +1535,26 @@ object SymDenotations { } private[core] def computeNPMembersNamed(name: Name, inherited: Boolean)(implicit ctx: Context): PreDenotation = /*>|>*/ Stats.track("computeNPMembersNamed") /*<|<*/ { - if (!inherited || - !Config.useFingerPrints || - (memberFingerPrint contains name)) { - Stats.record("computeNPMembersNamed after fingerprint") - ensureCompleted() - val ownDenots = info.decls.denotsNamed(name, selectNonPrivate) - if (debugTrace) // DEBUG - println(s"$this.member($name), ownDenots = $ownDenots") - def collect(denots: PreDenotation, parents: List[TypeRef]): PreDenotation = parents match { - case p :: ps => - val denots1 = collect(denots, ps) - p.symbol.denot match { - case parentd: ClassDenotation => - denots1 union - parentd.nonPrivateMembersNamed(name, inherited = true) - .mapInherited(ownDenots, denots1, thisType) - case _ => - denots1 - } - case nil => - denots - } - if (name.isConstructorName) ownDenots - else collect(ownDenots, classParents) - } else NoDenotation + Stats.record("computeNPMembersNamed after fingerprint") + ensureCompleted() + val ownDenots = info.decls.denotsNamed(name, selectNonPrivate) + if (debugTrace) // DEBUG + println(s"$this.member($name), ownDenots = $ownDenots") + def collect(denots: PreDenotation, parents: List[TypeRef]): PreDenotation = parents match { + case p :: ps => + val denots1 = collect(denots, ps) + p.symbol.denot match { + case parentd: ClassDenotation => + denots1 union + parentd.nonPrivateMembersNamed(name, inherited = true) + .mapInherited(ownDenots, denots1, thisType) + case _ => + denots1 + } + case nil => + denots + } + if (name.isConstructorName) ownDenots else collect(ownDenots, classParents) } override final def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = { From ddf16fda447a4a100dd2e0aef161b94b3358bd53 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 16:31:34 +0200 Subject: [PATCH 27/32] Drop Frozen The selective dotc bootstrap has shown that we cannot always establish the invariants required by the previous scheme of memberNames cache validation, which was based on the Frozen flag. Therefore, Frozen gets dropped and we use a generation nunber based scheme with a scan of base classes instead. --- src/dotty/tools/dotc/core/Contexts.scala | 12 ++++++- src/dotty/tools/dotc/core/Denotations.scala | 1 - src/dotty/tools/dotc/core/Flags.scala | 33 +++++++++---------- .../tools/dotc/core/SymDenotations.scala | 17 +++++----- src/dotty/tools/dotc/core/Symbols.scala | 4 +-- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index e1aeac8c3491..232b7b7f91f7 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -584,7 +584,7 @@ object Contexts { private[core] var lastSuperId = -1 /** Allocate and return next free superclass id */ - private[core] def nextSuperId: Int = { + private[core] def nextSuperId(): Int = { lastSuperId += 1 if (lastSuperId >= classOfId.length) { val tmp = new Array[ClassSymbol](classOfId.length * 2) @@ -594,6 +594,16 @@ object Contexts { lastSuperId } + /** The last member-names generation number */ + private[this] var myMNGen = 0 + + /** The next successive member-names generation number */ + private[core] def nextMemberNamesGeneration(): Int = { + myMNGen += 1 + myMNGen + } + + // Types state /** A table for hash consing unique types */ private[core] val uniques = new util.HashSet[Type](Config.initialUniquesCapacity) { diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 5dfc5c17d58c..8276d602e304 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -669,7 +669,6 @@ object Denotations { next match { case next: ClassDenotation => assert(!next.is(Package), s"illegal transformation of package denotation by transformer ${ctx.withPhase(transformer).phase}") - next.resetFlag(Frozen) case _ => } next.insertAfter(cur) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index f866621f258d..c84bc98ef981 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -379,53 +379,50 @@ object Flags { /** Denotation is in train of being loaded and completed, used to catch cyclic dependencies */ final val Touched = commonFlag(48, "") - /** Class is not allowed to accept new members because fingerprint of subclass has been taken */ - final val Frozen = commonFlag(49, "") - /** An error symbol */ - final val Erroneous = commonFlag(50, "") + final val Erroneous = commonFlag(49, "") /** Class has been lifted out to package level, local value has been lifted out to class level */ - final val Lifted = commonFlag(51, "") + final val Lifted = commonFlag(50, "") /** Term member has been mixed in */ - final val MixedIn = commonFlag(52, "") + final val MixedIn = commonFlag(51, "") /** Symbol is a generated specialized member */ - final val Specialized = commonFlag(53, "") + final val Specialized = commonFlag(52, "") /** Symbol is a self name */ - final val SelfName = termFlag(54, "") + final val SelfName = termFlag(53, "") /** Symbol is an implementation class of a Scala2 trait */ - final val ImplClass = typeFlag(54, "") + final val ImplClass = typeFlag(53, "") final val SelfNameOrImplClass = SelfName.toCommonFlags /** An existentially bound symbol (Scala 2.x only) */ - final val Scala2ExistentialCommon = commonFlag(55, "") + final val Scala2ExistentialCommon = commonFlag(54, "") final val Scala2Existential = Scala2ExistentialCommon.toTypeFlags /** An overloaded symbol (Scala 2.x only) */ - final val Scala2Overloaded = termFlag(56, "") + final val Scala2Overloaded = termFlag(55, "") /** A module variable (Scala 2.x only) */ - final val Scala2ModuleVar = termFlag(57, "") + final val Scala2ModuleVar = termFlag(56, "") /** A definition that's initialized before the super call (Scala 2.x only) */ - final val Scala2PreSuper = termFlag(58, "") + final val Scala2PreSuper = termFlag(57, "") /** A macro (Scala 2.x only) */ - final val Macro = commonFlag(59, "") + final val Macro = commonFlag(58, "") /** A method that is known to have inherited default parameters */ - final val InheritedDefaultParams = termFlag(60, "") + final val InheritedDefaultParams = termFlag(59, "") /** A method that is known to have no default parameters */ - final val NoDefaultParams = termFlag(61, "") + final val NoDefaultParams = termFlag(60, "") /** A denotation that is valid in all run-ids */ - final val Permanent = commonFlag(62, "") + final val Permanent = commonFlag(61, "") // --------- Combined Flag Sets and Conjunctions ---------------------- @@ -448,7 +445,7 @@ object Flags { final val FromStartFlags = AccessFlags | Module | Package | Deferred | Final | MethodOrHKCommon | Param | ParamAccessor | Scala2ExistentialCommon | InSuperCall | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | ExpandedName | AccessorOrSealed | - CaseAccessorOrBaseTypeArg | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | Synthetic | + CaseAccessorOrBaseTypeArg | Fresh | Erroneous | ImplicitCommon | Permanent | Synthetic | LazyOrTrait | SuperAccessorOrScala2x | SelfNameOrImplClass assert(FromStartFlags.isTermFlags && FromStartFlags.isTypeFlags) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 1e25138083c2..c0f0e961f0bc 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1149,7 +1149,7 @@ object SymDenotations { privateWithin: Symbol = null, annotations: List[Annotation] = null)(implicit ctx: Context) = { // simulate default parameters, while also passing implicit context ctx to the default values - val initFlags1 = (if (initFlags != UndefinedFlags) initFlags else this.flags) &~ Frozen + val initFlags1 = if (initFlags != UndefinedFlags) initFlags else this.flags val info1 = if (info != null) info else this.info val privateWithin1 = if (privateWithin != null) privateWithin else this.privateWithin val annotations1 = if (annotations != null) annotations else this.annotations @@ -1462,11 +1462,7 @@ object SymDenotations { /** Enter a symbol in given `scope` without potentially replacing the old copy. */ def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = { - require((sym.denot.flagsUNSAFE is Private) || - !(this is Frozen) || - (scope ne this.unforcedDecls)) scope.enter(sym) - if (myMemberCache != null) myMemberCache invalidate sym.name } @@ -1475,7 +1471,6 @@ object SymDenotations { * @pre `prev` and `replacement` have the same name. */ def replace(prev: Symbol, replacement: Symbol)(implicit ctx: Context): Unit = { - require(!(this is Frozen)) unforcedDecls.openForMutations.replace(prev, replacement) if (myMemberCache != null) myMemberCache invalidate replacement.name @@ -1486,7 +1481,6 @@ object SymDenotations { * someone does a findMember on a subclass. */ def delete(sym: Symbol)(implicit ctx: Context) = { - require(!(this is Frozen)) info.decls.openForMutations.unlink(sym) if (myMemberCache != null) myMemberCache invalidate sym.name } @@ -1642,6 +1636,11 @@ object SymDenotations { } private[this] var memberNamesCache: SimpleMap[NameFilter, Set[Name]] = SimpleMap.Empty + private var memberNamesGeneration: Int = 0 + + private def checkMemberNamesUpToDate()(implicit ctx: Context): Unit = + if (baseClasses.exists(_.classDenot.memberNamesGeneration > memberNamesGeneration)) + memberNamesCache = SimpleMap.Empty def memberNames(keepOnly: NameFilter)(implicit ctx: Context): Set[Name] = { def computeMemberNames: Set[Name] = { @@ -1660,12 +1659,14 @@ object SymDenotations { if ((this is PackageClass) || !Config.cacheMemberNames) computeMemberNames // don't cache package member names; they might change else { + checkMemberNamesUpToDate() val cached = memberNamesCache(keepOnly) if (cached != null) cached else { val names = computeMemberNames if (isFullyCompleted) { - setFlag(Frozen) + if (memberNamesCache.size == 0) + memberNamesGeneration = ctx.nextMemberNamesGeneration() memberNamesCache = memberNamesCache.updated(keepOnly, names) } names diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index b9458b1339f5..d291610fdd6b 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -327,7 +327,7 @@ trait Symbols { this: Context => copy.denot = odenot.copySymDenotation( symbol = copy, owner = ttmap1.mapOwner(odenot.owner), - initFlags = odenot.flags &~ Frozen | Fresh, + initFlags = odenot.flags | Fresh, info = ttmap1.mapType(oinfo), privateWithin = ttmap1.mapOwner(odenot.privateWithin), // since this refers to outer symbols, need not include copies (from->to) in ownermap here. annotations = odenot.annotations.mapConserve(ttmap1.apply)) @@ -534,7 +534,7 @@ object Symbols { case Some(id) => id case None => - val id = ctx.nextSuperId + val id = ctx.nextSuperId() ctx.superIdOfClass(this) = id ctx.classOfId(id) = this id From 65513e091072c03a4d5a16e70f626b84751e45f5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 21 May 2016 13:30:36 +0200 Subject: [PATCH 28/32] Make sure delayed Tasty unpicklings are done at the latest at Pickler phase --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b8622680b893..2abcce0fa6bf 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -53,7 +53,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { // I did not have the time to track down what caused the failure. // The testing scheme could well have produced a false negative. // - // private var stubs: Set[Symbol] = Set() + // private var stubs: Set[Symbol] = Set() private var roots: Set[SymDenotation] = null @@ -96,7 +96,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { class Completer(reader: TastyReader) extends LazyType { import reader._ def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { - treeAtAddr(currentAddr) = new TreeReader(reader).readIndexedDef() + treeAtAddr(currentAddr) = + new TreeReader(reader).readIndexedDef()( + ctx.withPhaseNoLater(ctx.picklerPhase))//(ctx.withOwner(owner)) } } @@ -951,7 +953,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { class LazyReader[T <: AnyRef](reader: TreeReader, op: TreeReader => Context => T) extends Trees.Lazy[T] with DeferredPosition { def complete(implicit ctx: Context): T = { pickling.println(i"starting to read at ${reader.reader.currentAddr}") - val res = op(reader)(ctx.addMode(Mode.AllowDependentFunctions)) + val res = op(reader)(ctx.addMode(Mode.AllowDependentFunctions).withPhaseNoLater(ctx.picklerPhase)) normalizePos(res, parentPos) res } @@ -960,7 +962,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { class LazyAnnotationReader(sym: Symbol, reader: TreeReader) extends LazyAnnotation(sym) with DeferredPosition { def complete(implicit ctx: Context) = { - val res = reader.readTerm() + val res = reader.readTerm()(ctx.withPhaseNoLater(ctx.picklerPhase)) normalizePos(res, parentPos) res } From fc2389f238ccff584671e5286c3ab0675c850767 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 21 May 2016 13:44:22 +0200 Subject: [PATCH 29/32] Let createSymbol return a symbol Compute initialization flags of possibly enclosing traits elsewhere (in indexStats). Cleans up the logic and makes the module more understandable. --- src/dotty/tools/dotc/core/Flags.scala | 3 ++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index c84bc98ef981..c4cddfecdd13 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -522,6 +522,9 @@ object Flags { /** Either method or lazy */ final val MethodOrLazy = Method | Lazy + /** Either method or lazy or deferred */ + final val MethodOrLazyOrDeferred = Method | Lazy | Deferred + /** Labeled `private` or `final` */ final val PrivateOrFinal = Private | Final diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 2abcce0fa6bf..cd5eb9e5015a 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -371,10 +371,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } /** Create symbol of definition node and enter in symAtAddr map - * @return the largest subset of {NoInits, PureInterface} that a - * trait owning this symbol can have as flags. + * @return the created symbol */ - def createSymbol()(implicit ctx: Context): FlagSet = { + def createSymbol()(implicit ctx: Context): Symbol = { val start = currentAddr val tag = readByte() val end = readEnd() @@ -434,10 +433,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - if (isClass) NoInits - else if (sym.isType || sym.isConstructor || flags.is(Deferred)) NoInitsInterface - else if (tag == VALDEF) EmptyFlags - else NoInits + sym } /** Read modifier list into triplet of flags, annotations and a privateWithin @@ -504,28 +500,33 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { (flags, annots.toList, privateWithin) } - /** Create symbols for a definitions in statement sequence between + /** Create symbols for the definitions in the statement sequence between * current address and `end`. * @return the largest subset of {NoInits, PureInterface} that a * trait owning the indexed statements can have as flags. */ def indexStats(end: Addr)(implicit ctx: Context): FlagSet = { - val flagss = - until(end) { - nextByte match { - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => - createSymbol() - case IMPORT => - skipTree() - NoInitsInterface - case PACKAGE => - processPackage { (pid, end) => implicit ctx => indexStats(end) } - case _ => - skipTree() - EmptyFlags - } + var initsFlags = NoInitsInterface + while (currentAddr.index < end.index) { + nextByte match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => + val sym = createSymbol() + if (sym.isTerm && !sym.is(MethodOrLazyOrDeferred)) + initsFlags = EmptyFlags + else if (sym.isClass || + sym.is(Method, butNot = Deferred) && !sym.isConstructor) + initsFlags &= NoInits + case IMPORT => + skipTree() + case PACKAGE => + processPackage { (pid, end) => implicit ctx => indexStats(end) } + case _ => + skipTree() + initsFlags = EmptyFlags } - (NoInitsInterface /: flagss)(_ & _) + } + assert(currentAddr.index == end.index) + initsFlags } /** Process package with given operation `op`. The operation takes as arguments From 85b488dff593345cf77bd95fc7ce4235e9f226ea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 21 May 2016 19:11:15 +0200 Subject: [PATCH 30/32] Maintain ownerTree data structure when unpickling Tasty First step for a more robust scheme to access symbols in Tasty. This entailed a swap of two fields in RefiendType, to make tree format more uniform in what concerns where references are found. --- .../tools/dotc/core/tasty/TastyFormat.scala | 11 +++ .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 80 +++++++++++++++++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index ea7e985c9a1a..2211706225e6 100644 --- a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -485,4 +485,15 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" } + + /** @return If non-negative, the number of leading references of a length/trees entry. + * If negative, minus the number of leading non-reference trees. + */ + def numRefs(tag: Int) = tag match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | + SELFDEF | REFINEDtype => 1 + case RENAMED | PARAMtype => 2 + case POLYtype | METHODtype => -1 + case _ => 0 + } } diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f8f9c993f43d..37b9341eb62f 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -217,8 +217,8 @@ class TreePickler(pickler: TastyPickler) { case tpe: RefinedType => writeByte(REFINEDtype) withLength { - pickleType(tpe.parent) pickleName(tpe.refinedName) + pickleType(tpe.parent) pickleType(tpe.refinedInfo, richTypes = true) } case tpe: TypeAlias => diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cd5eb9e5015a..a82c4cad40a7 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -57,6 +57,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { private var roots: Set[SymDenotation] = null + private var ownerTree: OwnerTree = _ + private def registerSym(addr: Addr, sym: Symbol) = { symAtAddr(addr) = sym unpickledSyms += sym @@ -118,6 +120,45 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { def skipParams(): Unit = while (nextByte == PARAMS || nextByte == TYPEPARAM) skipTree() + /** Record all directly nested definitions and templates in current tree + * as `OwnerTree`s in `buf` + */ + def scanTree(buf: ListBuffer[OwnerTree]): Unit = { + val start = currentAddr + val tag = readByte() + //println(s"scan tree at $currentAddr, tag = $tag") + tag match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | TEMPLATE => + val end = readEnd() + for (i <- 0 until numRefs(tag)) readNat() + buf += new OwnerTree(start, fork, end) + case tag => + if (tag >= firstLengthTreeTag) { + val end = readEnd() + var nrefs = numRefs(tag) + if (nrefs < 0) { + for (i <- nrefs until 0) scanTree(buf) + goto(end) + } + else { + for (i <- 0 until nrefs) readNat() + scanTrees(buf, end) + } + } + else if (tag >= firstNatASTTreeTag) { readNat(); scanTree(buf) } + else if (tag >= firstASTTreeTag) scanTree(buf) + else if (tag >= firstNatTreeTag) readNat() + } + } + + /** Record all directly nested definitions and templates between current address and `end` + * as `OwnerTree`s in `buf` + */ + def scanTrees(buf: ListBuffer[OwnerTree], end: Addr): Unit = { + while (currentAddr.index < end.index) scanTree(buf) + assert(currentAddr.index == end.index) + } + /** The next tag, following through SHARED tags */ def nextUnsharedTag: Int = { val tag = nextByte @@ -197,8 +238,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case SUPERtype => SuperType(readType(), readType()) case REFINEDtype => - val parent = readType() var name: Name = readName() + val parent = readType() val ttag = nextUnsharedTag if (ttag == TYPEBOUNDS || ttag == TYPEALIAS) name = name.toTypeName RefinedType(parent, name, rt => registeringType(rt, readType())) @@ -437,7 +478,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } /** Read modifier list into triplet of flags, annotations and a privateWithin - * boindary symbol. + * boundary symbol. */ def readModifiers(end: Addr)(implicit ctx: Context): (FlagSet, List[Annotation], Symbol) = { var flags: FlagSet = EmptyFlags @@ -664,6 +705,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { else NoType setClsInfo(Nil, assumedSelfType) val localDummy = ctx.newLocalDummy(cls) + registerSym(start, localDummy) assert(readByte() == TEMPLATE) val end = readEnd() val tparams = readIndexedParams[TypeDef](TYPEPARAM) @@ -716,12 +758,11 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } def readTopLevel()(implicit ctx: Context): List[Tree] = { + ownerTree = new OwnerTree(NoAddr, fork, reader.endAddr) @tailrec def read(acc: ListBuffer[Tree]): List[Tree] = nextByte match { case IMPORT | PACKAGE => acc += readIndexedStat(NoSymbol) - if (!isAtEnd) - read(acc) - else acc.toList + if (!isAtEnd) read(acc) else acc.toList case _ => // top-level trees which are not imports or packages are not part of tree acc.toList } @@ -968,4 +1009,33 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { res } } + + class OwnerTree(val addr: Addr, reader: TreeReader, val end: Addr) { + lazy val children: List[OwnerTree] = { + val buf = new ListBuffer[OwnerTree] + reader.scanTrees(buf, end) + buf.toList + } + def findOwner(addr: Addr)(implicit ctx: Context): Symbol = { + //println(s"find owner $addr") + def search(cs: List[OwnerTree], current: Symbol): Symbol = cs match { + case ot :: cs1 => + if (ot.addr.index == addr.index) { + //println(i"search ok $addr, owner = $current") + current + } + else if (ot.addr.index < addr.index && addr.index < ot.end.index) { + val encl = reader.symbolAt(ot.addr) + //println(s"search $addr in ${ot.children} with $encl") + search(ot.children, encl) + } + else + search(cs1, current) + case Nil => + throw new Error("unattached tree") + } + search(children, NoSymbol) + } + override def toString = s"OwnerTree(${addr.index}, ${end.index}" + } } From 630efff88bc895c014c7d118464fe2c35d4ac2fd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 22 May 2016 21:55:53 +0200 Subject: [PATCH 31/32] Adopt new scheme for handling forward references in Tasty Instead of stubbing with potentially wrong owners and hping for the best, we now compute owners on demand, using the lazy data structure of an OwnerTree. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 172 +++++++++++------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index a82c4cad40a7..9598fc0c5441 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -22,6 +22,7 @@ import config.Printers.pickling class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { import TastyFormat._ import TastyName._ + import TreeUnpickler._ import tpd._ private var readPositions = false @@ -45,18 +46,12 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { private val treeAtAddr = new mutable.HashMap[Addr, Tree] private val typeAtAddr = new mutable.HashMap[Addr, Type] // currently populated only for types that are known to be SHAREd. - // Currently disabled set used for checking that all - // already encountered symbols are forward refereneces. This - // check fails in more complicated scenarios of separate - // compilation in dotty (for instance: compile all of `core` - // given the TASTY files of everything else in the compiler). - // I did not have the time to track down what caused the failure. - // The testing scheme could well have produced a false negative. - // - // private var stubs: Set[Symbol] = Set() - + /** The root symbol denotation which are defined by the Tasty file associated with this + * TreeUnpickler. Set by `enterTopLevel`. + */ private var roots: Set[SymDenotation] = null + /** The root owner tree. See `OwnerTree` class definition. Set by `enterTopLevel`. */ private var ownerTree: OwnerTree = _ private def registerSym(addr: Addr, sym: Symbol) = { @@ -69,7 +64,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { */ def enterTopLevel(roots: Set[SymDenotation])(implicit ctx: Context): Unit = { this.roots = roots - new TreeReader(reader).fork.indexStats(reader.endAddr) + var rdr = new TreeReader(reader).fork + ownerTree = new OwnerTree(NoAddr, 0, rdr.fork, reader.endAddr) + rdr.indexStats(reader.endAddr) } /** The unpickled trees */ @@ -123,17 +120,19 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Record all directly nested definitions and templates in current tree * as `OwnerTree`s in `buf` */ - def scanTree(buf: ListBuffer[OwnerTree]): Unit = { + def scanTree(buf: ListBuffer[OwnerTree], mode: MemberDefMode = AllDefs): Unit = { val start = currentAddr val tag = readByte() - //println(s"scan tree at $currentAddr, tag = $tag") tag match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | TEMPLATE => val end = readEnd() for (i <- 0 until numRefs(tag)) readNat() - buf += new OwnerTree(start, fork, end) + if (tag == TEMPLATE) scanTrees(buf, end, MemberDefsOnly) + if (mode != NoMemberDefs) buf += new OwnerTree(start, tag, fork, end) + goto(end) case tag => - if (tag >= firstLengthTreeTag) { + if (mode == MemberDefsOnly) skipTree(tag) + else if (tag >= firstLengthTreeTag) { val end = readEnd() var nrefs = numRefs(tag) if (nrefs < 0) { @@ -154,8 +153,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Record all directly nested definitions and templates between current address and `end` * as `OwnerTree`s in `buf` */ - def scanTrees(buf: ListBuffer[OwnerTree], end: Addr): Unit = { - while (currentAddr.index < end.index) scanTree(buf) + def scanTrees(buf: ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode): Unit = { + while (currentAddr.index < end.index) scanTree(buf, mode) assert(currentAddr.index == end.index) } @@ -197,19 +196,25 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { until(end) { readNat(); readType().asInstanceOf[T] } /** Read referece to definition and return symbol created at that definition */ - def readSymRef()(implicit ctx: Context): Symbol = { - val start = currentAddr - val addr = readAddr() - symAtAddr get addr match { - case Some(sym) => sym - case None => - // Create a stub; owner might be wrong but will be overwritten later. - forkAt(addr).createSymbol() - val sym = symAtAddr(addr) - ctx.log(i"forward reference to $sym") - // stubs += sym - sym - } + def readSymRef()(implicit ctx: Context): Symbol = symbolAt(readAddr()) + + /** The symbol at given address; createa new one if none exists yet */ + def symbolAt(addr: Addr)(implicit ctx: Context): Symbol = symAtAddr.get(addr) match { + case Some(sym) => + sym + case None => + val sym = forkAt(addr).createSymbol()(ctx.withOwner(ownerTree.findOwner(addr))) + ctx.log(i"forward reference to $sym") + sym + } + + /** The symbol defined by current definition */ + def symbolAtCurrent()(implicit ctx: Context): Symbol = symAtAddr.get(currentAddr) match { + case Some(sym) => + assert(ctx.owner == sym.owner, i"owner discrepancy for $sym, expected: ${ctx.owner}, found: ${sym.owner}") + sym + case None => + createSymbol() } /** Read a type */ @@ -414,7 +419,21 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Create symbol of definition node and enter in symAtAddr map * @return the created symbol */ - def createSymbol()(implicit ctx: Context): Symbol = { + def createSymbol()(implicit ctx: Context): Symbol = nextByte match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => + createMemberSymbol() + case TEMPLATE => + val localDummy = ctx.newLocalDummy(ctx.owner) + registerSym(currentAddr, localDummy) + localDummy + case tag => + throw new Error(s"illegal createSymbol at $currentAddr, tag = $tag") + } + + /** Create symbol of member definition or parameter node and enter in symAtAddr map + * @return the created symbol + */ + def createMemberSymbol()(implicit ctx: Context): Symbol = { val start = currentAddr val tag = readByte() val end = readEnd() @@ -451,22 +470,10 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case _ => val completer = adjustIfModule(new Completer(subReader(start, end))) if (isClass) - ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, - privateWithin, coord = start.index) - else { - val sym = symAtAddr.get(start) match { - case Some(preExisting) => - //assert(stubs contains preExisting, preExisting) - //stubs -= preExisting - preExisting - case none => - ctx.newNakedSymbol(start.index) - } - val denot = ctx.SymDenotation(symbol = sym, owner = ctx.owner, name, flags, completer, privateWithin) - sym.denot = denot - sym - } - } // TODO set position + ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, privateWithin, coord = start.index) + else + ctx.newSymbol(ctx.owner, name, flags, completer, privateWithin, coord = start.index) + } // TODO set position somehow (but take care not to upset Symbol#isDefinedInCurrentRun) sym.annotations = annots ctx.enter(sym) registerSym(start, sym) @@ -474,6 +481,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } + goto(start) sym } @@ -551,7 +559,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { while (currentAddr.index < end.index) { nextByte match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => - val sym = createSymbol() + val sym = symbolAtCurrent() + skipTree() if (sym.isTerm && !sym.is(MethodOrLazyOrDeferred)) initsFlags = EmptyFlags else if (sym.isClass || @@ -586,7 +595,10 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { * `tag` starting at current address. */ def indexParams(tag: Int)(implicit ctx: Context) = - while (nextByte == tag) createSymbol() + while (nextByte == tag) { + symbolAtCurrent() + skipTree() + } /** Create symbols for all type and value parameters of template starting * at current address. @@ -613,7 +625,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val end = readEnd() def readParams[T <: MemberDef](tag: Int)(implicit ctx: Context): List[T] = { - fork.indexParams(tag) + fork.indexParams(tag)(localContext(sym)) readIndexedParams(tag) } @@ -704,8 +716,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { TermRef.withSig(cls.owner.thisType, cls.name.sourceModuleName, Signature.NotAMethod) else NoType setClsInfo(Nil, assumedSelfType) - val localDummy = ctx.newLocalDummy(cls) - registerSym(start, localDummy) + val localDummy = symbolAtCurrent() assert(readByte() == TEMPLATE) val end = readEnd() val tparams = readIndexedParams[TypeDef](TYPEPARAM) @@ -758,7 +769,6 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } def readTopLevel()(implicit ctx: Context): List[Tree] = { - ownerTree = new OwnerTree(NoAddr, fork, reader.endAddr) @tailrec def read(acc: ListBuffer[Tree]): List[Tree] = nextByte match { case IMPORT | PACKAGE => acc += readIndexedStat(NoSymbol) @@ -1010,32 +1020,62 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } } - class OwnerTree(val addr: Addr, reader: TreeReader, val end: Addr) { + /** A lazy datastructure that records how definitions are nested in TASTY data. + * The structure is lazy because it needs to be computed only for forward references + * to symbols that happen before the referenced symbol is created (see `symbolAt`). + * Such forward references are rare. + * + * @param addr The address of tree representing an owning definition, NoAddr for root tree + * @param tag The tag at `addr`. Used to determine which subtrees to scan for children + * (i.e. if `tag` is template, don't scan member defs, as these belong already + * to enclosing class). + * @param reader The reader to be used for scanning for children + * @param end The end of the owning definition + */ + class OwnerTree(val addr: Addr, tag: Int, reader: TreeReader, val end: Addr) { + + /** All definitions that have the definition at `addr` as closest enclosing definition */ lazy val children: List[OwnerTree] = { val buf = new ListBuffer[OwnerTree] - reader.scanTrees(buf, end) + reader.scanTrees(buf, end, if (tag == TEMPLATE) NoMemberDefs else AllDefs) buf.toList } + + /** Find the owner of definition at `addr` */ def findOwner(addr: Addr)(implicit ctx: Context): Symbol = { - //println(s"find owner $addr") - def search(cs: List[OwnerTree], current: Symbol): Symbol = cs match { + def search(cs: List[OwnerTree], current: Symbol): Symbol = + try cs match { case ot :: cs1 => - if (ot.addr.index == addr.index) { - //println(i"search ok $addr, owner = $current") + if (ot.addr.index == addr.index) current - } - else if (ot.addr.index < addr.index && addr.index < ot.end.index) { - val encl = reader.symbolAt(ot.addr) - //println(s"search $addr in ${ot.children} with $encl") - search(ot.children, encl) - } + else if (ot.addr.index < addr.index && addr.index < ot.end.index) + search(ot.children, reader.symbolAt(ot.addr)) else search(cs1, current) case Nil => - throw new Error("unattached tree") + throw new TreeWithoutOwner + } + catch { + case ex: TreeWithoutOwner => + println(i"no owner for $addr among $cs") // DEBUG + throw ex } search(children, NoSymbol) } + override def toString = s"OwnerTree(${addr.index}, ${end.index}" } } + +object TreeUnpickler { + + /** An enumeration indicating which subtrees should be added to an OwnerTree. */ + type MemberDefMode = Int + final val MemberDefsOnly = 0 // add only member defs; skip other statements + final val NoMemberDefs = 1 // add only statements that are not member defs + final val AllDefs = 2 // add everything + + class TreeWithoutOwner extends Exception +} + + From 1094e63e9b0a3611f00ea88c316a656eefadb01a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 May 2016 10:52:48 +0200 Subject: [PATCH 32/32] Refine owner trees for templates Include member defs inside templates in the enclosing class, otherwise they would get localDummy as onwer. --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9598fc0c5441..787b89ef8263 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -153,7 +153,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Record all directly nested definitions and templates between current address and `end` * as `OwnerTree`s in `buf` */ - def scanTrees(buf: ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode): Unit = { + def scanTrees(buf: ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode = AllDefs): Unit = { while (currentAddr.index < end.index) scanTree(buf, mode) assert(currentAddr.index == end.index) }