diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index ac6cb78f9e91..2a9417858006 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -297,14 +297,16 @@ class OrderingConstraint(private val boundsMap: ParamBounds, */ private trait ConstraintAwareTraversal[T] extends TypeAccumulator[T]: + /** Does `param` have bounds in the current constraint? */ + protected def hasBounds(param: TypeParamRef): Boolean = entry(param).isInstanceOf[TypeBounds] + override def tyconTypeParams(tp: AppliedType)(using Context): List[ParamInfo] = def tparams(tycon: Type): List[ParamInfo] = tycon match case tycon: TypeVar if !tycon.inst.exists => tparams(tycon.origin) - case tycon: TypeParamRef => - entry(tycon) match - case _: TypeBounds => tp.tyconTypeParams - case tycon1 if tycon1.typeParams.nonEmpty => tycon1.typeParams - case _ => tp.tyconTypeParams + case tycon: TypeParamRef if !hasBounds(tycon) => + val entryParams = entry(tycon).typeParams + if entryParams.nonEmpty then entryParams + else tp.tyconTypeParams case _ => tp.tyconTypeParams tparams(tp.tycon) @@ -312,12 +314,19 @@ class OrderingConstraint(private val boundsMap: ParamBounds, this(x, tp.prefix) end ConstraintAwareTraversal - private class Adjuster(srcParam: TypeParamRef)(using Context) + /** A type traverser that adjust dependencies originating from a given type + * @param ignoreBinding if not null, a parameter that is assumed to be still uninstantiated. + * This is necessary to handle replacements. + */ + private class Adjuster(srcParam: TypeParamRef, ignoreBinding: TypeParamRef | Null)(using Context) extends TypeTraverser, ConstraintAwareTraversal[Unit]: var add: Boolean = compiletime.uninitialized val seen = util.HashSet[LazyRef]() + override protected def hasBounds(param: TypeParamRef) = + (param eq ignoreBinding) || super.hasBounds(param) + def update(deps: ReverseDeps, referenced: TypeParamRef): ReverseDeps = val prev = deps.at(referenced) val newSet = if add then prev + srcParam else prev - srcParam @@ -326,12 +335,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds, def traverse(t: Type) = t match case param: TypeParamRef => - entry(param) match - case _: TypeBounds => - if variance >= 0 then coDeps = update(coDeps, param) - if variance <= 0 then contraDeps = update(contraDeps, param) - case tp => - traverse(tp) + if hasBounds(param) then + if variance >= 0 then coDeps = update(coDeps, param) + if variance <= 0 then contraDeps = update(contraDeps, param) + else + traverse(entry(param)) case tp: LazyRef => if !seen.contains(tp) then seen += tp @@ -342,8 +350,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds, /** Adjust dependencies to account for the delta of previous entry `prevEntry` * and the new bound `entry` for the type parameter `srcParam`. */ - def adjustDeps(entry: Type | Null, prevEntry: Type | Null, srcParam: TypeParamRef)(using Context): this.type = - val adjuster = new Adjuster(srcParam) + def adjustDeps(entry: Type | Null, prevEntry: Type | Null, srcParam: TypeParamRef, ignoreBinding: TypeParamRef | Null = null)(using Context): this.type = + val adjuster = new Adjuster(srcParam, ignoreBinding) /** Adjust reverse dependencies of all type parameters referenced by `bound` * @param isLower `bound` is a lower bound @@ -676,7 +684,14 @@ class OrderingConstraint(private val boundsMap: ParamBounds, override def apply(t: Type): Type = if (t eq replacedTypeVar) && t.exists then to else mapOver(t) - var current = this + val coDepsOfParam = coDeps.at(param) + val contraDepsOfParam = contraDeps.at(param) + + var current = updateEntry(this, param, replacement) + // Need to update param early to avoid infinite recursion on instantiation. + // See i16311.scala for a test case. On the other hand, for the purposes of + // dependency adjustment, we need to pretend that `param` is still unbound. + // We achieve that by passing a `ignoreBinding = param` to `adjustDeps` below. def removeParamFrom(ps: List[TypeParamRef]) = ps.filterConserve(param ne _) @@ -710,22 +725,15 @@ class OrderingConstraint(private val boundsMap: ParamBounds, newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry) case _ => if oldDepEntry ne newDepEntry then - if current eq this then - // We can end up here if oldEntry eq newEntry, so posssibly no new constraint - // was created, but oldDepEntry ne newDepEntry. In that case we must make - // sure we have a new constraint before updating dependencies. - current = newConstraint() - current.adjustDeps(newDepEntry, oldDepEntry, other) + current.adjustDeps(newDepEntry, oldDepEntry, other, ignoreBinding = param) end replaceParamIn if optimizeReplace then - val co = current.coDeps.at(param) - val contra = current.contraDeps.at(param) current.foreachParam { (p, i) => val other = p.paramRefs(i) entry(other) match case _: TypeBounds => - if co.contains(other) || contra.contains(other) then + if coDepsOfParam.contains(other) || contraDepsOfParam.contains(other) then replaceParamIn(other) case _ => replaceParamIn(other) } @@ -734,10 +742,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, val other = p.paramRefs(i) if other != param then replaceParamIn(other) } - - current = - if isRemovable(param.binder) then current.remove(param.binder) - else updateEntry(current, param, replacement) + if isRemovable(param.binder) then current = current.remove(param.binder) current.dropDeps(param) current.checkWellFormed() end replace diff --git a/tests/pending/pos/i16311.scala b/tests/pos-deep-subtype/i16311.scala similarity index 100% rename from tests/pending/pos/i16311.scala rename to tests/pos-deep-subtype/i16311.scala diff --git a/tests/pos/i16311b.scala b/tests/pos/i16311b.scala new file mode 100644 index 000000000000..ea44431033b8 --- /dev/null +++ b/tests/pos/i16311b.scala @@ -0,0 +1,17 @@ +trait Tagged[U] +type WithTag[+T, U] = T & Tagged[U] + +trait FromInput[Val] +implicit def coercedScalaInput[T]: FromInput[T & Tagged[Int]] = ??? +implicit def optionInput[T](implicit ev: FromInput[T]): FromInput[Option[T]] = ??? + +trait WithoutInputTypeTags[T] +implicit def coercedOptArgTpe[T]: WithoutInputTypeTags[Option[T & Tagged[Int]]] = ??? + +trait InputType[+T] +class OptionInputType[T](ofType: InputType[T]) extends InputType[Option[T]] + +type Argument[T] +def argument[T](argumentType: InputType[T])(implicit fromInput: FromInput[T], res: WithoutInputTypeTags[T]): Argument[Option[T]] = ??? + +def test = argument(OptionInputType(??? : InputType[Boolean & Tagged[Int]]))