Skip to content

Backport "Revert lambda cleanup" to 3.3 LTS #279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -263,9 +263,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 @@ -368,7 +368,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 @@ -394,8 +394,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 @@ -604,7 +604,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 @@ -628,135 +627,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 @@ -69,13 +69,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