diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index f701438884dd..b6adba85a28b 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -131,7 +131,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{ val hashMethodSym: Symbol = NoSymbol // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase val externalEqualsNumNum: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum) - lazy val externalEqualsNumChar: Symbol = ??? // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private + val externalEqualsNumChar: Symbol = NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private val externalEqualsNumObject: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject) val externalEquals: Symbol = defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol val MaxFunctionArity: Int = Definitions.MaxFunctionArity diff --git a/src/dotty/tools/dotc/core/CheckRealizable.scala b/src/dotty/tools/dotc/core/CheckRealizable.scala new file mode 100644 index 000000000000..6e6efa549471 --- /dev/null +++ b/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -0,0 +1,146 @@ +package dotty.tools +package dotc +package core + +import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ +import SymDenotations._, Denotations.SingleDenotation +import config.Printers._ +import util.Positions._ +import Decorators._ +import StdNames._ +import Annotations._ +import collection.mutable +import ast.tpd._ + +/** Realizability status */ +object CheckRealizable { + + abstract class Realizability(val msg: String) { + def andAlso(other: => Realizability) = + if (this == Realizable) other else this + def mapError(f: Realizability => Realizability) = + if (this == Realizable) this else f(this) + } + + object Realizable extends Realizability("") + + object NotConcrete extends Realizability(" is not a concrete type") + + object NotStable extends Realizability(" is not a stable reference") + + class NotFinal(sym: Symbol)(implicit ctx: Context) + extends Realizability(i" refers to nonfinal $sym") + + class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context) + extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}") + + class HasProblemField(fld: SingleDenotation, problem: Realizability)(implicit ctx: Context) + extends Realizability(i" has a member $fld which is not a legal path\n since ${fld.symbol.name}: ${fld.info}${problem.msg}") + + class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) + extends Realizability(i"s underlying type ${tp}${problem.msg}") { + assert(problem != Realizable) + } + + def realizability(tp: Type)(implicit ctx: Context) = + new CheckRealizable().realizability(tp) + + def boundsRealizability(tp: Type)(implicit ctx: Context) = + new CheckRealizable().boundsRealizability(tp) +} + +/** Compute realizability status */ +class CheckRealizable(implicit ctx: Context) { + import CheckRealizable._ + + /** A set of all fields that have already been checked. Used + * to avoid infinite recursions when analyzing recursive types. + */ + private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]() + + /** Is symbol's definitition a lazy val? + * (note we exclude modules here, because their realizability is ensured separately) + */ + private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) + + /** Is this type a path with some part that is initialized on use? + */ + private def isLateInitialized(tp: Type): Boolean = tp.dealias match { + case tp: TermRef => + isLateInitialized(tp.symbol) || isLateInitialized(tp.prefix) + case _: SingletonType | NoPrefix => + false + case tp: TypeRef => + true + case tp: TypeProxy => + isLateInitialized(tp.underlying) + case tp: AndOrType => + isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2) + case _ => + true + } + + /** The realizability status of given type `tp`*/ + def realizability(tp: Type): Realizability = tp.dealias match { + case tp: TermRef => + val sym = tp.symbol + if (sym.is(Stable)) realizability(tp.prefix) + else { + val r = + if (!sym.isStable) NotStable + else if (!isLateInitialized(sym)) realizability(tp.prefix) + else if (!sym.isEffectivelyFinal) new NotFinal(sym) + else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) + if (r == Realizable) sym.setFlag(Stable) + r + } + case _: SingletonType | NoPrefix => + Realizable + case tp => + def isConcrete(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isClass + case tp: TypeProxy => isConcrete(tp.underlying) + case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) + case _ => false + } + if (!isConcrete(tp)) NotConcrete + else boundsRealizability(tp).andAlso(memberRealizability(tp)) + } + + /** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance + * pointing to a bad bounds member otherwise. + */ + private def boundsRealizability(tp: Type) = { + def hasBadBounds(mbr: SingleDenotation) = { + val bounds = mbr.info.bounds + !(bounds.lo <:< bounds.hi) + } + tp.nonClassTypeMembers.find(hasBadBounds) match { + case Some(mbr) => new HasProblemBounds(mbr) + case _ => Realizable + } + } + + /** `Realizable` if `tp` all of `tp`'s non-struct fields have realizable types, + * a `HasProblemField` instance pointing to a bad field otherwise. + */ + private def memberRealizability(tp: Type) = { + def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = + sofar andAlso { + if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy)) + Realizable + else { + checkedFields += fld.symbol + realizability(fld.info).mapError(r => new HasProblemField(fld, r)) + } + } + if (ctx.settings.strict.value) + // check fields only under strict mode for now. + // Reason: An embedded field could well be nullable, which means it + // should not be part of a path and need not be checked; but we cannot recognize + // this situation until we have a typesystem that tracks nullability. + ((Realizable: Realizability) /: tp.fields)(checkField) + else + Realizable + } +} diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 9ccb03b89d47..c1132ce4b34c 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -493,7 +493,7 @@ object Contexts { } @sharable object NoContext extends Context { - lazy val base = unsupported("base") + val base = null override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this) } diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 42d06c2ab0f8..8c9db3a5c965 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -300,7 +300,7 @@ object Flags { */ final val Abstract = commonFlag(23, "abstract") - /** Method is assumed to be stable */ + /** Lazy val or method is known or assumed to be stable and realizable */ final val Stable = termFlag(24, "") /** A case parameter accessor */ diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ac4c870a6ab6..4495b40961af 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -12,6 +12,7 @@ import scala.reflect.io.AbstractFile import Decorators.SymbolIteratorDecorator import ast._ import annotation.tailrec +import CheckRealizable._ import typer.Mode import util.SimpleMap import util.Stats @@ -519,15 +520,8 @@ object SymDenotations { ) /** Is this a denotation of a stable term (or an arbitrary type)? */ - final def isStable(implicit ctx: Context) = { - val isUnstable = - (this is UnstableValue) || - ctx.isVolatile(info) && !hasAnnotation(defn.UncheckedStableAnnot) - (this is Stable) || isType || { - if (isUnstable) false - else { setFlag(Stable); true } - } - } + final def isStable(implicit ctx: Context) = + isType || is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType]) /** Is this a "real" method? A real method is a method which is: * - not an accessor @@ -816,14 +810,11 @@ object SymDenotations { enclClass(symbol, false) } - final def isEffectivelyFinal(implicit ctx: Context): Boolean = { - (this.flags is Flags.PrivateOrFinal) || (!this.owner.isClass) || - ((this.owner.flags is (Flags.ModuleOrFinal)) && (!this.flags.is(Flags.MutableOrLazy))) || - (this.owner.isAnonymousClass) - } + /** A symbol is effectively final if it cannot be overridden in a subclass */ + final def isEffectivelyFinal(implicit ctx: Context): Boolean = + is(PrivateOrFinal) || !owner.isClass || owner.is(ModuleOrFinal) || owner.isAnonymousClass - /** The class containing this denotation which has the given effective name. - */ + /** The class containing this denotation which has the given effective name. */ final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = { val cls = enclosingClass if (cls.effectiveName == name || !cls.exists) cls else cls.owner.enclosingClassNamed(name) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 34febf3be689..4251648a307b 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -3,7 +3,7 @@ package dotc package core import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ -import SymDenotations._, Denotations.Denotation +import SymDenotations._, Denotations.SingleDenotation import config.Printers._ import util.Positions._ import Decorators._ @@ -341,96 +341,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } - /** A type is volatile if its DNF contains an alternative of the form - * {P1, ..., Pn}, {N1, ..., Nk}, where the Pi are parent typerefs and the - * Nj are refinement names, and one the 4 following conditions is met: - * - * 1. At least two of the parents Pi are abstract types. - * 2. One of the parents Pi is an abstract type, and one other type Pj, - * j != i has an abstract member which has the same name as an - * abstract member of the whole type. - * 3. One of the parents Pi is an abstract type, and one of the refinement - * names Nj refers to an abstract member of the whole type. - * 4. One of the parents Pi is an an alias type with a volatile alias - * or an abstract type with a volatile upper bound. - * - * Lazy values are not allowed to have volatile type, as otherwise - * unsoundness can result. - */ - final def isVolatile(tp: Type): Boolean = { - - /** Pre-filter to avoid expensive DNF computation - * If needsChecking returns false it is guaranteed that - * DNF does not contain intersections, or abstract types with upper - * bounds that themselves need checking. - */ - def needsChecking(tp: Type, isPart: Boolean): Boolean = tp match { - case tp: TypeRef => - tp.info match { - case TypeAlias(alias) => - needsChecking(alias, isPart) - case TypeBounds(lo, hi) => - isPart || tp.controlled(isVolatile(hi)) - case _ => false - } - case tp: RefinedType => - needsChecking(tp.parent, true) - case tp: TypeProxy => - needsChecking(tp.underlying, isPart) - case tp: AndType => - true - case tp: OrType => - isPart || needsChecking(tp.tp1, isPart) && needsChecking(tp.tp2, isPart) - case _ => - false - } - - needsChecking(tp, false) && { - DNF(tp) forall { case (parents, refinedNames) => - val absParents = parents filter (_.symbol is Deferred) - absParents.nonEmpty && { - absParents.lengthCompare(2) >= 0 || { - val ap = absParents.head - ((parents exists (p => - (p ne ap) - || p.memberNames(abstractTypeNameFilter, tp).nonEmpty - || p.memberNames(abstractTermNameFilter, tp).nonEmpty)) - || (refinedNames & tp.memberNames(abstractTypeNameFilter, tp)).nonEmpty - || (refinedNames & tp.memberNames(abstractTermNameFilter, tp)).nonEmpty - || isVolatile(ap)) - } - } - } - } - } - - /** The disjunctive normal form of this type. - * This collects a set of alternatives, each alternative consisting - * of a set of typerefs and a set of refinement names. Both sets are represented - * as lists, to obtain a deterministic order. Collected are - * all type refs reachable by following aliases and type proxies, and - * collecting the elements of conjunctions (&) and disjunctions (|). - * The set of refinement names in each alternative - * are the set of names in refinement types encountered during the collection. - */ - final def DNF(tp: Type): List[(List[TypeRef], Set[Name])] = ctx.traceIndented(s"DNF($this)", checks) { - tp.dealias match { - case tp: TypeRef => - (tp :: Nil, Set[Name]()) :: Nil - case RefinedType(parent, name) => - for ((ps, rs) <- DNF(parent)) yield (ps, rs + name) - case tp: TypeProxy => - DNF(tp.underlying) - case AndType(l, r) => - for ((lps, lrs) <- DNF(l); (rps, rrs) <- DNF(r)) - yield (lps | rps, lrs | rrs) - case OrType(l, r) => - DNF(l) | DNF(r) - case tp => - TypeOps.emptyDNF - } - } - private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = { val lazyInfo = new LazyType { // needed so we do not force `formal`. def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { @@ -644,10 +554,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. if (scala2Mode) migrationWarning(msg, pos) scala2Mode } - } object TypeOps { - val emptyDNF = (Nil, Set[Name]()) :: Nil @sharable var track = false // !!!DEBUG } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 47a4f088f754..8595da6405a9 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -573,6 +573,12 @@ object Types { (name, buf) => buf += member(name).asSingleDenotation) } + /** The set of abstract type members of this type. */ + final def nonClassTypeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("nonClassTypeMembers") { + memberDenots(nonClassTypeNameFilter, + (name, buf) => buf += member(name).asSingleDenotation) + } + /** The set of type members of this type */ final def typeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("typeMembers") { memberDenots(typeNameFilter, @@ -3368,6 +3374,15 @@ object Types { } } + /** A filter for names of abstract types of a given type */ + object nonClassTypeNameFilter extends NameFilter { + def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = + name.isTypeName && { + val mbr = pre.member(name) + mbr.symbol.isType && !mbr.symbol.isClass + } + } + /** A filter for names of deferred term definitions of a given type */ object abstractTermNameFilter extends NameFilter { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index f9862bb95115..14edaa7b5c70 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -101,7 +101,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran case tree: TypeTree => tree case _ => if (tree.isType) { - Checking.boundsChecker.traverse(tree) + Checking.typeChecker.traverse(tree) TypeTree(tree.tpe).withPos(tree.pos) } else tree.tpe.widenTermRefExpr match { @@ -180,7 +180,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran val tree1 = if (sym.isClass) tree else { - Checking.boundsChecker.traverse(tree.rhs) + Checking.typeChecker.traverse(tree.rhs) cpy.TypeDef(tree)(rhs = TypeTree(tree.symbol.info)) } super.transform(tree1) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 0b4787d15905..437902d0527e 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -16,6 +16,7 @@ import Trees._ import ProtoTypes._ import Constants._ import Scopes._ +import CheckRealizable._ import ErrorReporting.errorTree import annotation.unchecked import util.Positions._ @@ -49,7 +50,7 @@ object Checking { checkBounds(args, poly.paramBounds, _.substParams(poly, _)) /** Check all AppliedTypeTree nodes in this tree for legal bounds */ - val boundsChecker = new TreeTraverser { + val typeChecker = new TreeTraverser { def traverse(tree: Tree)(implicit ctx: Context) = { tree match { case AppliedTypeTree(tycon, args) => @@ -57,6 +58,12 @@ object Checking { val bounds = tparams.map(tparam => tparam.info.asSeenFrom(tycon.tpe.normalizedPrefix, tparam.owner.owner).bounds) checkBounds(args, bounds, _.substDealias(tparams, _)) + case Select(qual, name) if name.isTypeName => + checkRealizable(qual.tpe, qual.pos) + case SelectFromTypeTree(qual, name) if name.isTypeName => + checkRealizable(qual.tpe, qual.pos) + case SingletonTypeTree(ref) => + checkRealizable(ref.tpe, ref.pos) case _ => } traverseChildren(tree) @@ -83,6 +90,15 @@ object Checking { case _ => } + /** Check that type `tp` is realizable. */ + def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = realizability(tp) + if (rstatus ne Realizable) { + def msg = d"$tp is not a legal path\n since it${rstatus.msg}" + if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) + } + } + /** A type map which checks that the only cycles in a type are F-bounds * and that protects all F-bounded references by LazyRefs. */ @@ -319,8 +335,14 @@ trait Checking { /** Check that type `tp` is stable. */ def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = - if (!tp.isStable && !tp.isErroneous) - ctx.error(d"$tp is not stable", pos) + if (!tp.isStable) ctx.error(d"$tp is not stable", pos) + + /** Check that all type members of `tp` have realizable bounds */ + def checkRealizableBounds(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = boundsRealizability(tp) + if (rstatus ne Realizable) + ctx.error(i"$tp cannot be instantiated since it${rstatus.msg}", pos) + } /** Check that `tp` is a class type with a stable prefix. Also, if `traitReq` is * true check that `tp` is a trait. diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index 7ccb3d10398e..067694bfdfa4 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -211,7 +211,7 @@ object RefChecks { if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { // suppress errors relating toi synthetic companion objects if other override // errors (e.g. relating to the companion class) have already been reported. - if (member.owner == clazz) ctx.error(fullmsg+", member = $member", member.pos) + if (member.owner == clazz) ctx.error(fullmsg, member.pos) else mixinOverrideErrors += new MixinOverrideError(member, fullmsg) hasErrors = true } @@ -330,10 +330,12 @@ object RefChecks { "(this rule is designed to prevent ``accidental overrides'')") } else if (other.isStable && !member.isStable) { // (1.4) overrideError("needs to be a stable, immutable value") - } else if (member.is(Lazy) && !other.isRealMethod && !other.is(Deferred | Lazy)) { - overrideError("cannot override a concrete non-lazy value") - } else if (other.is(Lazy, butNot = Deferred) && !other.isRealMethod && !member.is(Lazy)) { - overrideError("must be declared lazy to override a concrete lazy value") + } else if (member.is(ModuleVal) && !other.isRealMethod && !other.is(Deferred | Lazy)) { + overrideError("may not override a concrete non-lazy value") + } else if (member.is(Lazy, butNot = Module) && !other.isRealMethod && !other.is(Lazy)) { + overrideError("may not override a non-lazy value") + } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { + overrideError("must be declared lazy to override a lazy value") } else if (other.is(Deferred) && member.is(Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) overrideError("cannot be used here - term macros cannot override abstract methods") } else if (other.is(Macro) && !member.is(Macro)) { // (1.10) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index c94c90d1de4a..dd75e960e5ef 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -919,8 +919,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = { // necessary to force annotation trees to be computed. sym.annotations.foreach(_.tree) - // necessary in order to mark the typed ahead annotations as definitiely typed: - untpd.modsDeco(mdef).mods.annotations.mapconserve(typedAnnotation) + // necessary in order to mark the typed ahead annotations as definitely typed: + untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation) } def typedAnnotation(annot: untpd.Tree)(implicit ctx: Context): Tree = track("typedAnnotation") { @@ -986,6 +986,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) checkVariance(impl1) + if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos) assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls) // todo later: check that @@ -1057,6 +1058,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit 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) + if (!ctx.isAfterTyper) checkRealizable(expr1.tpe, imp.expr.pos) assignType(cpy.Import(imp)(expr1, imp.selectors), sym) } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 549dc582c7ce..3610482364ea 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -42,6 +42,7 @@ class tests extends CompilerTest { val testsDir = "./tests/" val posDir = testsDir + "pos/" val posSpecialDir = testsDir + "pos-special/" + val posScala2Dir = testsDir + "pos-scala2/" val negDir = testsDir + "neg/" val runDir = testsDir + "run/" val newDir = testsDir + "new/" @@ -101,15 +102,15 @@ class tests extends CompilerTest { @Test def pos_all = compileFiles(posDir) // twice omitted to make tests run faster - @Test def pos_i871 = compileFile(posSpecialDir, "i871", scala2mode) - @Test def pos_variancesConstr = compileFile(posSpecialDir, "variances-constr", scala2mode) + @Test def pos_scala2_all = compileFiles(posScala2Dir, scala2mode) + @Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes) @Test def new_all = compileFiles(newDir, twice) @Test def neg_abstractOverride() = compileFile(negDir, "abstract-override", xerrors = 2) @Test def neg_blockescapes() = compileFile(negDir, "blockescapesNeg", xerrors = 1) - @Test def neg_bounds() = compileFile(negDir, "bounds", xerrors = 3) + @Test def neg_bounds() = compileFile(negDir, "bounds", xerrors = 2) @Test def neg_typedapply() = compileFile(negDir, "typedapply", xerrors = 3) @Test def neg_typedIdents() = compileDir(negDir, "typedIdents", xerrors = 2) @Test def neg_assignments() = compileFile(negDir, "assignments", xerrors = 3) @@ -121,10 +122,10 @@ class tests extends CompilerTest { @Test def neg_autoTupling2 = compileFile(negDir, "autoTuplingTest", xerrors = 3) @Test def neg_companions = compileFile(negDir, "companions", xerrors = 1) @Test def neg_over = compileFile(negDir, "over", xerrors = 3) - @Test def neg_overrides = compileFile(negDir, "overrides", xerrors = 12) + @Test def neg_overrides = compileFile(negDir, "overrides", xerrors = 14) @Test def neg_overrideClass = compileFile(negDir, "overrideClass", List("-language:Scala2"), xerrors = 1) @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 2) - @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6) + @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 3) @Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12) val negTailcallDir = negDir + "tailcall/" @@ -146,10 +147,10 @@ class tests extends CompilerTest { @Test def neg_typetest = compileFile(negDir, "typetest", xerrors = 1) @Test def neg_t1569_failedAvoid = compileFile(negDir, "t1569-failedAvoid", xerrors = 1) @Test def neg_clashes = compileFile(negDir, "clashes", xerrors = 2) - @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 8) + @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 7) @Test def neg_boundspropagation = compileFile(negDir, "boundspropagation", xerrors = 5) @Test def neg_refinedSubtyping = compileFile(negDir, "refinedSubtyping", xerrors = 2) - @Test def neg_hklower = compileFile(negDir, "hklower", xerrors = 3) + @Test def neg_hklower = compileFile(negDir, "hklower", xerrors = 4) @Test def neg_Iter2 = compileFile(negDir, "Iter2", xerrors = 2) @Test def neg_i0091_infpaths = compileFile(negDir, "i0091-infpaths", xerrors = 3) @Test def neg_i0248_inherit_refined = compileFile(negDir, "i0248-inherit-refined", xerrors = 4) @@ -161,6 +162,9 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) + @Test def neg_i1050 = compileFile(negDir, "i1050", List("-strict"), xerrors = 11) + @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) + @Test def neg_i1050c = compileFile(negDir, "i1050c", xerrors = 8) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) @@ -169,7 +173,7 @@ class tests extends CompilerTest { @Test def neg_selfreq = compileFile(negDir, "selfreq", xerrors = 2) @Test def neg_singletons = compileFile(negDir, "singletons", xerrors = 8) @Test def neg_shadowedImplicits = compileFile(negDir, "arrayclone-new", xerrors = 2) - @Test def neg_ski = compileFile(negDir, "ski", xerrors = 2) + @Test def neg_ski = compileFile(negDir, "ski", xerrors = 12) @Test def neg_traitParamsTyper = compileFile(negDir, "traitParamsTyper", xerrors = 5) @Test def neg_traitParamsMixin = compileFile(negDir, "traitParamsMixin", xerrors = 2) @Test def neg_firstError = compileFile(negDir, "firstError", xerrors = 3) @@ -199,7 +203,7 @@ class tests extends CompilerTest { |./scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala""".stripMargin) @Test def compileIndexedSeq = compileLine("./scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala") - @Test def dotty = compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant"))(allowDeepSubtypes) // note the -deep argument + @Test def dotty = compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict"))(allowDeepSubtypes) // note the -deep argument @Test def dotc_ast = compileDir(dotcDir, "ast") @Test def dotc_config = compileDir(dotcDir, "config") diff --git a/tests/neg/bounds.scala b/tests/neg/bounds.scala index 55eec6941b10..cd6b8d05c9cd 100644 --- a/tests/neg/bounds.scala +++ b/tests/neg/bounds.scala @@ -1,9 +1,7 @@ object Test { - class C[B >: String <: Int](x: B) def g[B >: String <: Int](x: B): Int = x def main(args: Array[String]): Unit = { g("foo") - new C("bar") } def baz[X >: Y, Y <: String](x: X, y: Y) = (x, y) diff --git a/tests/neg/cycles.scala b/tests/neg/cycles.scala index 0dd24c309ebc..ced6f56b529d 100644 --- a/tests/neg/cycles.scala +++ b/tests/neg/cycles.scala @@ -1,42 +1,42 @@ -class Foo[T <: U, U <: T] +class Foo[T <: U, U <: T] // error: cycle -class Bar[T >: T] +class Bar[T >: T] // error: cycle class A { val x: T = ??? - type T <: x.type + type T <: x.type // error: cycle } class B { - type T <: x.type - val x: T = ??? + type T <: x.type // error: cycle + final val x: T = ??? } class C { - val x: D#T = ??? + final val x: D#T = ??? class D { - type T <: x.type + type T <: x.type // error: cycle val z: x.type = ??? } } class E { class F { - type T <: x.type - val z: x.type = ??? + type T <: x.type // error: not stable + val z: x.type = ??? // error: not stable } - val x: F#T = ??? + lazy val x: F#T = ??? } class T1 { - type X = (U, U) // cycle + type X = (U, U) // error: cycle type U = X & Int } class T2 { - type X = (U, U) // cycle + type X = (U, U) // error: cycle type U = X | Int } object T12 { - ??? : (T1 {})#U - ??? : (T2 {})#U + ??? : (T1 {})#U // error: conflicting bounds + ??? : (T2 {})#U // error: conflicting bounds } diff --git a/tests/neg/hklower.scala b/tests/neg/hklower.scala index e29a1545e8d1..5c1ba27ba28f 100644 --- a/tests/neg/hklower.scala +++ b/tests/neg/hklower.scala @@ -1,4 +1,4 @@ -class Test { +class Test { // error conflicting bounds type T[X] // OK type U[X] = T[X] // OK diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala new file mode 100644 index 000000000000..1ade1366a9b8 --- /dev/null +++ b/tests/neg/i1050.scala @@ -0,0 +1,183 @@ +// i1050 checks failing at posttyper +trait A { type L <: Nothing } +trait B { type L >: Any} +object Test { + lazy val x: A & B = ??? + val y: x.L = 1 // error: underlying conflicting bounds + val z: String = y +} +object Test50 { + trait A { + type X = String + } + trait B { + type X = Int + } + lazy val o: A & B = ??? + + def xToString(x: o.X): String = x // error: underlying conflicting bounds + + def intToString(i: Int): String = xToString(i) + + def main(args: Array[String]) = { + val s: String = intToString(1) + } +} +object Test2 { + + trait C { type A } + + type T = C { type A = Any } + type U = C { type A = Nothing } + type X = T & U + + def main(args: Array[String]) = { + val y: X#A = 1 // error: conflicting bounds + val z: String = y + } +} +object Tiark1 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + lazy val p: B + def brand(x: Any): p.L = x // error: nonfinal lazy + } + trait V extends U { + lazy val p: A & B = ??? + } + val v = new V {} + v.brand("boom!") +} +object Tiark2 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + type X <: B + lazy val p: X + def brand(x: Any): p.L = x // error: nonfinal lazy + } + trait V extends U { + type X = B & A + lazy val p: X = ??? + } + val v = new V {} + v.brand("boom!"): Nothing +} +object Tiark3 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + type X <: B + def p2: X + final lazy val p: X = p2 + def brand(x: Any): p.L = x // error: underlying not concrete + } + trait V extends U { + type X = B with A + def p2: X = ??? + } + val v = new V {} + v.brand("boom!"): Nothing +} +object Tiark6 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait X { + val q: A & B = ??? + } + final lazy val p: X = ??? + def brand(x: Any): p.q.L = x // error: conflicting bounds + } + val v = new U {} + v.brand("boom!"): Nothing +} + +object Indirect { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait X { + val q: A & B = ??? + type M = q.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} +object Indirect2 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: A & B = ??? + } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} +object Rec1 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + type L = Int + val r: Y + } + trait X { + val q: Y = ??? + type M = q.r.L // if we are not careful we get a stackoverflow here + } + } +} +object Rec2 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: A & B & Y + } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} +object Indirect3 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: Y & A & B = ??? + } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} diff --git a/tests/neg/i1050a.scala b/tests/neg/i1050a.scala new file mode 100644 index 000000000000..4fea5e3b1426 --- /dev/null +++ b/tests/neg/i1050a.scala @@ -0,0 +1,30 @@ +// i1050 checks failing at refchecks +object Tiark1 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + val p: B + def brand(x: Any): p.L = x + } + trait V extends U { + lazy val p: A & B = ??? // error: may not override + } + val v = new V {} + v.brand("boom!") +} +object Tiark2 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + type X <: B + val p: X + def brand(x: Any): p.L = x + } + trait V extends U { + type X = B & A + lazy val p: X = ??? // error: may not override + } + val v = new V {} + v.brand("boom!"): Nothing +} + diff --git a/tests/neg/i1050c.scala b/tests/neg/i1050c.scala new file mode 100644 index 000000000000..7998d864da24 --- /dev/null +++ b/tests/neg/i1050c.scala @@ -0,0 +1,52 @@ +// i1050 checks failing at typer +object Import { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + lazy val p: B + locally { val x: p.L = ??? } // error: nonfinal lazy + locally { + import p._ + val x: L = ??? // error: nonfinal lazy + } + } +} +object Tiark4 { + trait U { + type Y + trait X { type L = Y } + def compute: X + final lazy val p: X = compute + def brand(x: Y): p.L = x + } + trait V extends U { + type Y >: Any <: Nothing + def compute: X = ??? + } + val v = new V {} // error: cannot be instantiated + v.brand("boom!") +} +object V { // error: cannot be instantiated + type Y >: Any <: Nothing // error: only classes can have declared but undefined members +} +object Tiark5 { + trait A { type L <: Nothing } + trait B { type L >: Any } + def f(x: => A & B)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds + f(???)("boom!") +} +object Tiark5Inherited { + trait A { type L <: Nothing } + trait B { type L >: Any } + trait A2 extends A + trait B2 extends B + def f(x: => A2 & B2)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds + f(???)("boom!") +} +object Tiark7 { + trait A { type L <: Nothing } + trait B { type L >: Any } + def f(x: => B)(y: Any):x.L = y // error: x is not stable + def f1(x: => A & B)(y: Any):Nothing = f(x)(y) // error: type mismatch + f1(???)("boom!"): Nothing +} diff --git a/tests/neg/overrides.scala b/tests/neg/overrides.scala index fe14f91efa3e..81a93a7a2833 100644 --- a/tests/neg/overrides.scala +++ b/tests/neg/overrides.scala @@ -3,10 +3,16 @@ class Foo { type B >: Int <: Int def get: A = 42 } -class Bar extends Foo { +trait T { + lazy val x: Int + val y: Int +} +class Bar extends Foo with T { override type A = Any // error type B >: String <: Any // error override def get: A = "bar" + val x = 2 // error + lazy val y = 3 // error } object Test { def main(args: Array[String]): Unit = { diff --git a/tests/neg/ski.scala b/tests/neg/ski.scala index 6510e66aeefe..b192dc9e2e14 100644 --- a/tests/neg/ski.scala +++ b/tests/neg/ski.scala @@ -17,8 +17,8 @@ trait S2[x <: Term, y <: Term] extends Term { type eval = S2[x, y] } trait S3[x <: Term, y <: Term, z <: Term] extends Term { - type ap[v <: Term] = eval#ap[v] - type eval = x#ap[z]#ap[y#ap[z]]#eval + type ap[v <: Term] = eval#ap[v] // error + type eval = x#ap[z]#ap[y#ap[z]]#eval // error // error } // The K combinator @@ -31,8 +31,8 @@ trait K1[x <: Term] extends Term { type eval = K1[x] } trait K2[x <: Term, y <: Term] extends Term { - type ap[z <: Term] = eval#ap[z] - type eval = x#eval + type ap[z <: Term] = eval#ap[z] // error + type eval = x#eval // error } // The I combinator @@ -41,8 +41,8 @@ trait I extends Term { type eval = I } trait I1[x <: Term] extends Term { - type ap[y <: Term] = eval#ap[y] - type eval = x#eval + type ap[y <: Term] = eval#ap[y] // error + type eval = x#eval // error } // Constants @@ -66,7 +66,7 @@ object Test { type T1 = Equals[Int, Int] // compiles fine type T2 = Equals[String, Int] // error type T3 = Equals[I#ap[c]#eval, c] - type T3a = Equals[I#ap[c]#eval, d]// error + type T3a = Equals[I#ap[c]#eval, d] // error // Ic -> c type T4 = Equals[I#ap[c]#eval, c] @@ -106,11 +106,11 @@ object Test { type eval = A0 } trait A1 extends Term { - type ap[x <: Term] = x#ap[A0]#eval + type ap[x <: Term] = x#ap[A0]#eval // error type eval = A1 } trait A2 extends Term { - type ap[x <: Term] = x#ap[A1]#eval + type ap[x <: Term] = x#ap[A1]#eval // error type eval = A2 } @@ -126,7 +126,7 @@ object Test { type T15 = Equals[NN3#eval, c] trait An extends Term { - type ap[x <: Term] = x#ap[An]#eval + type ap[x <: Term] = x#ap[An]#eval // error type eval = An } diff --git a/tests/neg/t2994.scala b/tests/neg/t2994.scala index 9e9c4ec087b0..9827b19896f6 100644 --- a/tests/neg/t2994.scala +++ b/tests/neg/t2994.scala @@ -7,7 +7,7 @@ object Naturals { type a[s[_ <: NAT] <: NAT, z <: NAT] = z } final class SUCC[n <: NAT] extends NAT { - type a[s[_ <: NAT] <: NAT, z <: NAT] = s[n#a[s, z]] + type a[s[_ <: NAT] <: NAT, z <: NAT] = s[n#a[s, z]] // error: not a legal path } type _0 = ZERO type _1 = SUCC[_0] @@ -21,8 +21,8 @@ object Naturals { // crashes scala-2.8.0 beta1 trait MUL[n <: NAT, m <: NAT] extends NAT { trait curry[n[_[_], _], s[_]] { type f[z <: NAT] = n[s, z] } // can't do double param lists: - // error: `]' expected but `[` found. - type a[s[_ <: NAT] <: NAT, z <: NAT] = n#a[curry[m#a, s]#f, z] + // error: `]' expected but `[` found. // error: wrong number of type arguments + type a[s[_ <: NAT] <: NAT, z <: NAT] = n#a[curry[m#a, s]#f, z] // error: not a legal path // error: not a legal path } } diff --git a/tests/neg/t7278.scala b/tests/neg/t7278.scala new file mode 100644 index 000000000000..9a8292409ee1 --- /dev/null +++ b/tests/neg/t7278.scala @@ -0,0 +1,42 @@ +class A { class E } +class B extends A { class E } +trait C { type E = Int } +trait D { type E = String } +trait EC { type E } + +object Test { + // should not compile (?) + // martin says "I'd argue about that" + // martin retracts his statement: this should not compile + type EE[+X <: EC] = X#E + type EE2[+X <: EC] = X#E // repeat to get error count to 2 + + def fail1(): Unit = { + val b = new B + var x1: EE[A] = null + var x2: EE[B] = new b.E // error: found: B#E, required: A#E +// x1 = x2 // gives a prior type error: B#E, required: A#E, masked to get at the real thing. + } + +/* Not representable in dotty as there are no existential types + def fail2(): Unit = { + val b = new B + var x1: p.E forSome { val p: A } = new b.E // should not compile + var x2: p.E forSome { val p: B } = new b.E + x1 = x2 // should not compile + } +*/ + def fail3(): Unit = { + var x1: EE[C] = 5 + var x2: EE[C & D] = "" + x1 = x2 + } + + def wrap(label: String)(op: => Unit): Unit = + try { op } catch { case x: ClassCastException => println("%30s %s".format(label, x)) } + + def main(args: Array[String]): Unit = { + wrap("Variance and inner classes")(fail1()) + wrap("Linearization and type aliases")(fail3()) + } +} diff --git a/tests/pos-special/i871.scala b/tests/pos-scala2/i871.scala similarity index 100% rename from tests/pos-special/i871.scala rename to tests/pos-scala2/i871.scala diff --git a/tests/pos-scala2/pos-special/i871.scala b/tests/pos-scala2/pos-special/i871.scala new file mode 100644 index 000000000000..2e5f100d8772 --- /dev/null +++ b/tests/pos-scala2/pos-special/i871.scala @@ -0,0 +1,4 @@ +trait Message { + def first(x: Int) + def second +} diff --git a/tests/pos/t1292.scala b/tests/pos-scala2/t1292.scala similarity index 100% rename from tests/pos/t1292.scala rename to tests/pos-scala2/t1292.scala diff --git a/tests/pos/t2994.scala b/tests/pos-scala2/t2994.scala similarity index 100% rename from tests/pos/t2994.scala rename to tests/pos-scala2/t2994.scala diff --git a/tests/pos/t3568.scala b/tests/pos-scala2/t3568.scala similarity index 100% rename from tests/pos/t3568.scala rename to tests/pos-scala2/t3568.scala diff --git a/tests/pos/t3731.scala b/tests/pos-scala2/t3731.scala similarity index 100% rename from tests/pos/t3731.scala rename to tests/pos-scala2/t3731.scala diff --git a/tests/pos/t3833.scala b/tests/pos-scala2/t3833.scala similarity index 100% rename from tests/pos/t3833.scala rename to tests/pos-scala2/t3833.scala diff --git a/tests/pos/t5070.scala b/tests/pos-scala2/t5070.scala similarity index 100% rename from tests/pos/t5070.scala rename to tests/pos-scala2/t5070.scala diff --git a/tests/pos/t5541.scala b/tests/pos-scala2/t5541.scala similarity index 100% rename from tests/pos/t5541.scala rename to tests/pos-scala2/t5541.scala diff --git a/tests/pos-special/variances-constr.scala b/tests/pos-scala2/variances-constr.scala similarity index 100% rename from tests/pos-special/variances-constr.scala rename to tests/pos-scala2/variances-constr.scala diff --git a/tests/pos-special/i871.flags b/tests/pos-special/i871.flags deleted file mode 100644 index a5c112f5a7e7..000000000000 --- a/tests/pos-special/i871.flags +++ /dev/null @@ -1 +0,0 @@ --language:Scala2 \ No newline at end of file diff --git a/tests/pos/i1047.scala b/tests/pos/i1047.scala new file mode 100644 index 000000000000..0f9b54135f6e --- /dev/null +++ b/tests/pos/i1047.scala @@ -0,0 +1,31 @@ +package hello + +object world extends App { + println("hello dotty!") + + trait AnimalPackage { + type Animal <: AnimalU + type AnimalU = { val age: Int } + def newAnimal(a: AnimalU): Animal + def newSubAnimal[T](a: AnimalU & T): Animal & T + } + val p: AnimalPackage = new AnimalPackage { p => + type Animal = AnimalU + override def newAnimal(a: AnimalU): Animal = a + override def newSubAnimal[T](a: AnimalU & T): Animal & T = a + } + val lambda: p.Animal = p.newAnimal(new { val age = 1 }) + trait CatPackage { pc => + type Cat <: p.Animal & pc.CatDelta + type CatDelta = { val meow: Int } + type CatU = p.AnimalU & pc.CatDelta + def newCat(c: CatU): Cat + def newSubCat[T](c: CatU & T): Cat & T + } + val pc: CatPackage = new CatPackage { pc => + type Cat = p.Animal & pc.CatDelta + def newCat(c: CatU): Cat = p.newSubAnimal[pc.CatDelta](c) + def newSubCat[T](c: CatU & T): Cat & T = p.newSubAnimal[pc.CatDelta & T](c) + } + val felix: pc.Cat = pc.newCat(new { val age = 1; val meow = 2 }) +} diff --git a/tests/run/t429.scala b/tests/run/t429.scala index e62a6b307bc9..eeed4b080a15 100644 --- a/tests/run/t429.scala +++ b/tests/run/t429.scala @@ -1,7 +1,7 @@ object Test { abstract class A { Console.print("A"); - val x: Int; + lazy val x: Int; val y: Int = {Console.print("y"); x + 1} } class B extends A { diff --git a/tests/run/t7278.scala b/tests/run/t7278.scala new file mode 100644 index 000000000000..d408ed550e64 --- /dev/null +++ b/tests/run/t7278.scala @@ -0,0 +1,32 @@ +class Foo { + class Bar { + def foo = 1 + } + } + + class SubFoo extends Foo { + class SubBar { + def foo = 42 + } + } + +object Test { + def main(args: Array[String]): Unit = { + +// Let's create some instances: + val foo = new Foo + val fooBar = new foo.Bar + assert(fooBar.foo == 1) //> res0: Int = 1 + // ok + + val subFoo = new SubFoo + val subFooBar = new subFoo.SubBar + assert(subFooBar.foo == 42) //> res1: Int = 42 + // ok + + val superFoo: Foo = subFoo + val superFooBar = new superFoo.Bar + assert(superFooBar.foo == 1) //> res2: Int = 1 + // NOT OK! + } +}