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/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/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/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) } 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/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index ea3afc67905f..8da19e643222 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -11,8 +11,10 @@ import core.Types._ import core.Constants._ import core.StdNames._ import core.transform.Erasure.isUnboundedGeneric +import typer._ import typer.ErrorReporting._ import ast.Trees._ +import ast.{tpd, untpd} /** This transform eliminates patterns. Right now it's a dummy. * Awaiting the real pattern matcher. @@ -22,14 +24,37 @@ class TreeChecker { def check(ctx: Context) = { println(s"checking ${ctx.compilationUnit} after phase ${ctx.phase.prev}") - Checker.transform(ctx.compilationUnit.tpdTree)(ctx) + Checker.typedExpr(ctx.compilationUnit.tpdTree)(ctx) } - object Checker extends TreeMap { - override def transform(tree: Tree)(implicit ctx: Context) = { - println(i"checking $tree") - assert(tree.isEmpty || tree.hasType, tree.show) - super.transform(tree) + object Checker extends ReTyper { + override def typed(tree: untpd.Tree, pt: Type)(implicit ctx: Context) = + if (tree.isEmpty) tree.asInstanceOf[Tree] + else { + assert(tree.hasType, tree.show) + val tree1 = super.typed(tree, pt) + def sameType(tp1: Type, tp2: Type) = + (tp1 eq tp2) || // accept NoType / NoType + (tp1 =:= tp2) + def divergenceMsg = + s"""Types differ + |Original type : ${tree.typeOpt.show} + |After checking: ${tree1.tpe.show} + |Original tree : ${tree.show} + |After checking: ${tree1.show} + """.stripMargin + assert(sameType(tree1.tpe, tree.typeOpt), divergenceMsg) + tree1 + } + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.isTerm, tree.show) + super.typedIdent(tree, pt) + } + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.isTerm, tree.show) + super.typedSelect(tree, pt) } } } 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) diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 896dbba7dc2f..c2f627b1e7b9 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -3,7 +3,7 @@ package typer import core.Contexts._ import core.Types._ -import core.Symbols.Symbol +import core.Symbols._ import typer.ProtoTypes._ import ast.{tpd, untpd} import ast.Trees._ @@ -48,6 +48,8 @@ class ReTyper extends Typer { untpd.cpy.Bind(tree, tree.name, body1).withType(tree.typeOpt) } + override def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context) = impl.symbol + override def retrieveSym(tree: untpd.Tree)(implicit ctx: Context): Symbol = tree.symbol override def localTyper(sym: Symbol) = this diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index c2488f68cfdb..23389a2efefc 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -809,11 +809,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val parents1 = ensureConstrCall(ensureFirstIsClass( parents mapconserve typedParent, cdef.pos.toSynthetic)) val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class memebers are not visible - val localDummy = ctx.newLocalDummy(cls, impl.pos) - val body1 = typedStats(body, localDummy)(inClassContext(self1.symbol)) + val dummy = localDummy(cls, impl) + val body1 = typedStats(body, dummy)(inClassContext(self1.symbol)) checkNoDoubleDefs(cls) val impl1 = cpy.Template(impl, constr1, parents1, self1, body1) - .withType(localDummy.termRef) + .withType(dummy.termRef) assignType(cpy.TypeDef(cdef, mods1, name, impl1), cls) // todo later: check that @@ -825,6 +825,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // 4. Polymorphic type defs override nothing. } + def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context): Symbol = + ctx.newLocalDummy(cls, impl.pos) + def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) checkStable(expr1.tpe, imp.expr.pos) 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)) + }