diff --git a/src/dotty/language.scala b/src/dotty/language.scala deleted file mode 100644 index 416a4281ba64..000000000000 --- a/src/dotty/language.scala +++ /dev/null @@ -1,9 +0,0 @@ -package dotty - -object language { - - class Feature - - /** Keep union types */ - val keepUnions = new Feature -} diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index c188bfab4620..7744a5479b4b 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -87,8 +87,12 @@ object Config { */ final val checkLambdaVariance = false - /** Check that certain types cannot be created in erasedTypes phases */ - final val checkUnerased = true + /** Check that certain types cannot be created in erasedTypes phases. + * Note: Turning this option on will get some false negatives, since it is + * possible that And/Or types are still created during erasure as the result + * of some operation on an existing type. + */ + final val checkUnerased = false /** In `derivedSelect`, rewrite * diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 5911af72c6a2..84f531385e59 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -35,6 +35,15 @@ trait ConstraintHandling { /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false + protected var alwaysFluid = false + + /** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */ + def fluidly[T](op: => T): T = { + val saved = alwaysFluid + alwaysFluid = true + try op finally alwaysFluid = saved + } + /** We are currently comparing lambdas. Used as a flag for * optimization: when `false`, no need to do an expensive `pruneLambdaParams` */ @@ -126,14 +135,14 @@ trait ConstraintHandling { final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint - frozenConstraint = true + frozenConstraint = !alwaysFluid try isSubType(tp1, tp2) finally frozenConstraint = saved } final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint - frozenConstraint = true + frozenConstraint = !alwaysFluid try isSameType(tp1, tp2) finally frozenConstraint = saved } @@ -219,7 +228,7 @@ trait ConstraintHandling { // is not a union type, approximate the union type from above by an intersection // of all common base types. if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) - inst = inst.approximateUnion + inst = ctx.harmonizeUnion(inst) // 3. If instance is from below, and upper bound has open named parameters // make sure the instance has all named parameters of the bound. diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 75b75d3d595b..b1c2bc5350d4 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -430,10 +430,8 @@ class Definitions { def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix) def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol - lazy val LanguageModuleRef = ctx.requiredModule("dotty.language") + lazy val LanguageModuleRef = ctx.requiredModule("scala.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass - lazy val Scala2LanguageModuleRef = ctx.requiredModule("scala.language") - def Scala2LanguageModuleClass(implicit ctx: Context) = Scala2LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 4f01c43cfb63..0f95fc59190f 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -336,7 +336,8 @@ object Denotations { (!sym2.isAsConcrete(sym1) || precedes(sym1.owner, sym2.owner) || accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) || - sym1.is(Method) && !sym2.is(Method)) + sym1.is(Method) && !sym2.is(Method)) || + sym1.info.isErroneous /** Sym preference provided types also override */ def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) = diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala index 458f8b82f1c3..68c7655ef577 100644 --- a/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -521,6 +521,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case _ if e1 contains e2 => e2 case _ => mergeError } + case tv1: TypeVar => + e2 match { + case tv2: TypeVar if tv1.instanceOpt eq tv2.instanceOpt => e1 + case _ => mergeError + } case _ if e1 eq e2 => e1 case _ => mergeError } diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index c52264637551..920c9635e62d 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -429,7 +429,6 @@ object StdNames { val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val java: N = "java" - val keepUnions: N = "keepUnions" val key: N = "key" val lang: N = "lang" val length: N = "length" diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 991dd26644d6..52b248abb956 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -8,7 +8,7 @@ import StdNames.{nme, tpnme} import collection.mutable import util.{Stats, DotClass, SimpleMap} import config.Config -import config.Printers.{typr, constr, subtyping} +import config.Printers.{typr, constr, subtyping, noPrinter} import TypeErasure.{erasedLub, erasedGlb} import TypeApplications._ import scala.util.control.NonFatal @@ -324,8 +324,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case AndType(tp11, tp12) => if (tp11.stripTypeVar eq tp12.stripTypeVar) isSubType(tp11, tp2) else thirdTry(tp1, tp2) - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) + case tp1 @ OrType(tp11, tp12) => + def joinOK = tp2.dealias match { + case tp12: HKApply => + // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a + // type parameter, we will instantiate `C` to `A` and then fail when comparing + // with `B[Y]`. To do the right thing, we need to instantiate `C` to the + // common superclass of `A` and `B`. + isSubType(tp1.join, tp2) + case _ => + false + } + joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2) case ErrorType => true case _ => @@ -827,8 +837,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { op1 && { val leftConstraint = constraint constraint = preConstraint - if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) + if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) { + if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint)) + constr.println(i"CUT - prefer $leftConstraint over $constraint") constraint = leftConstraint + } true } || op2 } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index bee69ae691fe..d480a792bab3 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -173,21 +173,53 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } /** Approximate union type by intersection of its dominators. - * See Type#approximateUnion for an explanation. + * That is, replace a union type Tn | ... | Tn + * by the smallest intersection type of base-class instances of T1,...,Tn. + * Example: Given + * + * trait C[+T] + * trait D + * class A extends C[A] with D + * class B extends C[B] with D with E + * + * we approximate `A | B` by `C[A | B] with D` */ - def approximateUnion(tp: Type): Type = { + def orDominator(tp: Type): Type = { + /** a faster version of cs1 intersect cs2 */ def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { val cs2AsSet = new util.HashSet[ClassSymbol](100) cs2.foreach(cs2AsSet.addEntry) cs1.filter(cs2AsSet.contains) } + /** The minimal set of classes in `cs` which derive all other classes in `cs` */ def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match { case c :: rest => val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu if (cs == c.baseClasses) accu1 else dominators(rest, accu1) } + + def mergeRefined(tp1: Type, tp2: Type): Type = { + def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2") + tp1 match { + case tp1 @ RefinedType(parent1, name1, rinfo1) => + tp2 match { + case RefinedType(parent2, `name1`, rinfo2) => + tp1.derivedRefinedType( + mergeRefined(parent1, parent2), name1, rinfo1 | rinfo2) + case _ => fail + } + case tp1 @ TypeRef(pre1, name1) => + tp2 match { + case tp2 @ TypeRef(pre2, `name1`) => + tp1.derivedSelect(pre1 | pre2) + case _ => fail + } + case _ => fail + } + } + def approximateOr(tp1: Type, tp2: Type): Type = { def isClassRef(tp: Type): Boolean = tp match { case tp: TypeRef => tp.symbol.isClass @@ -195,78 +227,70 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case _ => false } - /** If `tp1` and `tp2` are typebounds, try to make one fit into the other - * or to make them equal, by instantiating uninstantiated type variables. - */ - def homogenizedUnion(tp1: Type, tp2: Type): Type = { - tp1 match { - case tp1: TypeBounds => - tp2 match { - case tp2: TypeBounds => - def fitInto(tp1: TypeBounds, tp2: TypeBounds): Unit = { - val nestedCtx = ctx.fresh.setNewTyperState - if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx)) - nestedCtx.typerState.commit() - } - fitInto(tp1, tp2) - fitInto(tp2, tp1) - case _ => - } - case _ => - } - tp1 | tp2 - } - - tp1 match { - case tp1: RefinedType => - tp2 match { - case tp2: RefinedType if tp1.refinedName == tp2.refinedName => - return tp1.derivedRefinedType( - approximateUnion(OrType(tp1.parent, tp2.parent)), - tp1.refinedName, - homogenizedUnion(tp1.refinedInfo, tp2.refinedInfo)) - //.ensuring { x => println(i"approx or $tp1 | $tp2 = $x\n constr = ${ctx.typerState.constraint}"); true } // DEBUG - case _ => - } - case _ => - } - tp1 match { case tp1: RecType => tp1.rebind(approximateOr(tp1.parent, tp2)) case tp1: TypeProxy if !isClassRef(tp1) => - approximateUnion(tp1.superType | tp2) + orDominator(tp1.superType | tp2) case _ => tp2 match { case tp2: RecType => tp2.rebind(approximateOr(tp1, tp2.parent)) case tp2: TypeProxy if !isClassRef(tp2) => - approximateUnion(tp1 | tp2.superType) + orDominator(tp1 | tp2.superType) case _ => val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) val doms = dominators(commonBaseClasses, Nil) - def baseTp(cls: ClassSymbol): Type = - if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls) - else tp.baseTypeWithArgs(cls) + def baseTp(cls: ClassSymbol): Type = { + val base = + if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls) + else tp.baseTypeWithArgs(cls) + base.mapReduceOr(identity)(mergeRefined) + } doms.map(baseTp).reduceLeft(AndType.apply) } } } - if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp - else tp match { + + tp match { case tp: OrType => - approximateOr(tp.tp1, tp.tp2) // Maybe refactor using liftToRec? - case tp @ AndType(tp1, tp2) => - tp derived_& (approximateUnion(tp1), approximateUnion(tp2)) - case tp: RefinedType => - tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo) - case tp: RecType => - tp.rebind(approximateUnion(tp.parent)) + approximateOr(tp.tp1, tp.tp2) case _ => tp } } + /** Given a disjunction T1 | ... | Tn of types with potentially embedded + * type variables, constrain type variables further if this eliminates + * some of the branches of the disjunction. Do this also for disjunctions + * embedded in intersections, as parents in refinements, and in recursive types. + * + * For instance, if `A` is an unconstrained type variable, then + * + * ArrayBuffer[Int] | ArrayBuffer[A] + * + * is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]` + * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` + */ + def harmonizeUnion(tp: Type): Type = tp match { + case tp: OrType => + joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2)) + case tp @ AndType(tp1, tp2) => + tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2)) + case tp: RefinedType => + tp.derivedRefinedType(harmonizeUnion(tp.parent), tp.refinedName, tp.refinedInfo) + case tp: RecType => + tp.rebind(harmonizeUnion(tp.parent)) + case _ => + tp + } + + /** Under -language:Scala2: Replace or-types with their joins */ + private def joinIfScala2(tp: Type) = tp match { + case tp: OrType if scala2Mode => tp.join + case _ => tp + } + /** Not currently needed: * def liftToRec(f: (Type, Type) => Type)(tp1: Type, tp2: Type)(implicit ctx: Context) = { @@ -493,7 +517,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. */ def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = { def toPrefix(sym: Symbol): String = - if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleClass)) "" + if (!sym.exists || (sym eq defn.LanguageModuleClass)) "" else toPrefix(sym.owner) + sym.name + "." def featureName = toPrefix(owner) + feature def hasImport(implicit ctx: Context): Boolean = { @@ -512,13 +536,13 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Is auto-tupling enabled? */ def canAutoTuple = - !featureEnabled(defn.Scala2LanguageModuleClass, nme.noAutoTupling) + !featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling) def scala2Mode = featureEnabled(defn.LanguageModuleClass, nme.Scala2) def dynamicsEnabled = - featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics) + featureEnabled(defn.LanguageModuleClass, nme.dynamics) def testScala2Mode(msg: String, pos: Position) = { if (scala2Mode) migrationWarning(msg, pos) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 2f1b6b829d45..0f81f8c38f5f 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -436,8 +436,12 @@ object Types { tp.cls.findMember(name, pre, excluded) case AndType(l, r) => goAnd(l, r) - case OrType(l, r) => - goOr(l, r) + case tp: OrType => + // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` + // achieved that by narrowing `pre` to each alternative, but it led to merge errors in + // lots of places. The present strategy is instead of widen `tp` using `join` to be a + // supertype of `pre`. + go(tp.join) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, excluded) case ErrorType => @@ -556,7 +560,6 @@ object Types { def goAnd(l: Type, r: Type) = { go(l) & (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) } - def goOr(l: Type, r: Type) = go(l) | (go(r), pre) { val recCount = ctx.findMemberCount + 1 ctx.findMemberCount = recCount @@ -1230,28 +1233,6 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) - /** Approximations of union types: We replace a union type Tn | ... | Tn - * by the smallest intersection type of baseclass instances of T1,...,Tn. - * Example: Given - * - * trait C[+T] - * trait D - * class A extends C[A] with D - * class B extends C[B] with D with E - * - * we approximate `A | B` by `C[A | B] with D` - * - * As a second measure we also homogenize refinements containing - * type variables. For instance, if `A` is an instantiatable type variable, - * then - * - * ArrayBuffer[Int] | ArrayBuffer[A] - * - * is approximated by instantiating `A` to `Int` and returning `ArrayBuffer[Int]` - * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` - */ - def approximateUnion(implicit ctx: Context) = ctx.approximateUnion(this) - /** customized hash code of this type. * NotCached for uncached types. Cached types * compute hash and use it as the type's hashCode. @@ -2233,9 +2214,24 @@ object Types { } abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + assert(tp1.isInstanceOf[ValueType] && tp2.isInstanceOf[ValueType]) def isAnd = false + private[this] var myJoin: Type = _ + private[this] var myJoinPeriod: Period = Nowhere + + /** Replace or type by the closest non-or type above it */ + def join(implicit ctx: Context): Type = { + if (myJoinPeriod != ctx.period) { + myJoin = ctx.orDominator(this) + core.println(i"join of $this == $myJoin") + assert(myJoin != this) + myJoinPeriod = ctx.period + } + myJoin + } + def derivedOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this else OrType.make(tp1, tp2) diff --git a/src/dotty/tools/dotc/transform/ExplicitSelf.scala b/src/dotty/tools/dotc/transform/ExplicitSelf.scala index 618a0f108541..7bb65e5755b2 100644 --- a/src/dotty/tools/dotc/transform/ExplicitSelf.scala +++ b/src/dotty/tools/dotc/transform/ExplicitSelf.scala @@ -20,12 +20,21 @@ import Flags._ * * where `S` is the self type of `C`. * See run/i789.scala for a test case why this is needed. + * + * Also replaces idents referring to the self type with ThisTypes. */ class ExplicitSelf extends MiniPhaseTransform { thisTransform => import ast.tpd._ override def phaseName = "explicitSelf" + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { + case tp: ThisType => + ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") + This(tp.cls) withPos tree.pos + case _ => tree + } + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { case Select(thiz: This, name) if name.isTermName => val cls = thiz.symbol.asClass diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala index 18b030913ef0..19fb3dd0c8e4 100644 --- a/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -121,7 +121,10 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = f.getOrElseUpdate(sym, newSymSet) - def freeVars(sym: Symbol): List[Symbol] = free.getOrElse(sym, Nil).toList + def freeVars(sym: Symbol): List[Symbol] = free get sym match { + case Some(set) => set.toList + case None => Nil + } def proxyOf(sym: Symbol, fv: Symbol) = proxyMap.getOrElse(sym, Map.empty)(fv) diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index efcf95ede016..d62be1a827e0 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -6,25 +6,34 @@ import ast.Trees._ import core._ import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ -/** 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. - * - * For now, only self references are treated. +/** Distribute applications into Block and If nodes */ class Splitter extends MiniPhaseTransform { thisTransform => import ast.tpd._ override def phaseName: String = "splitter" - /** Replace self referencing idents with ThisTypes. */ - override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { - case tp: ThisType => - ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") - This(tp.cls) withPos tree.pos - case _ => tree + /** 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) + +/* The following is no longer necessary, since we select members on the join of an or type: + * /** If we select a name, make sure the node has a symbol. * If necessary, split the qualifier with type tests. * Example: Assume: @@ -108,23 +117,5 @@ class Splitter extends MiniPhaseTransform { thisTransform => 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) +*/ } diff --git a/src/dotty/tools/dotc/transform/TailRec.scala b/src/dotty/tools/dotc/transform/TailRec.scala index 065bcb397690..d99a48af3975 100644 --- a/src/dotty/tools/dotc/transform/TailRec.scala +++ b/src/dotty/tools/dotc/transform/TailRec.scala @@ -252,7 +252,10 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete } else targs val method = if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef) - val thisPassed = if(this.method.owner.isClass) method appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) else method + val thisPassed = + if (this.method.owner.isClass) + method.appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) + else method val res = if (thisPassed.tpe.widen.isParameterless) thisPassed diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index fd8e41dc3fa9..808178369154 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -133,10 +133,7 @@ class TreeChecker extends Phase with SymTransformer { catch { case NonFatal(ex) => //TODO CHECK. Check that we are bootstrapped implicit val ctx: Context = checkingCtx - ctx.echo(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") - ctx.echo(ex.toString) - ctx.echo(ex.getStackTrace.take(30).deep.mkString("\n")) - ctx.echo("<<<") + println(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") throw ex } } @@ -331,8 +328,30 @@ class TreeChecker extends Phase with SymTransformer { checkNotRepeated(super.typedIdent(tree, pt)) } + /** Makes sure the symbol in the tree can be approximately reconstructed by + * calling `member` on the qualifier type. + * Approximately means: The two symbols might be different but one still overrides the other. + */ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase) + val tpe = tree.typeOpt + val sym = tree.symbol + if (!tpe.isInstanceOf[WithFixedSym] && sym.exists && !sym.is(Private)) { + val qualTpe = tree.qualifier.typeOpt + val member = + if (sym.is(Private)) qualTpe.member(tree.name) + else qualTpe.nonPrivateMember(tree.name) + val memberSyms = member.alternatives.map(_.symbol) + assert(memberSyms.exists(mbr => + sym == mbr || + sym.overriddenSymbol(mbr.owner.asClass) == mbr || + mbr.overriddenSymbol(sym.owner.asClass) == sym), + ex"""symbols differ for $tree + |was : $sym + |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, % + |qualifier type : ${tree.qualifier.typeOpt} + |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""") + } checkNotRepeated(super.typedSelect(tree, pt)) } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 7ba66e3d8a61..3461facc141e 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -542,6 +542,13 @@ trait Checking { errorTree(tpt, ex"missing type parameter for ${tpt.tpe}") } else tpt + + /** Check that `tpt` does not refer to a singleton type */ + def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = + if (tpt.tpe.isInstanceOf[SingletonType]) { + errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where") + } + else tpt } trait NoChecking extends Checking { @@ -556,4 +563,5 @@ trait NoChecking extends Checking { override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt + override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt } diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 4f4278468f63..00e92cbfb156 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -898,7 +898,7 @@ class Namer { typer: Typer => // 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 + case _ => ctx.harmonizeUnion(tp.widen) } // Replace aliases to Unit by Unit itself. If we leave the alias in diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 0e6697fb730a..dd5705fbf6e5 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -43,6 +43,11 @@ object ProtoTypes { isCompatible(normalize(tp, pt)(nestedCtx), pt)(nestedCtx) } + private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { + case _: OrType => true + case pt => pt.isRef(defn.UnitClass) + } + /** Check that the result type of the current method * fits the given expected result type. */ @@ -54,7 +59,7 @@ object ProtoTypes { case _ => true } - case _: ValueTypeOrProto if !(pt isRef defn.UnitClass) => + case _: ValueTypeOrProto if !disregardProto(pt) => mt match { case mt: MethodType => mt.isDependent || isCompatible(normalize(mt, pt), pt) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index bbb20bcf5d5d..e423082d5738 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -999,8 +999,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedOrTypeTree(tree: untpd.OrTypeTree)(implicit ctx: Context): OrTypeTree = track("typedOrTypeTree") { - val left1 = typed(tree.left) - val right1 = typed(tree.right) + val where = "in a union type" + val left1 = checkNotSingleton(typed(tree.left), where) + val right1 = checkNotSingleton(typed(tree.right), where) assignType(cpy.OrTypeTree(tree)(left1, right1), left1, right1) } diff --git a/tests/run/OrType.scala b/tests/neg/OrType.scala similarity index 79% rename from tests/run/OrType.scala rename to tests/neg/OrType.scala index 9ab805defbde..a9860db9311c 100644 --- a/tests/run/OrType.scala +++ b/tests/neg/OrType.scala @@ -2,7 +2,7 @@ class B(val x: Int) class C(val x: Double) object Test{ - def bar(x: B | C): Int | Double = x.x + def bar(x: B | C): Int | Double = x.x // error def main(args: Array[String]): Unit = { val b = new B(1) val c = new C(1) diff --git a/tests/neg/singletonOrs.scala b/tests/neg/singletonOrs.scala new file mode 100644 index 000000000000..687e491ef7a1 --- /dev/null +++ b/tests/neg/singletonOrs.scala @@ -0,0 +1,6 @@ +object Test { + def foo: 1 | 2 = 1 // error // error + def bar: 3 | 4 = foo // error // error + def foo: 1 | 2 = 1 // error // error + def bar: 1 = foo +} diff --git a/tests/pickling/unions.scala b/tests/neg/unions.scala similarity index 81% rename from tests/pickling/unions.scala rename to tests/neg/unions.scala index 22e6391e3f57..099a628c93c1 100644 --- a/tests/pickling/unions.scala +++ b/tests/neg/unions.scala @@ -16,11 +16,10 @@ object unions { 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)) - println(y.g(1.0f)) + println(x.f) // error + println(x.g(2)) // error + println(y.f) // error + println(y.g(1.0)) // error class C { private def foo = 0 diff --git a/tests/pos/i1045.scala b/tests/pos/i1045.scala index f5985af9235a..f0cf1df90f8a 100644 --- a/tests/pos/i1045.scala +++ b/tests/pos/i1045.scala @@ -1,7 +1,24 @@ import scala.collection._ + +object EmptyHashMap extends mutable.HashMap[Nothing, Nothing] object T { val newSymbolMap: mutable.HashMap[String, mutable.HashMap[Int, Double]] = mutable.HashMap.empty val map = newSymbolMap.getOrElse("a", mutable.HashMap.empty) map.put(1, 0.0) newSymbolMap.put("a", map) + + /** A map storing free variables of functions and classes */ +// type SymSet = Set[Symbol] +// private val free = new collection.mutable.LinkedHashMap[Symbol, SymSet] +// def freeVars(sym: Symbol): List[Symbol] = free.getOrElse(sym, Nil).toList + + class Tree[X >: Null] { def tpe: X = null } + class Ident[X >: Null] extends Tree[X] + class Apply[X >: Null] extends Tree[X] + + val x: Ident[Symbol] | Apply[Symbol] = ??? + val y = x.tpe + val z: Symbol = y + + } diff --git a/tests/pos/intersection.scala b/tests/pos/intersection.scala index a4c19b5af406..48551920c318 100644 --- a/tests/pos/intersection.scala +++ b/tests/pos/intersection.scala @@ -1,4 +1,3 @@ -import dotty.language.keepUnions object intersection { class A diff --git a/tests/pos/orinf.scala b/tests/pos/orinf.scala new file mode 100644 index 000000000000..30b7fd2f6353 --- /dev/null +++ b/tests/pos/orinf.scala @@ -0,0 +1,6 @@ +object Test { + + def foo(lis: scala.collection.immutable.Set[Int] | scala.collection.immutable.Set[String]) = lis + foo(Set(1)) + foo(Set("")) +} diff --git a/tests/pos/union.scala b/tests/pos/union.scala new file mode 100644 index 000000000000..8b20a8458a48 --- /dev/null +++ b/tests/pos/union.scala @@ -0,0 +1,11 @@ +object Test { + + class A + class B extends A + class C extends A + class D extends A + + val b = true + val x = if (b) new B else new C + val y: B | C = x +} diff --git a/tests/pos/unions.scala b/tests/pos/unions.scala deleted file mode 100644 index e57a96fb9010..000000000000 --- a/tests/pos/unions.scala +++ /dev/null @@ -1,32 +0,0 @@ -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)) - - class C { - private def foo = 0 - class D extends C { - private def foo = 1 - def test(cd: C | D, dc: D | C) = (cd.foo, dc.foo) - } - } - -} diff --git a/tests/run/OrderingTest.scala b/tests/run/OrderingTest.scala index ad5acfa0cb00..561a3daeb381 100644 --- a/tests/run/OrderingTest.scala +++ b/tests/run/OrderingTest.scala @@ -23,7 +23,8 @@ object Test extends dotty.runtime.LegacyApp { testAll(false, true) testAll(1, 2); testAll(1.0, 2.0); - testAll(None, Some(1)); + // testAll(Some(1), Some(2)) // does not work in nsc or dotty + // testAll(None, Some(1)); // does not work in dotty with or-types testAll[Iterable[Int]](List(1), List(1, 2)); testAll[Iterable[Int]](List(1, 2), List(2)); testAll((1, "bar"), (1, "foo")) diff --git a/tests/run/flat-flat-flat.scala b/tests/run/flat-flat-flat.scala index 80868b9c5e40..e0fabe4e798b 100644 --- a/tests/run/flat-flat-flat.scala +++ b/tests/run/flat-flat-flat.scala @@ -2,10 +2,14 @@ object Test { def f1 = List(Iterator(Some(1), None, Some(2)), Iterator(Some(3), None)) def f2 = Iterator(List(Some(1), None, Some(2)), List(Some(3), None), Nil) def f3 = List(Some(Iterator(1)), None, Some(Iterator(2, 3))) + def f4 = List(Some(Iterator(1)), Some(Iterator(2, 3))) + def f5 = Iterator(List(Some(1), Some(2)), List(Some(3)), Nil) def main(args: Array[String]): Unit = { assert(f1.flatten.flatten.toList == List(1, 2, 3)) + assert(f5.flatten.flatten.toList == List(1, 2, 3)) assert(f2.flatten.flatten.toList == List(1, 2, 3)) assert(f3.flatten.flatten.toList == List(1, 2, 3)) + assert(f4.flatten.flatten.toList == List(1, 2, 3)) } }