Skip to content

Commit

Permalink
Revert lambda cleanup (#22697)
Browse files Browse the repository at this point in the history
Reverts #21466, but keeps the other changes in that PR and in the
abandoned attempt that is #22031.

Fixes #21981, by virtue of reverting.
  • Loading branch information
smarter authored Mar 4, 2025
2 parents a97974c + 28c1ae3 commit 8601228
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 137 deletions.
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
private var coDeps: ReverseDeps = SimpleIdentityMap.empty

/** A map that associates type parameters of this constraint with all other type
* parameters that refer to them in their bounds covariantly, such that, if the
* parameters that refer to them in their bounds contravariantly, such that, if the
* type parameter is instantiated to a smaller type, the constraint would be narrowed.
* (i.e. solution set changes other than simply being made larger).
* (i.e. solution set changes other than simply being made smaller).
*/
private var contraDeps: ReverseDeps = SimpleIdentityMap.empty

Expand Down Expand Up @@ -370,7 +370,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,

/** Adjust reverse dependencies of all type parameters referenced by `bound`
* @param isLower `bound` is a lower bound
* @param add if true, add referenced variables to dependencoes, otherwise drop them.
* @param add if true, add referenced variables to dependencies, otherwise drop them.
*/
def adjustReferenced(bound: Type, isLower: Boolean, add: Boolean) =
adjuster.variance = if isLower then 1 else -1
Expand All @@ -396,8 +396,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
}
case _ => false

