From df4dcce66f6b46a81686b5bed130001dd1b86bde Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Apr 2014 09:51:41 +0200 Subject: [PATCH 1/3] Better printing of denotations. Used to print for denotations with a symbol, now prints "some I" where I is the denotation's info.Reworked phases. --- src/dotty/tools/dotc/core/Contexts.scala | 6 ++++-- src/dotty/tools/dotc/core/Phases.scala | 1 - src/dotty/tools/dotc/printing/RefinedPrinter.scala | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index b0214a631e8b..e361a1b736f7 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -181,10 +181,10 @@ object Contexts { protected def searchHistory_= (searchHistory: SearchHistory) = _searchHistory = searchHistory def searchHistory: SearchHistory = _searchHistory + /** Caches for withPhase */ private var phasedCtx: Context = _ private var phasedCtxs: Array[Context] = _ - /** This context at given phase. * This method will always return a phase period equal to phaseId, thus will never return squashed phases */ @@ -205,7 +205,8 @@ object Contexts { final def withPhase(phase: Phase): Context = withPhase(phase.id) - /** If -Ydebug is on, the top of the stack trace where this context + + /** If -Ydebug is on, the top of the stack trace where this context * was created, otherwise `null`. */ private var creationTrace: Array[StackTraceElement] = _ @@ -298,6 +299,7 @@ object Contexts { setCreationTrace() this } + /** A fresh clone of this context. */ def fresh: FreshContext = clone.asInstanceOf[FreshContext].init(this) diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 8b5606da245b..ac5a19ad14db 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -67,7 +67,6 @@ object Phases { override def lastPhaseId(implicit ctx: Context) = id } - /** Use the following phases in the order they are given. * The list should never contain NoPhase. * if squashing is enabled, phases in same subgroup will be squashed to single phase. diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 557a2b5db6f8..f9cd9ec727ce 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -3,7 +3,7 @@ package printing import core._ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._ -import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation +import Contexts.Context, Scopes.Scope, Denotations._, Annotations.Annotation import StdNames.nme import ast.{Trees, untpd} import typer.Namer @@ -475,7 +475,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(flags.flagStrings.filterNot(_.startsWith("<")) map stringToText, " ") } - override def toText(denot: Denotation): Text = toText(denot.symbol) + override def toText(denot: Denotation): Text = denot match { + case denot: MultiDenotation => denot.toString + case _ => + if (denot.symbol.exists) toText(denot.symbol) + else "some " ~ toText(denot.info) + } override def plain = new PlainPrinter(_ctx) } From f6ba7980cf2734e51622b96d63750c532d987da8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 31 Mar 2014 15:42:52 +0200 Subject: [PATCH 2/3] Corrected computations of union denotations. Cannot discard a symbol simply because the other side's type is weaker. If in (A | B)#m A and B resolve to different symbols `m`, the resulting denotation cannot have either `m` as symbol; it must be NoSymbol instead. --- src/dotty/tools/dotc/core/Denotations.scala | 51 +++++++++---------- src/dotty/tools/dotc/typer/Applications.scala | 4 +- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 1e3dec255bb2..a6f151d37b4f 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -273,35 +273,34 @@ object Denotations { def unionDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = if (denot1.signature matches denot2.signature) { + val sym1 = denot1.symbol + val sym2 = denot2.symbol val info1 = denot1.info val info2 = denot2.info - val sym2 = denot2.symbol - def sym2Accessible = sym2.isAccessibleFrom(pre) - if (info1 <:< info2 && sym2Accessible) denot2 + val sameSym = sym1 eq sym2 + if (sameSym && info1 <:< info2) denot2 + else if (sameSym && info2 <:< info1) denot1 else { - val sym1 = denot1.symbol - def sym1Accessible = sym1.isAccessibleFrom(pre) - if (info2 <:< info1 && sym1Accessible) denot1 - else { - val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol - /** Determine a symbol which is overridden by both sym1 and sym2. - * Preference is given to accessible symbols. - */ - def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = - if (!overrides.hasNext) previous - else { - val candidate = overrides.next - if (owner2 derivesFrom candidate.owner) - if (candidate isAccessibleFrom pre) candidate - else lubSym(overrides, previous orElse candidate) - else - lubSym(overrides, previous) - } - new JointRefDenotation( - lubSym(sym1.allOverriddenSymbols, NoSymbol), - info1 | info2, - denot1.validFor & denot2.validFor) - } + val jointSym = + if (sameSym) sym1 + else { + val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol + /** Determine a symbol which is overridden by both sym1 and sym2. + * Preference is given to accessible symbols. + */ + def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = + if (!overrides.hasNext) previous + else { + val candidate = overrides.next + if (owner2 derivesFrom candidate.owner) + if (candidate isAccessibleFrom pre) candidate + else lubSym(overrides, previous orElse candidate) + else + lubSym(overrides, previous) + } + lubSym(sym1.allOverriddenSymbols, NoSymbol) + } + new JointRefDenotation(jointSym, info1 | info2, denot1.validFor & denot2.validFor) } } else NoDenotation diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 9a21e1c54f68..5a715c55cb6b 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -794,8 +794,8 @@ trait Applications extends Compatibility { self: Typer => tp } - val owner1 = alt1.symbol.owner - val owner2 = alt2.symbol.owner + val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol + val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol val tp1 = stripImplicit(alt1.widen) val tp2 = stripImplicit(alt2.widen) From dc2382b3fdd6d72b1f74b5172d0b61b999b29398 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 31 Mar 2014 17:20:02 +0200 Subject: [PATCH 3/3] Fleshed out Splitter phase Implemented splitting operations As a side effect, this contains a test ruling out structural term member dispatch. Tests 0586 and 0625 which used structural dispatch got moved to neg. --- src/dotty/tools/dotc/Run.scala | 2 +- src/dotty/tools/dotc/transform/Splitter.scala | 106 +++++++++++++++++- test/dotc/tests.scala | 2 + tests/{pos => neg}/t0586.scala | 0 tests/{pos => neg}/t0625.scala | 0 tests/pos/unions.scala | 11 ++ 6 files changed, 117 insertions(+), 4 deletions(-) rename tests/{pos => neg}/t0586.scala (100%) rename tests/{pos => neg}/t0625.scala (100%) diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index 264373baff93..247fa43365e1 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -51,7 +51,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { private def printTree(ctx: Context) = { val unit = ctx.compilationUnit - println(s"result of $unit after ${ctx.phase}:") + println(s"result of $unit after ${ctx.phase.prev}:") println(unit.tpdTree.show(ctx)) } diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index 9c01574aae45..f9de1d643ac0 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -3,10 +3,10 @@ package transform import TreeTransforms._ import ast.Trees._ -import core.Contexts._ -import core.Types._ +import core._ +import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ -/** This transform makes usre every identifier and select node +/** This transform makes sure every identifier and select node * carries a symbol. To do this, certain qualifiers with a union type * have to be "splitted" with a type test. * @@ -24,4 +24,104 @@ class Splitter extends TreeTransform { This(cls) withPos tree.pos case _ => tree } + + /** If we select a name, make sure the node has a symbol. + * If necessary, split the qualifier with type tests. + * Example: Assume: + * + * class A { def f(x: S): T } + * class B { def f(x: S): T } + * def p(): A | B + * + * Then p().f(a) translates to + * + * val ev$1 = p() + * if (ev$1.isInstanceOf[A]) ev$1.asInstanceOf[A].f(a) + * else ev$1.asInstanceOf[B].f(a) + */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = { + val Select(qual, name) = tree + + def memberDenot(tp: Type): SingleDenotation = { + val mbr = tp.member(name) + if (!mbr.isOverloaded) mbr.asSingleDenotation + else tree.tpe match { + case tref: TermRefWithSignature => mbr.atSignature(tref.sig) + case _ => ctx.error(s"cannot disambiguate overloaded member $mbr"); NoDenotation + } + } + + def candidates(tp: Type): List[Symbol] = { + val mbr = memberDenot(tp) + if (mbr.symbol.exists) mbr.symbol :: Nil + else tp.widen match { + case tref: TypeRef => + tref.info match { + case TypeBounds(_, hi) => candidates(hi) + case _ => Nil + } + case OrType(tp1, tp2) => + candidates(tp1) | candidates(tp2) + case AndType(tp1, tp2) => + candidates(tp1) & candidates(tp2) + case tpw => + Nil + } + } + + def isStructuralSelect(tp: Type): Boolean = tp.stripTypeVar match { + case tp: RefinedType => tp.refinedName == name || isStructuralSelect(tp) + case tp: TypeProxy => isStructuralSelect(tp.underlying) + case AndType(tp1, tp2) => isStructuralSelect(tp1) || isStructuralSelect(tp2) + case _ => false + } + + if (tree.symbol.exists) tree + else { + def choose(qual: Tree, syms: List[Symbol]): Tree = { + def testOrCast(which: Symbol, mbr: Symbol) = + TypeApply(Select(qual, which), TypeTree(mbr.owner.typeRef) :: Nil) + def select(sym: Symbol) = { + val qual1 = + if (qual.tpe derivesFrom sym.owner) qual + else testOrCast(defn.Any_asInstanceOf, sym) + Select(qual1, sym) withPos tree.pos + } + syms match { + case Nil => + def msg = + if (isStructuralSelect(qual.tpe)) + s"cannot access member '$name' from structural type ${qual.tpe.widen.show}; use Dynamic instead" + else + s"no candidate symbols for ${tree.tpe.show} found in ${qual.tpe.show}" + ctx.error(msg, tree.pos) + tree + case sym :: Nil => + select(sym) + case sym :: syms1 => + If(testOrCast(defn.Any_isInstanceOf, sym), select(sym), choose(qual, syms1)) + } + } + evalOnce(qual)(qual => choose(qual, candidates(qual.tpe))) + } + } + + /** Distribute arguments among splitted branches */ + def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { + def recur(fn: Tree): Tree = fn match { + case Block(stats, expr) => Block(stats, recur(expr)) + case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) + case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos + } + recur(tree.fun) + } + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, typeApply) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, apply) + + private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) + private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) } \ No newline at end of file diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index dcd5c67c8649..9665ec206282 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -65,6 +65,8 @@ class tests extends CompilerTest { @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1) @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4) @Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1) + @Test def neg_t0586_structural = compileFile(negDir, "t0586", xerrors = 1) + @Test def neg_t0625_structural = compileFile(negDir, "t0625", xerrors = 1) @Test def neg_t0654_polyalias = compileFile(negDir, "t0654", xerrors = 2) @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1) diff --git a/tests/pos/t0586.scala b/tests/neg/t0586.scala similarity index 100% rename from tests/pos/t0586.scala rename to tests/neg/t0586.scala diff --git a/tests/pos/t0625.scala b/tests/neg/t0625.scala similarity index 100% rename from tests/pos/t0625.scala rename to tests/neg/t0625.scala diff --git a/tests/pos/unions.scala b/tests/pos/unions.scala index 779d1847e97a..f358d6df9ad7 100644 --- a/tests/pos/unions.scala +++ b/tests/pos/unions.scala @@ -2,13 +2,24 @@ object unions { class A { def f: String = "abc" + + def g(x: Int): Int = x + def g(x: Double): Double = x } class B { def f: String = "bcd" + + def g(x: Int) = -x + def g(x: Double): Double = -x } val x: A | B = if (true) new A else new B + def y: B | A = if (true) new A else new B println(x.f) + println(x.g(2)) + println(y.f) + println(y.g(1.0)) + }