diff --git a/compiler/src/dotty/tools/dotc/core/Constraint.scala b/compiler/src/dotty/tools/dotc/core/Constraint.scala index c634f847e510..a205b1f3b1a1 100644 --- a/compiler/src/dotty/tools/dotc/core/Constraint.scala +++ b/compiler/src/dotty/tools/dotc/core/Constraint.scala @@ -138,6 +138,9 @@ abstract class Constraint extends Showable { /** The same as this constraint, but with `tv` marked as hard. */ def withHard(tv: TypeVar)(using Context): This + /** Mark toplevel type vars in `tp` as hard. */ + def hardenTypeVars(tp: Type)(using Context): This + /** Gives for each instantiated type var that does not yet have its `inst` field * set, the instance value stored in the constraint. Storing instances in constraints * is done only in a temporary way for contexts that may be retracted diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 0328cea9b3ca..7b94eb3f67d3 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -748,9 +748,18 @@ class OrderingConstraint(private val boundsMap: ParamBounds, } if isRemovable(param.binder) then current = current.remove(param.binder) current.dropDeps(param) + replacedTypeVar match + case replacedTypeVar: TypeVar if isHard(replacedTypeVar) => current = current.hardenTypeVars(replacement) + case _ => current.checkWellFormed() end replace + def hardenTypeVars(tp: Type)(using Context): OrderingConstraint = tp.dealiasKeepRefiningAnnots match + case tp: TypeVar if contains(tp.origin) => withHard(tp) + case tp: TypeParamRef if contains(tp) => hardenTypeVars(typeVarOfParam(tp)) + case tp: AndOrType => hardenTypeVars(tp.tp1).hardenTypeVars(tp.tp2) + case _ => this + def remove(pt: TypeLambda)(using Context): This = { def removeFromOrdering(po: ParamOrdering) = { def removeFromBoundss(key: TypeLambda, bndss: Array[List[TypeParamRef]]): Array[List[TypeParamRef]] = { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8df809dc9ee6..25f35a9dd32d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -501,17 +501,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } - /** Mark toplevel type vars in `tp2` as hard in the current constraint */ - def hardenTypeVars(tp2: Type): Unit = tp2.dealiasKeepRefiningAnnots match - case tvar: TypeVar if constraint.contains(tvar.origin) => - constraint = constraint.withHard(tvar) - case tp2: TypeParamRef if constraint.contains(tp2) => - hardenTypeVars(constraint.typeVarOfParam(tp2)) - case tp2: AndOrType => - hardenTypeVars(tp2.tp1) - hardenTypeVars(tp2.tp2) - case _ => - val res = widenOK || joinOK || recur(tp11, tp2) && recur(tp12, tp2) || containsAnd(tp1) @@ -534,7 +523,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // is marked so that it converts all soft unions in its lower bound to hard unions // before it is instantiated. The reason is that the variable's instance type will // be a supertype of (decomposed and reconstituted) `tp1`. - hardenTypeVars(tp2) + constraint = constraint.hardenTypeVars(tp2) res @@ -2388,7 +2377,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case Atoms.Range(lo2, hi2) => if hi1.subsetOf(lo2) then return tp2 if hi2.subsetOf(lo1) then return tp1 - if (hi1 & hi2).isEmpty then return orType(tp1, tp2) + if (hi1 & hi2).isEmpty then return orType(tp1, tp2, isSoft = isSoft) case none => case none => val t1 = mergeIfSuper(tp1, tp2, canConstrain) diff --git a/tests/pos/i18626.min1.scala b/tests/pos/i18626.min1.scala new file mode 100644 index 000000000000..ae895db4b29c --- /dev/null +++ b/tests/pos/i18626.min1.scala @@ -0,0 +1,14 @@ +sealed trait Animal +object Cat extends Animal +object Dog extends Animal + +type Mammal = Cat.type | Dog.type + +class Test: + def t1 = + val mammals: List[Mammal] = ??? + val result = mammals.head + val mammal: Mammal = result // was: Type Mismatch Error: + // Found: (result : Animal) + // Required: Mammal + () diff --git a/tests/pos/i18626.scala b/tests/pos/i18626.scala new file mode 100644 index 000000000000..0fda265f3812 --- /dev/null +++ b/tests/pos/i18626.scala @@ -0,0 +1,32 @@ +trait Random[F1[_]]: + def element[T1](list: Seq[T1]): F1[T1] = ??? + +trait Monad[F2[_]]: + def map[A1, B1](fa: F2[A1])(f: A1 => B1): F2[B1] + +object Monad: + extension [F3[_]: Monad, A3](fa: F3[A3]) + def map[B3](f: A3 => B3): F3[B3] = ??? + +sealed trait Animal +object Cat extends Animal +object Dog extends Animal + +type Mammal = Cat.type | Dog.type +val mammals: List[Mammal] = ??? + +class Work[F4[_]](random: Random[F4])(using mf: Monad[F4]): + def result1: F4[Mammal] = + mf.map(fa = random.element(mammals))(a => a) + + def result2: F4[Mammal] = Monad.map(random.element(mammals))(a => a) + + import Monad.* + + def result3: F4[Mammal] = random + .element(mammals) + .map { a => + a // was: Type Mismatch Error: + // Found: (a : Animal) + // Required: Cat.type | Dog.type +}