/** Add or remove depenencies referenced in `bounds`.
* @param add if true, dependecies are added, otherwise they are removed
/** Add or remove dependencies referenced in `bounds`.
* @param add if true, dependencies are added, otherwise they are removed
*/
def adjustBounds(bounds: TypeBounds, add: Boolean) =
adjustReferenced(bounds.lo, isLower = true, add)
Expand Down
252 changes: 127 additions & 125 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,6 @@ trait Inferencing { this: Typer =>
// This is needed because it could establish singleton type upper bounds. See i2998.scala.

val tp = tree.tpe.widen
val vs = variances(tp, pt)

// Avoid interpolating variables occurring in tree's type if typerstate has unreported errors.
// Reason: The errors might reflect unsatisfiable constraints. In that
Expand All @@ -695,135 +694,138 @@ trait Inferencing { this: Typer =>
// val y: List[List[String]] = List(List(1))
if state.reporter.hasUnreportedErrors then return tree

def constraint = state.constraint

trace(i"interpolateTypeVars($tree: ${tree.tpe}, $pt, $qualifying)", typr, (_: Any) => i"$qualifying\n$constraint\n${ctx.gadt}") {
//println(i"$constraint")
//println(i"${ctx.gadt}")

/** Values of this type report type variables to instantiate with variance indication:
* +1 variable appears covariantly, can be instantiated from lower bound
* -1 variable appears contravariantly, can be instantiated from upper bound
* 0 variable does not appear at all, can be instantiated from either bound
*/
type ToInstantiate = List[(TypeVar, Int)]

val toInstantiate: ToInstantiate =
val buf = new mutable.ListBuffer[(TypeVar, Int)]
for tvar <- qualifying do
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
constrainIfDependentParamRef(tvar, tree)
if !tvar.isInstantiated then
// isInstantiated needs to be checked again, since previous interpolations could already have
// instantiated `tvar` through unification.
val v = vs.computedVariance(tvar)
if v == null then buf += ((tvar, 0))
else if v.intValue != 0 then buf += ((tvar, v.intValue))
else comparing(cmp =>
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
// Invariant: The type of a tree whose enclosing scope is level
// N only contains type variables of level <= N.
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
cmp.atLevel(ctx.nestingLevel, tvar.origin)
else
typr.println(i"no interpolation for nonvariant $tvar in $state")
)
// constrainIfDependentParamRef could also have instantiated tvars added to buf before the check
buf.filterNot(_._1.isInstantiated).toList
end toInstantiate

def typeVarsIn(xs: ToInstantiate): TypeVars =
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)

/** Filter list of proposed instantiations so that they don't constrain further
* the current constraint.
*/
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
val excluded = // ignore dependencies from other variables that are being instantiated
typeVarsIn(tvs0)
def step(tvs: ToInstantiate): ToInstantiate = tvs match
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
if v == 0 && !aboveOK then
step((tvar, 1) :: tvs1)
else if v == 0 && !belowOK then
step((tvar, -1) :: tvs1)
else if v == -1 && !aboveOK || v == 1 && !belowOK then
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
step(tvs1)
else // no conflict, keep the instantiation proposal
tvs.derivedCons(hd, step(tvs1))
case Nil =>
Nil
val tvs1 = step(tvs0)
if tvs1 eq tvs0 then tvs1
else filterByDeps(tvs1) // filter again with smaller excluded set
end filterByDeps

/** Instantiate all type variables in `tvs` in the indicated directions,
* as described in the doc comment of `ToInstantiate`.
* If a type variable A is instantiated from below, and there is another
* type variable B in `buf` that is known to be smaller than A, wait and
* instantiate all other type variables before trying to instantiate A again.
* Dually, wait instantiating a type variable from above as long as it has
* upper bounds in `buf`.
*
* This is done to avoid loss of precision when forming unions. An example
* is in i7558.scala:
*
* type Tr[+V1, +O1 <: V1]
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
*
* Here we interpolate at some point V2 and O2 given the constraint
*
* V2 >: V3, O2 >: O3, O2 <: V2
*
* where O3 and V3 are type refs with O3 <: V3.
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
* instantiate O2 to V3, leading to the final constraint
*
* V2 := V3, O2 := V3
*
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
* more flexible instantiation
*
* V2 := V3, O2 := O3
*/
def doInstantiate(tvs: ToInstantiate): Unit =

/** Try to instantiate `tvs`, return any suspended type variables */
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
case (hd @ (tvar, v)) :: tvs1 =>
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
typr.println(
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
if tvar.isInstantiated then
tryInstantiate(tvs1)
else
val suspend = tvs1.exists{ (following, _) =>
if fromBelow
then constraint.isLess(following.origin, tvar.origin)
else constraint.isLess(tvar.origin, following.origin)
}
if suspend then
typr.println(i"suspended: $hd")
hd :: tryInstantiate(tvs1)
else
tvar.instantiate(fromBelow)
tryInstantiate(tvs1)
case Nil => Nil
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
end doInstantiate

doInstantiate(filterByDeps(toInstantiate))
}
instantiateTypeVars(tp, pt, qualifying, tree)
}
end if
tree
end interpolateTypeVars

def instantiateTypeVars(tp: Type, pt: Type, qualifying: List[TypeVar], tree: Tree = EmptyTree)(using Context): Unit =
trace(i"instantiateTypeVars($tp, $pt, $qualifying, $tree)", typr):
val state = ctx.typerState
def constraint = state.constraint

val vs = variances(tp, pt)

/** Values of this type report type variables to instantiate with variance indication:
* +1 variable appears covariantly, can be instantiated from lower bound
* -1 variable appears contravariantly, can be instantiated from upper bound
* 0 variable does not appear at all, can be instantiated from either bound
*/
type ToInstantiate = List[(TypeVar, Int)]

val toInstantiate: ToInstantiate =
val buf = new mutable.ListBuffer[(TypeVar, Int)]
for tvar <- qualifying do
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
constrainIfDependentParamRef(tvar, tree)
if !tvar.isInstantiated then
// isInstantiated needs to be checked again, since previous interpolations could already have
// instantiated `tvar` through unification.
val v = vs.computedVariance(tvar)
if v == null then buf += ((tvar, 0))
else if v.intValue != 0 then buf += ((tvar, v.intValue))
else comparing(cmp =>
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
// Invariant: The type of a tree whose enclosing scope is level
// N only contains type variables of level <= N.
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
cmp.atLevel(ctx.nestingLevel, tvar.origin)
else
typr.println(i"no interpolation for nonvariant $tvar in $state")
)
// constrainIfDependentParamRef could also have instantiated tvars added to buf before the check
buf.filterNot(_._1.isInstantiated).toList
end toInstantiate

def typeVarsIn(xs: ToInstantiate): TypeVars =
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)

/** Filter list of proposed instantiations so that they don't constrain further
* the current constraint.
*/
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
val excluded = // ignore dependencies from other variables that are being instantiated
typeVarsIn(tvs0)
def step(tvs: ToInstantiate): ToInstantiate = tvs match
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
if v == 0 && !aboveOK then
step((tvar, 1) :: tvs1)
else if v == 0 && !belowOK then
step((tvar, -1) :: tvs1)
else if v == -1 && !aboveOK || v == 1 && !belowOK then
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
step(tvs1)
else // no conflict, keep the instantiation proposal
tvs.derivedCons(hd, step(tvs1))
case Nil =>
Nil
val tvs1 = step(tvs0)
if tvs1 eq tvs0 then tvs1
else filterByDeps(tvs1) // filter again with smaller excluded set
end filterByDeps

/** Instantiate all type variables in `tvs` in the indicated directions,
* as described in the doc comment of `ToInstantiate`.
* If a type variable A is instantiated from below, and there is another
* type variable B in `buf` that is known to be smaller than A, wait and
* instantiate all other type variables before trying to instantiate A again.
* Dually, wait instantiating a type variable from above as long as it has
* upper bounds in `buf`.
*
* This is done to avoid loss of precision when forming unions. An example
* is in i7558.scala:
*
* type Tr[+V1, +O1 <: V1]
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
*
* Here we interpolate at some point V2 and O2 given the constraint
*
* V2 >: V3, O2 >: O3, O2 <: V2
*
* where O3 and V3 are type refs with O3 <: V3.
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
* instantiate O2 to V3, leading to the final constraint
*
* V2 := V3, O2 := V3
*
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
* more flexible instantiation
*
* V2 := V3, O2 := O3
*/
def doInstantiate(tvs: ToInstantiate): Unit =

