Skip to content

Commit 8601228

Browse files
authored
Revert lambda cleanup (#22697)
Reverts #21466, but keeps the other changes in that PR and in the abandoned attempt that is #22031. Fixes #21981, by virtue of reverting.
2 parents a97974c + 28c1ae3 commit 8601228

File tree

7 files changed

+236
-137
lines changed

7 files changed

+236
-137
lines changed

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
265265
private var coDeps: ReverseDeps = SimpleIdentityMap.empty
266266

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

@@ -370,7 +370,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
370370

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

399-
/** Add or remove depenencies referenced in `bounds`.
400-
* @param add if true, dependecies are added, otherwise they are removed
399+
/** Add or remove dependencies referenced in `bounds`.
400+
* @param add if true, dependencies are added, otherwise they are removed
401401
*/
402402
def adjustBounds(bounds: TypeBounds, add: Boolean) =
403403
adjustReferenced(bounds.lo, isLower = true, add)

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

+127-125
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,6 @@ trait Inferencing { this: Typer =>
671671
// This is needed because it could establish singleton type upper bounds. See i2998.scala.
672672

673673
val tp = tree.tpe.widen
674-
val vs = variances(tp, pt)
675674

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

698-
def constraint = state.constraint
699-
700-
trace(i"interpolateTypeVars($tree: ${tree.tpe}, $pt, $qualifying)", typr, (_: Any) => i"$qualifying\n$constraint\n${ctx.gadt}") {
701-
//println(i"$constraint")
702-
//println(i"${ctx.gadt}")
703-
704-
/** Values of this type report type variables to instantiate with variance indication:
705-
* +1 variable appears covariantly, can be instantiated from lower bound
706-
* -1 variable appears contravariantly, can be instantiated from upper bound
707-
* 0 variable does not appear at all, can be instantiated from either bound
708-
*/
709-
type ToInstantiate = List[(TypeVar, Int)]
710-
711-
val toInstantiate: ToInstantiate =
712-
val buf = new mutable.ListBuffer[(TypeVar, Int)]
713-
for tvar <- qualifying do
714-
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
715-
constrainIfDependentParamRef(tvar, tree)
716-
if !tvar.isInstantiated then
717-
// isInstantiated needs to be checked again, since previous interpolations could already have
718-
// instantiated `tvar` through unification.
719-
val v = vs.computedVariance(tvar)
720-
if v == null then buf += ((tvar, 0))
721-
else if v.intValue != 0 then buf += ((tvar, v.intValue))
722-
else comparing(cmp =>
723-
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
724-
// Invariant: The type of a tree whose enclosing scope is level
725-
// N only contains type variables of level <= N.
726-
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
727-
cmp.atLevel(ctx.nestingLevel, tvar.origin)
728-
else
729-
typr.println(i"no interpolation for nonvariant $tvar in $state")
730-
)
731-
// constrainIfDependentParamRef could also have instantiated tvars added to buf before the check
732-
buf.filterNot(_._1.isInstantiated).toList
733-
end toInstantiate
734-
735-
def typeVarsIn(xs: ToInstantiate): TypeVars =
736-
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
737-
738-
/** Filter list of proposed instantiations so that they don't constrain further
739-
* the current constraint.
740-
*/
741-
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
742-
val excluded = // ignore dependencies from other variables that are being instantiated
743-
typeVarsIn(tvs0)
744-
def step(tvs: ToInstantiate): ToInstantiate = tvs match
745-
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
746-
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
747-
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
748-
if v == 0 && !aboveOK then
749-
step((tvar, 1) :: tvs1)
750-
else if v == 0 && !belowOK then
751-
step((tvar, -1) :: tvs1)
752-
else if v == -1 && !aboveOK || v == 1 && !belowOK then
753-
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
754-
step(tvs1)
755-
else // no conflict, keep the instantiation proposal
756-
tvs.derivedCons(hd, step(tvs1))
757-
case Nil =>
758-
Nil
759-
val tvs1 = step(tvs0)
760-
if tvs1 eq tvs0 then tvs1
761-
else filterByDeps(tvs1) // filter again with smaller excluded set
762-
end filterByDeps
763-
764-
/** Instantiate all type variables in `tvs` in the indicated directions,
765-
* as described in the doc comment of `ToInstantiate`.
766-
* If a type variable A is instantiated from below, and there is another
767-
* type variable B in `buf` that is known to be smaller than A, wait and
768-
* instantiate all other type variables before trying to instantiate A again.
769-
* Dually, wait instantiating a type variable from above as long as it has
770-
* upper bounds in `buf`.
771-
*
772-
* This is done to avoid loss of precision when forming unions. An example
773-
* is in i7558.scala:
774-
*
775-
* type Tr[+V1, +O1 <: V1]
776-
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
777-
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
778-
*
779-
* Here we interpolate at some point V2 and O2 given the constraint
780-
*
781-
* V2 >: V3, O2 >: O3, O2 <: V2
782-
*
783-
* where O3 and V3 are type refs with O3 <: V3.
784-
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
785-
* instantiate O2 to V3, leading to the final constraint
786-
*
787-
* V2 := V3, O2 := V3
788-
*
789-
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
790-
* more flexible instantiation
791-
*
792-
* V2 := V3, O2 := O3
793-
*/
794-
def doInstantiate(tvs: ToInstantiate): Unit =
795-
796-
/** Try to instantiate `tvs`, return any suspended type variables */
797-
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
798-
case (hd @ (tvar, v)) :: tvs1 =>
799-
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
800-
typr.println(
801-
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
802-
if tvar.isInstantiated then
803-
tryInstantiate(tvs1)
804-
else
805-
val suspend = tvs1.exists{ (following, _) =>
806-
if fromBelow
807-
then constraint.isLess(following.origin, tvar.origin)
808-
else constraint.isLess(tvar.origin, following.origin)
809-
}
810-
if suspend then
811-
typr.println(i"suspended: $hd")
812-
hd :: tryInstantiate(tvs1)
813-
else
814-
tvar.instantiate(fromBelow)
815-
tryInstantiate(tvs1)
816-
case Nil => Nil
817-
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
818-
end doInstantiate
819-
820-
doInstantiate(filterByDeps(toInstantiate))
821-
}
697+
instantiateTypeVars(tp, pt, qualifying, tree)
822698
}
823699
end if
824700
tree
825701
end interpolateTypeVars
826702

703+
def instantiateTypeVars(tp: Type, pt: Type, qualifying: List[TypeVar], tree: Tree = EmptyTree)(using Context): Unit =
704+
trace(i"instantiateTypeVars($tp, $pt, $qualifying, $tree)", typr):
705+
val state = ctx.typerState
706+
def constraint = state.constraint
707+
708+
val vs = variances(tp, pt)
709+
710+
/** Values of this type report type variables to instantiate with variance indication:
711+
* +1 variable appears covariantly, can be instantiated from lower bound
712+
* -1 variable appears contravariantly, can be instantiated from upper bound
713+
* 0 variable does not appear at all, can be instantiated from either bound
714+
*/
715+
type ToInstantiate = List[(TypeVar, Int)]
716+
717+
val toInstantiate: ToInstantiate =
718+
val buf = new mutable.ListBuffer[(TypeVar, Int)]
719+
for tvar <- qualifying do
720+
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
721+
constrainIfDependentParamRef(tvar, tree)
722+
if !tvar.isInstantiated then
723+
// isInstantiated needs to be checked again, since previous interpolations could already have
724+
// instantiated `tvar` through unification.
725+
val v = vs.computedVariance(tvar)
726+
if v == null then buf += ((tvar, 0))
727+
else if v.intValue != 0 then buf += ((tvar, v.intValue))
728+
else comparing(cmp =>
729+
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
730+
// Invariant: The type of a tree whose enclosing scope is level
731+
// N only contains type variables of level <= N.
732+
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
733+
cmp.atLevel(ctx.nestingLevel, tvar.origin)
734+
else
735+
typr.println(i"no interpolation for nonvariant $tvar in $state")
736+
)
737+
// constrainIfDependentParamRef could also have instantiated tvars added to buf before the check
738+
buf.filterNot(_._1.isInstantiated).toList
739+
end toInstantiate
740+
741+
def typeVarsIn(xs: ToInstantiate): TypeVars =
742+
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
743+
744+
/** Filter list of proposed instantiations so that they don't constrain further
745+
* the current constraint.
746+
*/
747+
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
748+
val excluded = // ignore dependencies from other variables that are being instantiated
749+
typeVarsIn(tvs0)
750+
def step(tvs: ToInstantiate): ToInstantiate = tvs match
751+
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
752+
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
753+
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
754+
if v == 0 && !aboveOK then
755+
step((tvar, 1) :: tvs1)
756+
else if v == 0 && !belowOK then
757+
step((tvar, -1) :: tvs1)
758+
else if v == -1 && !aboveOK || v == 1 && !belowOK then
759+
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
760+
step(tvs1)
761+
else // no conflict, keep the instantiation proposal
762+
tvs.derivedCons(hd, step(tvs1))
763+
case Nil =>
764+
Nil
765+
val tvs1 = step(tvs0)
766+
if tvs1 eq tvs0 then tvs1
767+
else filterByDeps(tvs1) // filter again with smaller excluded set
768+
end filterByDeps
769+
770+
/** Instantiate all type variables in `tvs` in the indicated directions,
771+
* as described in the doc comment of `ToInstantiate`.
772+
* If a type variable A is instantiated from below, and there is another
773+
* type variable B in `buf` that is known to be smaller than A, wait and
774+
* instantiate all other type variables before trying to instantiate A again.
775+
* Dually, wait instantiating a type variable from above as long as it has
776+
* upper bounds in `buf`.
777+
*
778+
* This is done to avoid loss of precision when forming unions. An example
779+
* is in i7558.scala:
780+
*
781+
* type Tr[+V1, +O1 <: V1]
782+
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
783+
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
784+
*
785+
* Here we interpolate at some point V2 and O2 given the constraint
786+
*
787+
* V2 >: V3, O2 >: O3, O2 <: V2
788+
*
789+
* where O3 and V3 are type refs with O3 <: V3.
790+
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
791+
* instantiate O2 to V3, leading to the final constraint
792+
*
793+
* V2 := V3, O2 := V3
794+
*
795+
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
796+
* more flexible instantiation
797+
*
798+
* V2 := V3, O2 := O3
799+
*/
800+
def doInstantiate(tvs: ToInstantiate): Unit =
801+
802+
/** Try to instantiate `tvs`, return any suspended type variables */
803+
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
804+
case (hd @ (tvar, v)) :: tvs1 =>
805+
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
806+
typr.println(
807+
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
808+
if tvar.isInstantiated then
809+
tryInstantiate(tvs1)
810+
else
811+
val suspend = tvs1.exists{ (following, _) =>
812+
if fromBelow
813+
then constraint.isLess(following.origin, tvar.origin)
814+
else constraint.isLess(tvar.origin, following.origin)
815+
}
816+
if suspend then
817+
typr.println(i"suspended: $hd")
818+
hd :: tryInstantiate(tvs1)
819+
else
820+
tvar.instantiate(fromBelow)
821+
tryInstantiate(tvs1)
822+
case Nil => Nil
823+
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
824+
end doInstantiate
825+
826+
doInstantiate(filterByDeps(toInstantiate))
827+
end instantiateTypeVars
828+
827829
/** If `tvar` represents a parameter of a dependent method type in the current `call`
828830
* approximate it from below with the type of the actual argument. Skolemize that
829831
* type if necessary to make it a Singleton.

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

-7
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,6 @@ object ProtoTypes {
7171
|constraint was: ${ctx.typerState.constraint}
7272
|constraint now: ${newctx.typerState.constraint}""")
7373
if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then
74-
// Remove all type lambdas and tvars introduced by testCompat
75-
for tvar <- newctx.typerState.ownedVars do
76-
inContext(newctx):
77-
if !tvar.isInstantiated then
78-
tvar.instantiate(fromBelow = false) // any direction
79-
80-
// commit any remaining changes in typer state
8174
newctx.typerState.commit()
8275
result
8376
case _ => testCompat

tests/pos/i21981.alt.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
trait Ops[F[_], A]:
2+
def map0[B](f0: A => B): F[B] = ???
3+
4+
trait Functor1[G[_]]
5+
6+
trait Functor2[H[_]]
7+
8+
trait Ref[I[_], +E]
9+
10+
class Test:
11+
given [J[_]](using J: Functor1[J]): Functor2[J] with
12+
extension [K1, K2](jk: J[(K1, K2)])
13+
def map2[L](f2: (K1, K2) => L): J[L] = ???
14+
15+
def t1[
16+
M[_[t]],
17+
N[_],
18+
](using N: Functor1[N]): Unit =
19+
20+
val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ???
21+
22+
val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3
23+
.map0 { refs => (???, refs) }
24+
.map2 { case (not, refs) => (???, refs) }

tests/pos/i21981.contrak.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
case class Inv[T](x: T)
2+
class Contra[-ContraParam](x: ContraParam)
3+
4+
trait Ops[F[_], A]:
5+
def map0[B](f0: A => Contra[B]): F[B] = ???
6+
7+
trait Functor1[G[_]]
8+
9+
trait Functor2[H[_]]
10+
11+
trait Ref[I[_], +E]
12+
13+
class Test:
14+
given [J[_]](using J: Functor1[J]): Functor2[J] with
15+
extension [K](jk: J[Contra[K]])
16+
def map2[L](f2: K => L): J[L] = ???
17+
18+
def t1[
19+
M[_[t]],
20+
N[_],
21+
](using N: Functor1[N]): Unit =
22+
23+
val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ???
24+
25+
val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3
26+
.map0 { refs => Contra[Contra[(Nothing, M[[t] =>> Ref[N, t]])]](???) }
27+
.map2 { case (not, refs) => (???, refs) }

tests/pos/i21981.orig.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
object internal:
2+
trait Functor[F[_]] {
3+
extension [T](ft: F[T]) def map[T1](f: T => T1): F[T1]
4+
}
5+
6+
object cats:
7+
trait Functor[F[_]]
8+
object Functor:
9+
trait Ops[F[_], A]:
10+
def map[B](f: A => B): F[B] = ???
11+
def toAllFunctorOps[F[_], A](target: F[A])(using Functor[F]): Ops[F, A] = ???
12+
13+
given [F[_]](using cf: cats.Functor[F]): internal.Functor[F] with {
14+
extension [T](ft: F[T]) def map[T1](f: T => T1): F[T1] = ???
15+
}
16+
17+
trait Ref[F[_], +T]
18+
class MemoizingEvaluator[Input[_[_]], Output[_[_]], F[_]: cats.Functor] {
19+
type OptionRef[T] = Ref[F, Option[T]]
20+
21+
def sequence[CaseClass[_[_]], G[_], H[_]](instance: CaseClass[[t] =>> G[H[t]]]): G[CaseClass[H]] = ???
22+
def collectValues(input: Input[F]): F[(Input[F], Input[OptionRef])] = {
23+
val refsF: Input[[t] =>> F[OptionRef[t]]] = ???
24+
for {
25+
refs <- cats.Functor.toAllFunctorOps(sequence[Input, F, OptionRef](refsF))
26+
updating = ???
27+
} yield (updating, refs)
28+
}
29+
}

0 commit comments

Comments
 (0)