diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 279dce325c20..0560866a3e6e 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -272,40 +272,31 @@ trait ConstraintHandling[AbstractContext] { } } - /** Widen inferred type `tp` with upper bound `bound`, according to the following rules: - * 1. If `tp` is a singleton type, yet `bound` is not a singleton type, nor a subtype - * of `scala.Singleton`, widen `tp`. - * 2. If `tp` is a union type, yet upper bound is not a union type, - * approximate the union type from above by an intersection of all common base types. + /** Widen inferred type `inst` with upper `bound`, according to the following rules: + * 1. If `inst` is a singleton type, or a union containing some singleton types, + * widen (all) the singleton type(s), provied the result is a subtype of `bound` + * (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint) + * 2. If `inst` is a union type, approximate the union type from above by an intersection + * of all common base types, provied the result is a subtype of `bound`. + * + * Don't do these widenings if `bound` is a subtype of `scala.Singleton`. * * At this point we also drop the @Repeated annotation to avoid inferring type arguments with it, * as those could leak the annotation to users (see run/inferred-repeated-result). */ - def widenInferred(tp: Type, bound: Type)(implicit actx: AbstractContext): Type = { - def isMultiSingleton(tp: Type): Boolean = tp.stripAnnots match { - case tp: SingletonType => true - case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2) - case OrType(tp1, tp2) => isMultiSingleton(tp1) & isMultiSingleton(tp2) - case tp: TypeRef => isMultiSingleton(tp.info.hiBound) - case tp: TypeVar => isMultiSingleton(tp.underlying) - case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi) - case _ => false + def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type = { + def widenOr(tp: Type) = { + val tpw = tp.widenUnion + if ((tpw ne tp) && tpw <:< bound) tpw else tp } - def isOrType(tp: Type): Boolean = tp.dealias match { - case tp: OrType => true - case tp: RefinedOrRecType => isOrType(tp.parent) - case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2) - case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi) - case _ => false + def widenSingle(tp: Type) = { + val tpw = tp.widenSingletons + if ((tpw ne tp) && tpw <:< bound) tpw else tp } - def widenOr(tp: Type) = - if (isOrType(tp) && !isOrType(bound)) tp.widenUnion - else tp - def widenSingle(tp: Type) = - if (isMultiSingleton(tp) && !isMultiSingleton(bound) && - !isSubTypeWhenFrozen(bound, defn.SingletonType)) tp.widen - else tp - widenOr(widenSingle(tp)).dropRepeatedAnnot + val wideInst = + if (isSubTypeWhenFrozen(bound, defn.SingletonType)) inst + else widenOr(widenSingle(inst)) + wideInst.dropRepeatedAnnot } /** The instance type of `param` in the current constraint (which contains `param`). @@ -316,7 +307,7 @@ trait ConstraintHandling[AbstractContext] { */ def instanceType(param: TypeParamRef, fromBelow: Boolean)(implicit actx: AbstractContext): Type = { val inst = approximation(param, fromBelow).simplified - if (fromBelow) widenInferred(inst, constraint.fullUpperBound(param)) else inst + if (fromBelow) widenInferred(inst, param) else inst } /** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index dcf03d4f51c7..8a69184f0846 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -418,7 +418,15 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { case _ => false } - joinOK || recur(tp11, tp2) && recur(tp12, tp2) + def widenOK = + (tp2.widenSingletons eq tp2) && + (tp1.widenSingletons ne tp1) && + recur(tp1.widenSingletons, tp2) + + if (tp2.atoms.nonEmpty && canCompare(tp2.atoms)) + tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms) + else + widenOK || joinOK || recur(tp11, tp2) && recur(tp12, tp2) case tp1: MatchType => val reduced = tp1.reduced if (reduced.exists) recur(reduced, tp2) else thirdTry @@ -573,10 +581,28 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { } compareTypeLambda case OrType(tp21, tp22) => - val tp1a = tp1.widenDealiasKeepRefiningAnnots + if (tp2.atoms.nonEmpty && canCompare(tp2.atoms)) + return tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms) || + tp1.isRef(NothingClass) + + // The next clause handles a situation like the one encountered in i2745.scala. + // We have: + // + // x: A | B, x.type <:< A | X where X is a type variable + // + // We should instantiate X to B instead of x.type or A | B. To do this, we widen + // the LHS to A | B and recur *without indicating that this is a lowApprox*. The + // latter point is important since otherwise we would not get to instantiate X. + // If that succeeds, fine. If not we continue and hit the `either` below. + // That second path is important to handle comparisons with unions of singletons, + // as in `1 <:< 1 | 2`. + val tp1w = tp1.widen + if ((tp1w ne tp1) && recur(tp1w, tp2)) + return true + + val tp1a = tp1.dealiasKeepRefiningAnnots if (tp1a ne tp1) - // Follow the alias; this might avoid truncating the search space in the either below - // Note that it's safe to widen here because singleton types cannot be part of `|`. + // Follow the alias; this might lead to an OrType on the left which needs to be split return recur(tp1a, tp2) // Rewrite T1 <: (T211 & T212) | T22 to T1 <: (T211 | T22) and T1 <: (T212 | T22) @@ -1038,6 +1064,23 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { else None } + /** Check whether we can compare the given set of atoms with another to determine + * a subtype test between OrTypes. There is one situation where this is not + * the case, which has to do with SkolemTypes. TreeChecker sometimes expects two + * types to be equal that have different skolems. To account for this, we identify + * two different skolems in all phases `p`, where `p.isTyper` is false. + * But in that case comparing two sets of atoms that contain skolems + * for equality would give the wrong result, so we should not use the sets + * for comparisons. + */ + def canCompare(atoms: Set[Type]): Boolean = + ctx.phase.isTyper || { + val hasSkolems = new ExistsAccumulator(_.isInstanceOf[SkolemType]) { + override def stopAtStatic = true + } + !atoms.exists(hasSkolems(false, _)) + } + /** Subtype test for corresponding arguments in `args1`, `args2` according to * variances in type parameters `tparams2`. * @@ -1499,30 +1542,38 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { /** The greatest lower bound of a list types */ final def glb(tps: List[Type]): Type = ((AnyType: Type) /: tps)(glb) + def widenInUnions(implicit ctx: Context): Boolean = ctx.scala2Mode || ctx.erasedTypes + /** The least upper bound of two types * @param canConstrain If true, new constraints might be added to simplify the lub. * @note We do not admit singleton types in or-types as lubs. */ def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false): Type = /*>|>*/ trace(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain)", subtyping, show = true) /*<|<*/ { - if (tp1 eq tp2) tp1 - else if (!tp1.exists) tp1 - else if (!tp2.exists) tp2 - else if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp1 - else if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp2 - else { - val t1 = mergeIfSuper(tp1, tp2, canConstrain) - if (t1.exists) t1 - else { - val t2 = mergeIfSuper(tp2, tp1, canConstrain) - if (t2.exists) t2 - else { - val tp1w = tp1.widen - val tp2w = tp2.widen - if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w) - else orType(tp1w, tp2w) // no need to check subtypes again - } + if (tp1 eq tp2) return tp1 + if (!tp1.exists) return tp1 + if (!tp2.exists) return tp2 + if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) return tp1 + if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) return tp2 + val atoms1 = tp1.atoms + if (atoms1.nonEmpty && !widenInUnions) { + val atoms2 = tp2.atoms + if (atoms2.nonEmpty) { + if (atoms1.subsetOf(atoms2)) return tp2 + if (atoms2.subsetOf(atoms1)) return tp1 + if ((atoms1 & atoms2).isEmpty) return orType(tp1, tp2) } } + val t1 = mergeIfSuper(tp1, tp2, canConstrain) + if (t1.exists) return t1 + + val t2 = mergeIfSuper(tp2, tp1, canConstrain) + if (t2.exists) return t2 + + def widen(tp: Type) = if (widenInUnions) tp.widen else tp.widenIfUnstable + val tp1w = widen(tp1) + val tp2w = widen(tp2) + if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w) + else orType(tp1w, tp2w) // no need to check subtypes again } /** The least upper bound of a list of types */ @@ -2213,6 +2264,11 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { super.isSubType(tp1, tp2, approx) } + override def recur(tp1: Type, tp2: Type): Boolean = + traceIndented(s"${show(tp1)} <:< ${show(tp2)} recur ${if (frozenConstraint) " frozen" else ""}") { + super.recur(tp1, tp2) + } + override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.member(name).info)}") { super.hasMatchingMember(name, tp1, tp2) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index d7caa319df21..f41a710dcde5 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -131,7 +131,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * 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` + * we approximate `A | B` by `C[A | B] with D`. + * + * Before we do that, we try to find a common non-class supertype of T1 | ... | Tn + * in a "best effort", ad-hoc way by selectively widening types in `T1, ..., Tn` + * and stopping if the resulting union simplifies to a type that is not a disjunction. */ def orDominator(tp: Type): Type = { @@ -188,29 +192,83 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case _ => false } + // Step 1: Get RecTypes and ErrorTypes out of the way, tp1 match { - case tp1: RecType => - tp1.rebind(approximateOr(tp1.parent, tp2)) - case tp1: TypeProxy if !isClassRef(tp1) => - orDominator(tp1.superType | tp2) - case err: ErrorType => - err + case tp1: RecType => return tp1.rebind(approximateOr(tp1.parent, tp2)) + case err: ErrorType => return err case _ => - tp2 match { - case tp2: RecType => - tp2.rebind(approximateOr(tp1, tp2.parent)) - case tp2: TypeProxy if !isClassRef(tp2) => - orDominator(tp1 | tp2.superType) - case err: ErrorType => - err - case _ => - val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) - val doms = dominators(commonBaseClasses, Nil) - def baseTp(cls: ClassSymbol): Type = - tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied) - doms.map(baseTp).reduceLeft(AndType.apply) - } } + tp2 match { + case tp2: RecType => return tp2.rebind(approximateOr(tp1, tp2.parent)) + case err: ErrorType => return err + case _ => + } + + // Step 2: Try to widen either side. This is tricky and incomplete. + // An illustration is in test pos/padTo.scala: Here we need to compute the join of + // + // `A | C` under the constraints `B >: A` and `C <: B` + // + // where `A, B, C` are type parameters. + // Widening `A` to its upper bound would give `Any | C`, i.e. `Any`. + // But widening `C` first would give `A | B` and then `B`. + // So we need to widen `C` first. But how to decide this in general? + // In the algorithm below, we try to widen both sides (once), and then proceed as follows: + // + // 2.0. If no widening succeeds, proceed with step 3. + // 2.1. If only one widening succeeds, continue with that one. + // 2.2. If the two widened types are in a subtype relationship, continue with the smaller one. + // 2.3. If exactly one of the two types is a singleton type, continue with the widened singleton type. + // 2.4. If the widened tp2 is a supertype of tp1, return widened tp2. + // 2.5. If the widened tp1 is a supertype of tp2, return widened tp1. + // 2.6. Otherwise, continue with widened tp1 + // + // At steps 4-6 we lose possible solutions, since we have to make an + // arbitrary choice which side to widen. A better solution would look at + // the constituents of each operand (if the operand is an OrType again) and + // try to widen them selectively in turn. But this might lead to a combinatorial + // explosion of possibilities. + // + // Another approach could be to store information contained in lower bounds + // on both sides. So if `B >: A` we'd also record that `A <: B` and therefore + // widening `A` would yield `B` instead of `Any`, so we'd still be on the right track. + // This looks feasible if lower bounds are type parameters, but tricky if they + // are something else. We'd have to extract the strongest possible + // constraint over all type parameters that is implied by a lower bound. + // This looks related to an algorithmic problem arising in GADT matching. + // + // However, this alone is still not enough. There are other sources of incompleteness, + // for instance arising from mis-aligned refinements. + val tp1w = tp1 match { + case tp1: TypeProxy if !isClassRef(tp1) => tp1.superType.widenExpr + case _ => tp1 + } + val tp2w = tp2 match { + case tp2: TypeProxy if !isClassRef(tp2) => tp2.superType.widenExpr + case _ => tp2 + } + if ((tp1w ne tp1) || (tp2w ne tp2)) { + val isSingle1 = tp1.isInstanceOf[SingletonType] + val isSingle2 = tp2.isInstanceOf[SingletonType] + return { + if (tp2w eq tp2) orDominator(tp1w | tp2) // 2.1 + else if (tp1w eq tp1) orDominator(tp1 | tp2w) // 2.1 + else if (tp1w frozen_<:< tp2w) orDominator(tp1w | tp2) // 2.2 + else if (tp2w frozen_<:< tp1w) orDominator(tp1 | tp2w) // 2.2 + else if (isSingle1 && !isSingle2) orDominator(tp1w | tp2) // 2.3 + else if (isSingle2 && !isSingle1) orDominator(tp1 | tp2w) // 2.3 + else if (tp1 frozen_<:< tp2w) tp2w // 2.4 + else if (tp2 frozen_<:< tp1w) tp1w // 2.5 + else orDominator(tp1w | tp2) // 2.6 + } + } + + // Step 3: Intersect base classes of both sides + val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) + val doms = dominators(commonBaseClasses, Nil) + def baseTp(cls: ClassSymbol): Type = + tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied) + doms.map(baseTp).reduceLeft(AndType.apply) } tp match { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3bb56a77e7da..49908bacdcd6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1033,7 +1033,8 @@ object Types { case _ => this } - /** If this type contains embedded union types, replace them by their joins. + /** Widen this type and if the result contains embedded union types, replace + * them by their joins. * "Embedded" means: inside intersectons or recursive types, or in prefixes of refined types. * If an embedded union is found, we first try to simplify or eliminate it by * re-lubbing it while allowing type parameters to be constrained further. @@ -1046,7 +1047,7 @@ object Types { * is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]` * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` */ - def widenUnion(implicit ctx: Context): Type = this match { + def widenUnion(implicit ctx: Context): Type = widen match { case OrType(tp1, tp2) => ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match { case union: OrType => union.join @@ -1058,10 +1059,52 @@ object Types { tp.derivedRefinedType(tp.parent.widenUnion, tp.refinedName, tp.refinedInfo) case tp: RecType => tp.rebind(tp.parent.widenUnion) + case tp => + tp + } + + /** Widen all top-level singletons reachable by dealiasing + * and going to the operands of & and |. + * Overridden and cached in OrType. + */ + def widenSingletons(implicit ctx: Context): Type = dealias match { + case tp: SingletonType => + tp.widen + case tp: OrType => + val tp1w = tp.widenSingletons + if (tp1w eq tp) this else tp1w + case tp: AndType => + val tp1w = tp.tp1.widenSingletons + val tp2w = tp.tp2.widenSingletons + if ((tp.tp1 eq tp1w) && (tp.tp2 eq tp2w)) this else tp1w & tp2w case _ => this } + /** If this type is an alias of a disjunction of stable singleton types, + * these types as a set, otherwise the empty set. + * Overridden and cached in OrType. + */ + def atoms(implicit ctx: Context): Set[Type] = dealias match { + case tp: SingletonType if tp.isStable => + def normalize(tp: Type): Type = tp match { + case tp: SingletonType => + tp.underlying.dealias match { + case tp1: SingletonType => normalize(tp1) + case _ => + tp match { + case tp: TermRef => tp.derivedSelect(normalize(tp.prefix)) + case _ => tp + } + } + case _ => tp + } + Set.empty + normalize(tp) + case tp: OrType => tp.atoms + case tp: AndType => tp.tp1.atoms & tp.tp2.atoms + case _ => Set.empty + } + private def dealias1(keep: AnnotatedType => Context => Boolean)(implicit ctx: Context): Type = this match { case tp: TypeRef => if (tp.symbol.isClass) tp @@ -2777,6 +2820,31 @@ object Types { myJoin } + private[this] var atomsRunId: RunId = NoRunId + private[this] var myAtoms: Set[Type] = _ + private[this] var myWidened: Type = _ + + private def ensureAtomsComputed()(implicit ctx: Context): Unit = + if (atomsRunId != ctx.runId) { + val atoms1 = tp1.atoms + val atoms2 = tp2.atoms + myAtoms = if (atoms1.nonEmpty && atoms2.nonEmpty) atoms1 | atoms2 else Set.empty + val tp1w = tp1.widenSingletons + val tp2w = tp2.widenSingletons + myWidened = if ((tp1 eq tp1w) && (tp2 eq tp2w)) this else tp1w | tp2w + atomsRunId = ctx.runId + } + + override def atoms(implicit ctx: Context): Set[Type] = { + ensureAtomsComputed() + myAtoms + } + + override def widenSingletons(implicit ctx: Context): Type = { + ensureAtomsComputed() + myWidened + } + 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/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala b/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala index e4536c054fb1..d72ef6f71db5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala @@ -36,7 +36,11 @@ class ElimOpaque extends MiniPhase with DenotTransformer { info = TypeAlias(ref.opaqueAlias), initFlags = ref.flags &~ (Opaque | Deferred)) case ref: SymDenotation if sym.isOpaqueCompanion => - val ref1 = ref.copySymDenotation(initFlags = ref.flags &~ Opaque) + val cinfo = sym.asClass.classInfo + val RefinedType(sourceRef, _, _) = cinfo.selfInfo + val ref1 = ref.copySymDenotation( + info = cinfo.derivedClassInfo(selfInfo = sourceRef), + initFlags = ref.flags &~ Opaque) ref1.registeredCompanion = NoSymbol ref1 case _ if sym.isOpaqueHelper => diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 5bea0e84f050..b85bc32e5bb8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -827,11 +827,6 @@ trait Checking { } 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.isSingleton) errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where") - else tpt - /** Verify classes extending AnyVal meet the requirements */ def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context): Unit = Checking.checkDerivedValueClass(clazz, stats) @@ -1048,7 +1043,6 @@ trait NoChecking extends ReChecking { override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context): Unit = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt - override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context): Unit = () override def checkTraitInheritance(parentSym: Symbol, cls: ClassSymbol, pos: SourcePosition)(implicit ctx: Context): Unit = () override def checkCaseInheritance(parentSym: Symbol, caseCls: ClassSymbol, pos: SourcePosition)(implicit ctx: Context): Unit = () diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 17cb3fd66bf4..03b67a7a91fe 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -81,7 +81,7 @@ object ErrorReporting { if (tree.tpe.widen.exists) i"${exprStr(tree)} does not take ${kind}parameters" else { - //new FatalError("").printStackTrace() //DEBUG + if (ctx.settings.Ydebug.value) new FatalError("").printStackTrace() i"undefined: $tree # ${tree.uniqueId}: ${tree.tpe.toString} at ${ctx.phase}" } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 412410f36769..bbf1541fe222 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1326,7 +1326,7 @@ class Namer { typer: Typer => val tp1 = tp.widenTermRefExpr match { case ctp: ConstantType if isInlineVal => ctp case ref: TypeRef if ref.symbol.is(ModuleClass) => tp - case _ => tp.widen.widenUnion + case _ => tp.widenUnion } tp1.dropRepeatedAnnot } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 90523de12f6a..f07563ec49f3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1361,7 +1361,7 @@ class Typer extends Namer checkSimpleKinded(checkNoWildcard(arg))) else if (tpt1.symbol == defn.orType) checkedArgs = checkedArgs.mapconserve(arg => - checkNotSingleton(checkSimpleKinded(checkNoWildcard(arg)), "in a union type")) + checkSimpleKinded(checkNoWildcard(arg))) assignType(cpy.AppliedTypeTree(tree)(tpt1, checkedArgs), tpt1, checkedArgs) } } diff --git a/compiler/test/dotc/pos-from-tasty.blacklist b/compiler/test/dotc/pos-from-tasty.blacklist index dfcf33628e03..1c1b2e32e86c 100644 --- a/compiler/test/dotc/pos-from-tasty.blacklist +++ b/compiler/test/dotc/pos-from-tasty.blacklist @@ -3,3 +3,6 @@ collections_1.scala # Infinite loop t3612.scala + +# Other failure +t802.scala diff --git a/docs/docs/reference/new-types/union-types-spec.md b/docs/docs/reference/new-types/union-types-spec.md index 7d1b195b84eb..a073abd5e0c4 100644 --- a/docs/docs/reference/new-types/union-types-spec.md +++ b/docs/docs/reference/new-types/union-types-spec.md @@ -88,7 +88,7 @@ treatment of singleton types which are also widened to their underlying type unless explicitly specified. and the motivation is the same: inferring types which are "too precise" can lead to unintuitive typechecking issues later on. -Note: Since this behavior severely limits the usability of union types, it might +Note: Since this behavior limits the usability of union types, it might be changed in the future. For example by not widening unions that have been explicitly written down by the user and not inferred, or by not widening a type argument when the corresponding type parameter is covariant. See @@ -149,7 +149,3 @@ the erased lub is computed as follows: The reason to pick last is that we prefer classes over traits that way, which leads to more predictable bytecode and (?) faster dynamic dispatch. -## Limitations - -In a union type `A | B`, neither `A` nor `B` is allowed to be a singleton type. -This is an implementation restriction that may be lifted in the future. diff --git a/docs/docs/reference/new-types/union-types.md b/docs/docs/reference/new-types/union-types.md index e00150b97eb0..d3140c1f4770 100644 --- a/docs/docs/reference/new-types/union-types.md +++ b/docs/docs/reference/new-types/union-types.md @@ -3,7 +3,8 @@ layout: doc-page title: "Union Types" --- -Used on types, the `|` operator creates a union type. +A union type `A | B` has as values all values of type `A` and also all values of type `B`. + ```scala case class UserName(name: String) { @@ -22,13 +23,11 @@ def help(id: UserName | Password) = { } ``` -Union types are dual of intersection types. Values of type `A | B` are -all values of type `A` and all values of type `B`. `|` is _commutative_: +Union types are duals of intersection types. `|` is _commutative_: `A | B` is the same type as `B | A`. The compiler will assign a union type to an expression only if such a -type is explicitly given. -This can be seen in the following REPL transcript: +type is explicitly given. This can be seen in the following REPL transcript: ```scala scala> val password = Password(123) diff --git a/tests/neg/singletonOrs.scala b/tests/neg/singletonOrs.scala index 3bf103d68815..cad48f524606 100644 --- a/tests/neg/singletonOrs.scala +++ b/tests/neg/singletonOrs.scala @@ -1,6 +1,34 @@ object Test { - def a: 1 | 2 = 1 // error // error - def b: 3 | 4 = a // error // error - def c: 1 | 2 = 1 // error // error - def d: 1 = a + def a: 1 | 2 = 1 + def b: 3 | 4 = a // error + def c: 1 | 2 = 1 + def d: 1 = a // error } + +// Following examples are from issue #1551 +object Test2 { + type ABC = 'A' | 'B' | 'C' + type A2F = ABC | 'D' | 'E' | 'F' + + def foo(x: A2F) = () + foo('F') // ok + foo('G') // error +} + +object Get2As1 { + class OrIntroFn[T, U, TU >: T|U]{ + type V = TU + def tuToV(): (TU => V) = p => p + } + class Or11X[X] extends OrIntroFn[1&1, X, (1&1|X)]{ + def get2as11X:V = tuToV()(2) // error + } + class Or11Nothing extends Or11X[Nothing] + def get2as1:1 = new Or11Nothing().get2as11X // error + + def main(a:Array[String]) = { + println(get2as1) // prints 2 + val one:1 = get2as1 + println(one) // prints 1 + } +} \ No newline at end of file diff --git a/tests/pos/compare-singletons.scala b/tests/pos/compare-singletons.scala new file mode 100644 index 000000000000..a85674a5bd4c --- /dev/null +++ b/tests/pos/compare-singletons.scala @@ -0,0 +1,12 @@ +class Refl { + type S +} + +class A[R <: Refl & Singleton](val r: R) { + def s: r.S = ??? +} + +class B[R <: Refl & Singleton](val r: R) { + val a = new A(r) + val s: r.S = a.s +} \ No newline at end of file diff --git a/tests/pos/i6288.scala b/tests/pos/i6288.scala new file mode 100644 index 000000000000..1360dcf2b5d0 --- /dev/null +++ b/tests/pos/i6288.scala @@ -0,0 +1,15 @@ +object A { + opaque type T = 3 + object T { + val x: T = 3 + } +} + +object O { + val x = 3 + opaque type T = x.type + object T { + def wrap(a: x.type): T = a // was an error, now OK + def unwrap(a: T): x.type = a // OK + } +} \ No newline at end of file diff --git a/tests/pos/padTo.scala b/tests/pos/padTo.scala new file mode 100644 index 000000000000..0c594647fb9f --- /dev/null +++ b/tests/pos/padTo.scala @@ -0,0 +1,34 @@ +object Test { + abstract class AbstractIterator[A] extends Iterator[A] { + override def padTo[B >: A](len: Int, elem: B): Iterator[B] = { + val it = this + new AbstractIterator[B] { + private[this] var i = 0 + + // This illustrates a tricky situation for joins + // The RHS of `val b` has type `A | elem.type` where `elem: B` + // If we widen `A` first in the join we get a RHS of `Any` and a subsequent + // type error. The right thing to do is to widen `elem.type` to `B` first. + def next(): B = { + val b = + if (it.hasNext) it.next() + else if (i < len) elem + else Iterator.empty.next() + i += 1 + b + } + + // Same problem, but without singleton types. + // This one fails to compile in Scala 2. + def f[C <: B](c: () => C): B = { + val b = + if (it.hasNext) it.next() + else c() + b + } + + def hasNext: Boolean = it.hasNext || i < len + } + } + } +} diff --git a/tests/pos/single-unions.scala b/tests/pos/single-unions.scala new file mode 100644 index 000000000000..613f84a71fb1 --- /dev/null +++ b/tests/pos/single-unions.scala @@ -0,0 +1,15 @@ +object A { + val x1: 3 | 4 = 3 + val x2: 3 | 4 = 4 + val x3: 3 | 4 = if (???) 3 else 4 +} + +// The following example is from issue #1551 +object Test1 { + sealed trait Fence[+T, +S] + case object End extends Fence[Nothing, Nothing] + case class Post[+T, +S](value: T, next: Panel[T, S] | End.type) extends Fence[T, S] + case class Panel[+T, +S](value: S, next: Post[T, S]) extends Fence[T, S] + + val fence = Post(1, Panel("two", Post(3, End))) +} diff --git a/tests/run-macros/tasty-extractors-1.check b/tests/run-macros/tasty-extractors-1.check index c2c2cae482a1..21105a0af06f 100644 --- a/tests/run-macros/tasty-extractors-1.check +++ b/tests/run-macros/tasty-extractors-1.check @@ -35,7 +35,7 @@ Inlined(None, Nil, Block(List(Literal(Constant.Int(1)), Literal(Constant.Int(2)) Type.ConstantType(Constant.Int(3)) Inlined(None, Nil, If(Typed(Literal(Constant.Boolean(true)), TypeIdent("Boolean")), Literal(Constant.Int(1)), Literal(Constant.Int(2)))) -Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix()))) +Type.OrType(Type.ConstantType(Constant.Int(1)), Type.ConstantType(Constant.Int(2))) Inlined(None, Nil, Match(Literal(Constant.String("a")), List(CaseDef(Pattern.Value(Literal(Constant.String("a"))), None, Block(Nil, Literal(Constant.Unit())))))) Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix()))) @@ -65,13 +65,13 @@ Inlined(None, Nil, Match(Ident("Nil"), List(CaseDef(Pattern.Unapply(TypeApply(Se Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix()))) Inlined(None, Nil, Try(Literal(Constant.Int(1)), List(CaseDef(Pattern.WildcardPattern(), None, Block(Nil, Literal(Constant.Unit())))), None)) -Type.OrType(Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix()))), Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix())))) +Type.OrType(Type.ConstantType(Constant.Int(1)), Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix())))) Inlined(None, Nil, Try(Literal(Constant.Int(2)), Nil, Some(Literal(Constant.Unit())))) Type.ConstantType(Constant.Int(2)) Inlined(None, Nil, Try(Literal(Constant.Int(3)), List(CaseDef(Pattern.WildcardPattern(), None, Block(Nil, Literal(Constant.Unit())))), Some(Literal(Constant.Unit())))) -Type.OrType(Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix()))), Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix())))) +Type.OrType(Type.ConstantType(Constant.Int(3)), Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix())))) Inlined(None, Nil, Apply(Select(Literal(Constant.String("a")), "=="), List(Literal(Constant.String("b"))))) Type.SymRef(IsClassDefSymbol(), Type.ThisType(Type.SymRef(IsPackageDefSymbol(), NoPrefix())))