diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 11d72d1d2993..b83e4a9557f7 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -76,8 +76,7 @@ class Compiler { new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts - new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations - new CrossCastAnd) :: // Normalize selections involving intersection types. + new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatentations List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new InlinePatterns, // Remove placeholders of inlined patterns new VCInlineMethods, // Inlines calls to value class methods diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 329494f150aa..0798f1fd57f6 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -440,7 +440,7 @@ class Definitions { def AnyKindType: TypeRef = AnyKindClass.typeRef @tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _)) - @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _)) + @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false)) /** Marker method to indicate an argument to a call-by-name parameter. * Created by byNameClosures and elimByName, eliminated by Erasure, diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index c8058daf8c66..1a1550183f93 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -93,6 +93,9 @@ trait Hashable { protected final def doHash(bs: Binders, x1: Int, tp2: Type): Int = finishHash(bs, hashing.mix(hashSeed, x1), 1, tp2) + protected final def doHash(bs: Binders, x1: Int, tp2: Type, tp3: Type): Int = + finishHash(bs, hashing.mix(hashSeed, x1), 1, tp2, tp3) + protected final def doHash(bs: Binders, tp1: Type, tp2: Type): Int = finishHash(bs, hashSeed, 0, tp1, tp2) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index de1b595f62f7..b14a0066760a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -428,6 +428,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case Some(b) => return b case None => + def widenOK = + (tp2.widenSingletons eq tp2) + && (tp1.widenSingletons ne tp1) + && recur(tp1.widenSingletons, tp2) + def joinOK = tp2.dealiasKeepRefiningAnnots match { case tp2: AppliedType if !tp2.tycon.typeSymbol.isClass => // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a @@ -439,25 +444,30 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } + // If LHS is a hard union, constrain any type variables of the RHS with it as lower bound + // before splitting the LHS into its constituents. That way, the RHS variables are + // constraint by the hard union and can be instantiated to it. If we just split and add + // the two parts of the LHS separately to the constraint, the lower bound would become + // a soft union. + def constrainRHSVars(tp2: Type): Boolean = tp2.dealiasKeepRefiningAnnots match + case tp2: TypeParamRef if constraint contains tp2 => compareTypeParamRef(tp2) + case AndType(tp21, tp22) => constrainRHSVars(tp21) && constrainRHSVars(tp22) + case _ => true + + // An & on the left side loses information. We compensate by also trying the join. + // This is less ad-hoc than it looks since we produce joins in type inference, + // and then need to check that they are indeed supertypes of the original types + // under -Ycheck. Test case is i7965.scala. def containsAnd(tp: Type): Boolean = tp.dealiasKeepRefiningAnnots match case tp: AndType => true case OrType(tp1, tp2) => containsAnd(tp1) || containsAnd(tp2) case _ => false - def widenOK = - (tp2.widenSingletons eq tp2) && - (tp1.widenSingletons ne tp1) && - recur(tp1.widenSingletons, tp2) - widenOK || joinOK - || recur(tp11, tp2) && recur(tp12, tp2) + || (tp1.isSoft || constrainRHSVars(tp2)) && recur(tp11, tp2) && recur(tp12, tp2) || containsAnd(tp1) && recur(tp1.join, tp2) - // An & on the left side loses information. Compensate by also trying the join. - // This is less ad-hoc than it looks since we produce joins in type inference, - // and then need to check that they are indeed supertypes of the original types - // under -Ycheck. Test case is i7965.scala. - case tp1: MatchType => + case tp1: MatchType => val reduced = tp1.reduced if (reduced.exists) recur(reduced, tp2) else thirdTry case _: FlexType => @@ -511,35 +521,36 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling fourthTry } + def compareTypeParamRef(tp2: TypeParamRef): Boolean = + assumedTrue(tp2) || { + val alwaysTrue = + // The following condition is carefully formulated to catch all cases + // where the subtype relation is true without needing to add a constraint + // It's tricky because we might need to either approximate tp2 by its + // lower bound or else widen tp1 and check that the result is a subtype of tp2. + // So if the constraint is not yet frozen, we do the same comparison again + // with a frozen constraint, which means that we get a chance to do the + // widening in `fourthTry` before adding to the constraint. + if (frozenConstraint) recur(tp1, bounds(tp2).lo) + else isSubTypeWhenFrozen(tp1, tp2) + alwaysTrue || + frozenConstraint && (tp1 match { + case tp1: TypeParamRef => constraint.isLess(tp1, tp2) + case _ => false + }) || { + if (canConstrain(tp2) && !approx.low) + addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry + } + } + def thirdTry: Boolean = tp2 match { case tp2 @ AppliedType(tycon2, args2) => compareAppliedType2(tp2, tycon2, args2) case tp2: NamedType => thirdTryNamed(tp2) case tp2: TypeParamRef => - def compareTypeParamRef = - assumedTrue(tp2) || { - val alwaysTrue = - // The following condition is carefully formulated to catch all cases - // where the subtype relation is true without needing to add a constraint - // It's tricky because we might need to either approximate tp2 by its - // lower bound or else widen tp1 and check that the result is a subtype of tp2. - // So if the constraint is not yet frozen, we do the same comparison again - // with a frozen constraint, which means that we get a chance to do the - // widening in `fourthTry` before adding to the constraint. - if (frozenConstraint) recur(tp1, bounds(tp2).lo) - else isSubTypeWhenFrozen(tp1, tp2) - alwaysTrue || - frozenConstraint && (tp1 match { - case tp1: TypeParamRef => constraint.isLess(tp1, tp2) - case _ => false - }) || { - if (canConstrain(tp2) && !approx.low) - addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else fourthTry - } - } - compareTypeParamRef + compareTypeParamRef(tp2) case tp2: RefinedType => def compareRefinedSlow: Boolean = { val name2 = tp2.refinedName @@ -616,7 +627,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } } compareTypeLambda - case OrType(tp21, tp22) => + case tp2 as OrType(tp21, tp22) => compareAtoms(tp1, tp2) match case Some(b) => return b case _ => @@ -648,12 +659,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // solutions. The rewriting delays the point where we have to choose. tp21 match { case AndType(tp211, tp212) => - return recur(tp1, OrType(tp211, tp22)) && recur(tp1, OrType(tp212, tp22)) + return recur(tp1, OrType(tp211, tp22, tp2.isSoft)) && recur(tp1, OrType(tp212, tp22, tp2.isSoft)) case _ => } tp22 match { case AndType(tp221, tp222) => - return recur(tp1, OrType(tp21, tp221)) && recur(tp1, OrType(tp21, tp222)) + return recur(tp1, OrType(tp21, tp221, tp2.isSoft)) && recur(tp1, OrType(tp21, tp222, tp2.isSoft)) case _ => } either(recur(tp1, tp21), recur(tp1, tp22)) || fourthTry @@ -2123,7 +2134,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val t2 = distributeOr(tp2, tp1) if (t2.exists) t2 else if (isErased) erasedLub(tp1, tp2) - else liftIfHK(tp1, tp2, OrType(_, _), _ | _, _ & _) + else liftIfHK(tp1, tp2, OrType(_, _, soft = true), _ | _, _ & _) } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index d127348619ee..55b35731543d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -150,7 +150,14 @@ object TypeOps: tp.derivedAlias(simplify(tp.alias, theMap)) case AndType(l, r) if !ctx.mode.is(Mode.Type) => simplify(l, theMap) & simplify(r, theMap) - case OrType(l, r) if !ctx.mode.is(Mode.Type) => + case tp as OrType(l, r) + if !ctx.mode.is(Mode.Type) + && (tp.isSoft || defn.isBottomType(l) || defn.isBottomType(r)) => + // Normalize A | Null and Null | A to A even if the union is hard (i.e. + // explicitly declared), but not if -Yexplicit-nulls is set. The reason is + // that in this case the normal asSeenFrom machinery is not prepared to deal + // with Nulls (which have no base classes). Under -Yexplicit-nulls, we take + // corrective steps, so no widening is wanted. simplify(l, theMap) | simplify(r, theMap) case AnnotatedType(parent, annot) if !ctx.mode.is(Mode.Type) && annot.symbol == defn.UncheckedVarianceAnnot => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index aa550be11c0e..283bee2e853e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1040,7 +1040,7 @@ object Types { def safe_& (that: Type)(using Context): Type = (this, that) match { case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => TypeBounds( - OrType.makeHk(lo1.stripLazyRef, lo2.stripLazyRef), + OrType.makeHk(lo1.stripLazyRef, lo2.stripLazyRef), AndType.makeHk(hi1.stripLazyRef, hi2.stripLazyRef)) case _ => this & that @@ -1151,10 +1151,11 @@ object Types { case _ => this } - /** Widen this type and if the result contains embedded union types, replace + /** Widen this type and if the result contains embedded soft union types, replace * them by their joins. - * "Embedded" means: inside type lambdas, intersections or recursive types, or in prefixes of refined types. - * If an embedded union is found, we first try to simplify or eliminate it by + * "Embedded" means: inside type lambdas, intersections or recursive types, + * in prefixes of refined types, or in hard union types. + * If an embedded soft union is found, we first try to simplify or eliminate it by * re-lubbing it while allowing type parameters to be constrained further. * Any remaining union types are replaced by their joins. * @@ -1168,7 +1169,7 @@ object Types { * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ - def widenUnion(using Context): Type = widen match { + def widenUnion(using Context): Type = widen match case tp @ OrNull(tp1): OrType => // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. val tp1Widen = tp1.widenUnionWithoutNull @@ -1176,16 +1177,14 @@ object Types { else tp.derivedOrType(tp1Widen, defn.NullType) case tp => tp.widenUnionWithoutNull - } - def widenUnionWithoutNull(using Context): Type = widen match { - case tp @ OrType(lhs, rhs) => - TypeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { + def widenUnionWithoutNull(using Context): Type = widen match + case tp @ OrType(lhs, rhs) if tp.isSoft => + TypeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match case union: OrType => union.join case res => res - } - case tp @ AndType(tp1, tp2) => - tp derived_& (tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull) + case tp: AndOrType => + tp.derivedAndOrType(tp.tp1.widenUnionWithoutNull, tp.tp2.widenUnionWithoutNull) case tp: RefinedType => tp.derivedRefinedType(tp.parent.widenUnion, tp.refinedName, tp.refinedInfo) case tp: RecType => @@ -1194,7 +1193,6 @@ object Types { tp.derivedLambdaType(resType = tp.resType.widenUnion) case tp => tp - } /** Widen all top-level singletons reachable by dealiasing * and going to the operands of & and |. @@ -2917,8 +2915,9 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(using Context) = if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this - else if (isAnd) AndType.make(tp1, tp2, checkValid = true) - else OrType.make(tp1, tp2) + else this match + case tp: OrType => OrType.make(tp1, tp2, tp.isSoft) + case tp: AndType => AndType.make(tp1, tp2, checkValid = true) } abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType { @@ -2992,6 +2991,7 @@ object Types { abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType { def isAnd: Boolean = false + def isSoft: Boolean private var myBaseClassesPeriod: Period = Nowhere private var myBaseClasses: List[ClassSymbol] = _ /** Base classes of are the intersection of the operand base classes. */ @@ -3052,32 +3052,33 @@ object Types { myWidened } - def derivedOrType(tp1: Type, tp2: Type)(using Context): Type = - if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this - else OrType.make(tp1, tp2) + def derivedOrType(tp1: Type, tp2: Type, soft: Boolean = isSoft)(using Context): Type = + if ((tp1 eq this.tp1) && (tp2 eq this.tp2) && soft == isSoft) this + else OrType.make(tp1, tp2, soft) - override def computeHash(bs: Binders): Int = doHash(bs, tp1, tp2) + override def computeHash(bs: Binders): Int = + doHash(bs, if isSoft then 0 else 1, tp1, tp2) override def eql(that: Type): Boolean = that match { - case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) + case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) && isSoft == that.isSoft case _ => false } } - final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) + final class CachedOrType(tp1: Type, tp2: Type, override val isSoft: Boolean) extends OrType(tp1, tp2) object OrType { - def apply(tp1: Type, tp2: Type)(using Context): OrType = { + def apply(tp1: Type, tp2: Type, soft: Boolean)(using Context): OrType = { assertUnerased() - unique(new CachedOrType(tp1, tp2)) + unique(new CachedOrType(tp1, tp2, soft)) } - def make(tp1: Type, tp2: Type)(using Context): Type = + def make(tp1: Type, tp2: Type, soft: Boolean)(using Context): Type = if (tp1 eq tp2) tp1 - else apply(tp1, tp2) + else apply(tp1, tp2, soft) /** Like `make`, but also supports higher-kinded types as argument */ def makeHk(tp1: Type, tp2: Type)(using Context): Type = - TypeComparer.liftIfHK(tp1, tp2, OrType(_, _), makeHk, _ & _) + TypeComparer.liftIfHK(tp1, tp2, OrType(_, _, soft = true), makeHk, _ & _) } /** An extractor object to pattern match against a nullable union. @@ -3089,7 +3090,7 @@ object Types { */ object OrNull { def apply(tp: Type)(using Context) = - OrType(tp, defn.NullType) + OrType(tp, defn.NullType, soft = false) def unapply(tp: Type)(using Context): Option[Type] = if (ctx.explicitNulls) { val tp1 = tp.stripNull() @@ -3107,7 +3108,7 @@ object Types { */ object OrUncheckedNull { def apply(tp: Type)(using Context) = - OrType(tp, defn.UncheckedNullAliasType) + OrType(tp, defn.UncheckedNullAliasType, soft = false) def unapply(tp: Type)(using Context): Option[Type] = if (ctx.explicitNulls) { val tp1 = tp.stripUncheckedNull diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 617dc0f0b18b..af6d2cefd621 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -368,7 +368,7 @@ class TreeUnpickler(reader: TastyReader, case ANDtype => AndType(readType(), readType()) case ORtype => - OrType(readType(), readType()) + OrType(readType(), readType(), soft = false) case SUPERtype => SuperType(readType(), readType()) case MATCHtype => @@ -1222,7 +1222,7 @@ class TreeUnpickler(reader: TastyReader, val args = until(end)(readTpt()) val ownType = if (tycon.symbol == defn.andType) AndType(args(0).tpe, args(1).tpe) - else if (tycon.symbol == defn.orType) OrType(args(0).tpe, args(1).tpe) + else if (tycon.symbol == defn.orType) OrType(args(0).tpe, args(1).tpe, soft = false) else tycon.tpe.safeAppliedTo(args.tpes) untpd.AppliedTypeTree(tycon, args).withType(ownType) case ANNOTATEDtpt => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b3eda0d946b3..3da41b003adb 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -214,8 +214,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { // of AndType and OrType to account for associativity case AndType(tp1, tp2) => toTextInfixType(tpnme.raw.AMP, tp1, tp2) { toText(tpnme.raw.AMP) } - case OrType(tp1, tp2) => - toTextInfixType(tpnme.raw.BAR, tp1, tp2) { toText(tpnme.raw.BAR) } + case tp as OrType(tp1, tp2) => + toTextInfixType(tpnme.raw.BAR, tp1, tp2) { + if tp.isSoft && printDebug then toText(tpnme.ZOR) else toText(tpnme.raw.BAR) + } case tp @ EtaExpansion(tycon) if !printDebug && appliedText(tp.asInstanceOf[HKLambda].resType).isEmpty => // don't eta contract if the application would be printed specially diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala index c8543f822688..930306f1bd64 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala @@ -1823,7 +1823,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext: end OrTypeTypeTest object OrType extends OrTypeModule: - def apply(lhs: TypeRepr, rhs: TypeRepr): OrType = Types.OrType(lhs, rhs) + def apply(lhs: TypeRepr, rhs: TypeRepr): OrType = Types.OrType(lhs, rhs, soft = false) def unapply(x: OrType): Option[(TypeRepr, TypeRepr)] = Some((x.left, x.right)) end OrType diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 2a1b536e76fb..1fdb513a2f55 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -822,14 +822,16 @@ import transform.SymUtils._ |""" } - class PatternMatchExhaustivity(uncoveredFn: => String)(using Context) + class PatternMatchExhaustivity(uncoveredFn: => String, hasMore: Boolean)(using Context) extends Message(PatternMatchExhaustivityID) { def kind = "Pattern Match Exhaustivity" lazy val uncovered = uncoveredFn def msg = + val addendum = if hasMore then "(More unmatched cases are elided)" else "" em"""|${hl("match")} may not be exhaustive. | - |It would fail on pattern case: $uncovered""" + |It would fail on pattern case: $uncovered + |$addendum""" def explain = diff --git a/compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala b/compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala deleted file mode 100644 index 55c31612be87..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/CrossCastAnd.scala +++ /dev/null @@ -1,29 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Types.AndType -import dotty.tools.dotc.transform.MegaPhase._ -import tpd._ - - - -/** - * This transform makes sure that all private member selections from - * AndTypes are performed from the first component of AndType. - * This is needed for correctness of erasure. See `tests/run/PrivateAnd.scala` - */ -class CrossCastAnd extends MiniPhase { - - override def phaseName: String = "crossCast" - - override def transformSelect(tree: tpd.Select)(using Context): tpd.Tree = { - - lazy val qtype = tree.qualifier.tpe.widen - val sym = tree.symbol - if (sym.is(Flags.Private) && !sym.isConstructor && qtype.typeSymbol != sym.owner) - cpy.Select(tree)(tree.qualifier.cast(AndType(qtype.baseType(sym.owner), tree.qualifier.tpe)), tree.name) - else tree - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 9ffad2b1218d..f571610db2f0 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -71,7 +71,7 @@ object TypeTestsCasts { case tref: TypeRef if tref.typeSymbol.isPatternBound => if (variance == 1) tref.info.hiBound else if (variance == -1) tref.info.loBound - else OrType(defn.AnyType, defn.NothingType) + else OrType(defn.AnyType, defn.NothingType, soft = true) case _ => mapOver(tp) } }.apply(tp) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index bb13095249fd..582429823fac 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -150,19 +150,22 @@ trait SpaceLogic { }) /** Flatten space to get rid of `Or` for pretty print */ - def flatten(space: Space)(using Context): List[Space] = space match { + def flatten(space: Space)(using Context): Seq[Space] = space match { case Prod(tp, fun, spaces, full) => - spaces.map(flatten) match { - case Nil => Prod(tp, fun, Nil, full) :: Nil - case ss => - ss.foldLeft(List[Prod]()) { (acc, flat) => - if (acc.isEmpty) flat.map(s => Prod(tp, fun, s :: Nil, full)) - else for (Prod(tp, fun, ss, full) <- acc; s <- flat) yield Prod(tp, fun, ss :+ s, full) - } + val ss = LazyList(spaces: _*).map(flatten) + + ss.foldLeft(LazyList(Nil : List[Space])) { (acc, flat) => + for { sps <- acc; s <- flat } + yield sps :+ s + }.map { sps => + Prod(tp, fun, sps, full) } + case Or(spaces) => - spaces.flatMap(flatten _) - case _ => List(space) + LazyList(spaces: _*).flatMap(flatten) + + case _ => + List(space) } /** Is `a` a subspace of `b`? Equivalent to `a - b == Empty`, but faster */ @@ -477,8 +480,8 @@ class SpaceEngine(using Context) extends SpaceLogic { else args.map(arg => erase(arg, inArray = false)) tp.derivedAppliedType(erase(tycon, inArray), args2) - case OrType(tp1, tp2) => - OrType(erase(tp1, inArray), erase(tp2, inArray)) + case tp as OrType(tp1, tp2) => + OrType(erase(tp1, inArray), erase(tp2, inArray), tp.isSoft) case AndType(tp1, tp2) => AndType(erase(tp1, inArray), erase(tp2, inArray)) @@ -838,8 +841,10 @@ class SpaceEngine(using Context) extends SpaceLogic { s != Empty && (!checkGADTSAT || satisfiable(s)) } - if (uncovered.nonEmpty) - report.warning(PatternMatchExhaustivity(show(uncovered)), sel.srcPos) + + if uncovered.nonEmpty then + val hasMore = uncovered.lengthCompare(6) > 0 + report.warning(PatternMatchExhaustivity(show(uncovered.take(6)), hasMore), sel.srcPos) } private def redundancyCheckable(sel: Tree): Boolean = @@ -862,7 +867,7 @@ class SpaceEngine(using Context) extends SpaceLogic { if (ctx.explicitNulls || selTyp.classSymbol.isPrimitiveValueClass) project(selTyp) else - project(OrType(selTyp, constantNullType)) + project(OrType(selTyp, constantNullType, soft = false)) // in redundancy check, take guard as false in order to soundly approximate def projectPrevCases(cases: List[CaseDef]): Space = diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 494aec369996..ad4a6c73cd88 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -463,7 +463,7 @@ trait TypeAssigner { val ownType = if (sameLength(tparams, args)) if (tycon.symbol == defn.andType) AndType(args(0).tpe, args(1).tpe) - else if (tycon.symbol == defn.orType) OrType(args(0).tpe, args(1).tpe) + else if (tycon.symbol == defn.orType) OrType(args(0).tpe, args(1).tpe, soft = false) else tycon.tpe.appliedTo(args.tpes) else wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.srcPos) tree.withType(ownType) diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 646af4682a4f..fcaa3004ec19 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -18,7 +18,7 @@ import org.junit.Test class PatmatExhaustivityTest { val testsDir = "tests/patmat" // stop-after: patmatexhaust-huge.scala crash compiler - val options = List("-color:never", "-Ystop-after:crossCast", "-Ycheck-all-patmat", "-classpath", TestConfiguration.basicClasspath) + val options = List("-color:never", "-Ystop-after:explicitSelf", "-Ycheck-all-patmat", "-classpath", TestConfiguration.basicClasspath) private def compile(files: Seq[String]): Seq[String] = { val stringBuffer = new StringWriter() diff --git a/scala3doc/test/dotty/dokka/DottyTestRunner.scala b/scala3doc/test/dotty/dokka/DottyTestRunner.scala index 7377a462c008..b2dc7e163cfb 100644 --- a/scala3doc/test/dotty/dokka/DottyTestRunner.scala +++ b/scala3doc/test/dotty/dokka/DottyTestRunner.scala @@ -79,7 +79,6 @@ abstract class DottyAbstractCoreTest extends AbstractCoreTest: case l: DocumentableElement => (l.annotations ++ Seq(" ") ++ l.modifiers ++ Seq(l.name) ++ l.signature).map { case s: String => s - case (s: String, _) => s case Link(s: String, _) => s } case _ => Seq() diff --git a/tests/patmat/i4030.scala b/tests/patmat/i4030.scala index 65a10f24d30d..c0c7a76eb813 100644 --- a/tests/patmat/i4030.scala +++ b/tests/patmat/i4030.scala @@ -9,5 +9,6 @@ object TestGADT { def f[A <: Seq[_], B, Foo >: A => B](v: Root[Foo], u: Root[Foo]) = (v, u) match { case (C3(), C3()) => } - f(C3[Seq[_], Long](), C4[Seq[_], Long]()) + // The following line no longer type checks + // f(C3[Seq[_], Long](), C4[Seq[_], Long]()) } diff --git a/tests/patmat/i8922c.check b/tests/patmat/i8922c.check index 8c54da6d9a0c..be8f69feb884 100644 --- a/tests/patmat/i8922c.check +++ b/tests/patmat/i8922c.check @@ -1 +1 @@ -26: Pattern Match Exhaustivity: (_, _, _) +26: Pattern Match Exhaustivity: (true, _: String, _), (true, _: Double, _), (true, true, _), (true, false, _), (true, (), _), (false, _: String, _) diff --git a/tests/pos/widen-union.scala b/tests/pos/widen-union.scala new file mode 100644 index 000000000000..b0b64f0dc6c6 --- /dev/null +++ b/tests/pos/widen-union.scala @@ -0,0 +1,24 @@ + +object Test1: + val x: Int | String = 1 + val y = x + val z: Int | String = y + +object Test2: + type Sig = Int | String + def consistent(x: Sig, y: Sig): Boolean = ???// x == y + + def consistentLists(xs: List[Sig], ys: List[Sig]): Boolean = + xs.corresponds(ys)(consistent) // OK + || xs.corresponds(ys)(consistent(_, _)) // error, found: Any, required: Int | String + +object Test3: + + def g[X](x: X | String): Int = ??? + def y: Boolean | String = ??? + g[Boolean](y) + g(y) + g[Boolean](identity(y)) + g(identity(y)) + + diff --git a/tests/run/i8726.scala b/tests/run/i8726.scala index c13c99652cc9..b54404992182 100644 --- a/tests/run/i8726.scala +++ b/tests/run/i8726.scala @@ -1,5 +1,5 @@ case class A(a: Int) -object C { def unapply(a: A): true | true = true } +object C { def unapply(a: A): true = true } @main def Test = (A(1): A | A) match { case C() => "OK" } \ No newline at end of file