/** Try to instantiate `tvs`, return any suspended type variables */
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
case (hd @ (tvar, v)) :: tvs1 =>
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
typr.println(
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
if tvar.isInstantiated then
tryInstantiate(tvs1)
else
val suspend = tvs1.exists{ (following, _) =>
if fromBelow
then constraint.isLess(following.origin, tvar.origin)
else constraint.isLess(tvar.origin, following.origin)
}
if suspend then
typr.println(i"suspended: $hd")
hd :: tryInstantiate(tvs1)
else
tvar.instantiate(fromBelow)
tryInstantiate(tvs1)
case Nil => Nil
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
end doInstantiate

doInstantiate(filterByDeps(toInstantiate))
end instantiateTypeVars

/** If `tvar` represents a parameter of a dependent method type in the current `call`
* approximate it from below with the type of the actual argument. Skolemize that
* type if necessary to make it a Singleton.
Expand Down
7 changes: 0 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,6 @@ object ProtoTypes {
|constraint was: ${ctx.typerState.constraint}
|constraint now: ${newctx.typerState.constraint}""")
if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then
// Remove all type lambdas and tvars introduced by testCompat
for tvar <- newctx.typerState.ownedVars do
inContext(newctx):
if !tvar.isInstantiated then
tvar.instantiate(fromBelow = false) // any direction

// commit any remaining changes in typer state
newctx.typerState.commit()
result
case _ => testCompat
Expand Down
24 changes: 24 additions & 0 deletions tests/pos/i21981.alt.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
trait Ops[F[_], A]:
def map0[B](f0: A => B): F[B] = ???

trait Functor1[G[_]]

trait Functor2[H[_]]

trait Ref[I[_], +E]

class Test:
given [J[_]](using J: Functor1[J]): Functor2[J] with
extension [K1, K2](jk: J[(K1, K2)])
def map2[L](f2: (K1, K2) => L): J[L] = ???

def t1[
M[_[t]],
N[_],
](using N: Functor1[N]): Unit =

val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ???

val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3
.map0 { refs => (???, refs) }
.map2 { case (not, refs) => (???, refs) }
27 changes: 27 additions & 0 deletions tests/pos/i21981.contrak.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
case class Inv[T](x: T)
class Contra[-ContraParam](x: ContraParam)

trait Ops[F[_], A]:
def map0[B](f0: A => Contra[B]): F[B] = ???

trait Functor1[G[_]]

trait Functor2[H[_]]

trait Ref[I[_], +E]

class Test:
given [J[_]](using J: Functor1[J]): Functor2[J] with
extension [K](jk: J[Contra[K]])
def map2[L](f2: K => L): J[L] = ???

def t1[
M[_[t]],
N[_],
](using N: Functor1[N]): Unit =

val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ???

val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3
.map0 { refs => Contra[Contra[(Nothing, M[[t] =>> Ref[N, t]])]](???) }
.map2 { case (not, refs) => (???, refs) }
29 changes: 29 additions & 0 deletions tests/pos/i21981.orig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
object internal:
trait Functor[F[_]] {
extension [T](ft: F[T]) def map[T1](f: T => T1): F[T1]
}

object cats:
trait Functor[F[_]]
object Functor:
trait Ops[F[_], A]:
def map[B](f: A => B): F[B] = ???
def toAllFunctorOps[F[_], A](target: F[A])(using Functor[F]): Ops[F, A] = ???

given [F[_]](using cf: cats.Functor[F]): internal.Functor[F] with {
extension [T](ft: F[T]) def map[T1](f: T => T1): F[T1] = ???
}

trait Ref[F[_], +T]
class MemoizingEvaluator[Input[_[_]], Output[_[_]], F[_]: cats.Functor] {
type OptionRef[T] = Ref[F, Option[T]]

def sequence[CaseClass[_[_]], G[_], H[_]](instance: CaseClass[[t] =>> G[H[t]]]): G[CaseClass[H]] = ???
def collectValues(input: Input[F]): F[(Input[F], Input[OptionRef])] = {
val refsF: Input[[t] =>> F[OptionRef[t]]] = ???
for {
refs <- cats.Functor.toAllFunctorOps(sequence[Input, F, OptionRef](refsF))
updating = ???
} yield (updating, refs)
}
}
Loading

0 comments on commit 8601228

Please sign in to comment.