Skip to content

Commit 87eff62

Browse files
dwijnandtgodzik
authored andcommitted
Revert lambda cleanup (but keep remaining changes)
Reverts PR 21466, but keeps the other changes in that PR and in the abandoned attempt that is PR 22031. Fixes issue 21981, by virtue of rebasing. [Cherry-picked 28c1ae3]
1 parent 8e6a37d commit 87eff62

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
@@ -263,9 +263,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
263263
private var coDeps: ReverseDeps = SimpleIdentityMap.empty
264264

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

@@ -368,7 +368,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
368368

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

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

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

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

606606
val tp = tree.tpe.widen
607-
val vs = variances(tp, pt)
608607

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

631-
def constraint = state.constraint
632-
633-
trace(i"interpolateTypeVars($tree: ${tree.tpe}, $pt, $qualifying)", typr, (_: Any) => i"$qualifying\n$constraint\n${ctx.gadt}") {
634-
//println(i"$constraint")
635-
//println(i"${ctx.gadt}")
636-
637-
/** Values of this type report type variables to instantiate with variance indication:
638-
* +1 variable appears covariantly, can be instantiated from lower bound
639-
* -1 variable appears contravariantly, can be instantiated from upper bound
640-
* 0 variable does not appear at all, can be instantiated from either bound
641-
*/
642-
type ToInstantiate = List[(TypeVar, Int)]
643-
644-
val toInstantiate: ToInstantiate =
645-
val buf = new mutable.ListBuffer[(TypeVar, Int)]
646-
for tvar <- qualifying do
647-
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
648-
constrainIfDependentParamRef(tvar, tree)
649-
if !tvar.isInstantiated then
650-
// isInstantiated needs to be checked again, since previous interpolations could already have
651-
// instantiated `tvar` through unification.
652-
val v = vs.computedVariance(tvar)
653-
if v == null then buf += ((tvar, 0))
654-
else if v.intValue != 0 then buf += ((tvar, v.intValue))
655-
else comparing(cmp =>
656-
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
657-
// Invariant: The type of a tree whose enclosing scope is level
658-
// N only contains type variables of level <= N.
659-
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
660-
cmp.atLevel(ctx.nestingLevel, tvar.origin)
661-
else
662-
typr.println(i"no interpolation for nonvariant $tvar in $state")
663-
)
664-
// constrainIfDependentParamRef could also have instantiated tvars added to buf before the check
665-
buf.filterNot(_._1.isInstantiated).toList
666-
end toInstantiate
667-
668-
def typeVarsIn(xs: ToInstantiate): TypeVars =
669-
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
670-
671-
/** Filter list of proposed instantiations so that they don't constrain further
672-
* the current constraint.
673-
*/
674-
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
675-
val excluded = // ignore dependencies from other variables that are being instantiated
676-
typeVarsIn(tvs0)
677-
def step(tvs: ToInstantiate): ToInstantiate = tvs match
678-
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
679-
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
680-
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
681-
if v == 0 && !aboveOK then
682-
step((tvar, 1) :: tvs1)
683-
else if v == 0 && !belowOK then
684-
step((tvar, -1) :: tvs1)
685-
else if v == -1 && !aboveOK || v == 1 && !belowOK then
686-
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
687-
step(tvs1)
688-
else // no conflict, keep the instantiation proposal
689-
tvs.derivedCons(hd, step(tvs1))
690-
case Nil =>
691-
Nil
692-
val tvs1 = step(tvs0)
693-
if tvs1 eq tvs0 then tvs1
694-
else filterByDeps(tvs1) // filter again with smaller excluded set
695-
end filterByDeps
696-
697-
/** Instantiate all type variables in `tvs` in the indicated directions,
698-
* as described in the doc comment of `ToInstantiate`.
699-
* If a type variable A is instantiated from below, and there is another
700-
* type variable B in `buf` that is known to be smaller than A, wait and
701-
* instantiate all other type variables before trying to instantiate A again.
702-
* Dually, wait instantiating a type variable from above as long as it has
703-
* upper bounds in `buf`.
704-
*
705-
* This is done to avoid loss of precision when forming unions. An example
706-
* is in i7558.scala:
707-
*
708-
* type Tr[+V1, +O1 <: V1]
709-
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
710-
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
711-
*
712-
* Here we interpolate at some point V2 and O2 given the constraint
713-
*
714-
* V2 >: V3, O2 >: O3, O2 <: V2
715-
*
716-
* where O3 and V3 are type refs with O3 <: V3.
717-
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
718-
* instantiate O2 to V3, leading to the final constraint
719-
*
720-
* V2 := V3, O2 := V3
721-
*
722-
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
723-
* more flexible instantiation
724-
*
725-
* V2 := V3, O2 := O3
726-
*/
727-
def doInstantiate(tvs: ToInstantiate): Unit =
728-
729-
/** Try to instantiate `tvs`, return any suspended type variables */
730-
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
731-
case (hd @ (tvar, v)) :: tvs1 =>
732-
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
733-
typr.println(
734-
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
735-
if tvar.isInstantiated then
736-
tryInstantiate(tvs1)
737-
else
738-
val suspend = tvs1.exists{ (following, _) =>
739-
if fromBelow
740-
then constraint.isLess(following.origin, tvar.origin)
741-
else constraint.isLess(tvar.origin, following.origin)
742-
}
743-
if suspend then
744-
typr.println(i"suspended: $hd")
745-
hd :: tryInstantiate(tvs1)
746-
else
747-
tvar.instantiate(fromBelow)
748-
tryInstantiate(tvs1)
749-
case Nil => Nil
750-
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
751-
end doInstantiate
752-
753-
doInstantiate(filterByDeps(toInstantiate))
754-
}
630+
instantiateTypeVars(tp, pt, qualifying, tree)
755631
}
756632
end if
757633
tree
758634
end interpolateTypeVars
759635

636+
def instantiateTypeVars(tp: Type, pt: Type, qualifying: List[TypeVar], tree: Tree = EmptyTree)(using Context): Unit =
637+
trace(i"instantiateTypeVars($tp, $pt, $qualifying, $tree)", typr):
638+
val state = ctx.typerState
639+
def constraint = state.constraint
640+
641+
val vs = variances(tp, pt)
642+
643+
/** Values of this type report type variables to instantiate with variance indication:
644+
* +1 variable appears covariantly, can be instantiated from lower bound
645+
* -1 variable appears contravariantly, can be instantiated from upper bound
646+
* 0 variable does not appear at all, can be instantiated from either bound
647+
*/
648+
type ToInstantiate = List[(TypeVar, Int)]
649+
650+
val toInstantiate: ToInstantiate =
651+
val buf = new mutable.ListBuffer[(TypeVar, Int)]
652+
for tvar <- qualifying do
653+
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
654+
constrainIfDependentParamRef(tvar, tree)
655+
if !tvar.isInstantiated then
656+
// isInstantiated needs to be checked again, since previous interpolations could already have
657+
// instantiated `tvar` through unification.
658+
val v = vs.computedVariance(tvar)
659+
if v == null then buf += ((tvar, 0))
660+
else if v.intValue != 0 then buf += ((tvar, v.intValue))
661+
else comparing(cmp =>
662+
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
663+
// Invariant: The type of a tree whose enclosing scope is level
664+
// N only contains type variables of level <= N.
665+
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
666+
cmp.atLevel(ctx.nestingLevel, tvar.origin)
667+
else
668+
typr.println(i"no interpolation for nonvariant $tvar in $state")
669+
)
670+
// constrainIfDependentParamRef could also have instantiated tvars added to buf before the check
671+
buf.filterNot(_._1.isInstantiated).toList
672+
end toInstantiate
673+
674+
def typeVarsIn(xs: ToInstantiate): TypeVars =
675+
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
676+
677+
/** Filter list of proposed instantiations so that they don't constrain further
678+
* the current constraint.
679+
*/
680+
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
681+
val excluded = // ignore dependencies from other variables that are being instantiated
682+
typeVarsIn(tvs0)
683+
def step(tvs: ToInstantiate): ToInstantiate = tvs match
684+
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
685+
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
686+
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
687+
if v == 0 && !aboveOK then
688+
step((tvar, 1) :: tvs1)
689+
else if v == 0 && !belowOK then
690+
step((tvar, -1) :: tvs1)
691+
else if v == -1 && !aboveOK || v == 1 && !belowOK then
692+
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
693+
step(tvs1)
694+
else // no conflict, keep the instantiation proposal
695+
tvs.derivedCons(hd, step(tvs1))
696+
case Nil =>
697+
Nil
698+
val tvs1 = step(tvs0)
699+
if tvs1 eq tvs0 then tvs1
700+
else filterByDeps(tvs1) // filter again with smaller excluded set
701+
end filterByDeps
702+
703+
/** Instantiate all type variables in `tvs` in the indicated directions,
704+
* as described in the doc comment of `ToInstantiate`.
705+
* If a type variable A is instantiated from below, and there is another
706+
* type variable B in `buf` that is known to be smaller than A, wait and
707+
* instantiate all other type variables before trying to instantiate A again.
708+
* Dually, wait instantiating a type variable from above as long as it has
709+
* upper bounds in `buf`.
710+
*
711+
* This is done to avoid loss of precision when forming unions. An example
712+
* is in i7558.scala:
713+
*
714+
* type Tr[+V1, +O1 <: V1]
715+
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
716+
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
717+
*
718+
* Here we interpolate at some point V2 and O2 given the constraint
719+
*
720+
* V2 >: V3, O2 >: O3, O2 <: V2
721+
*
722+
* where O3 and V3 are type refs with O3 <: V3.
723+
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
724+
* instantiate O2 to V3, leading to the final constraint
725+
*
726+
* V2 := V3, O2 := V3
727+
*
728+
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
729+
* more flexible instantiation
730+
*
731+
* V2 := V3, O2 := O3
732+
*/
733+
def doInstantiate(tvs: ToInstantiate): Unit =
734+
735+
/** Try to instantiate `tvs`, return any suspended type variables */
736+
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
737+
case (hd @ (tvar, v)) :: tvs1 =>
738+
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
739+
typr.println(
740+
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
741+
if tvar.isInstantiated then
742+
tryInstantiate(tvs1)
743+
else
744+
val suspend = tvs1.exists{ (following, _) =>
745+
if fromBelow
746+
then constraint.isLess(following.origin, tvar.origin)
747+
else constraint.isLess(tvar.origin, following.origin)
748+
}
749+
if suspend then
750+
typr.println(i"suspended: $hd")
751+
hd :: tryInstantiate(tvs1)
752+
else
753+
tvar.instantiate(fromBelow)
754+
tryInstantiate(tvs1)
755+
case Nil => Nil
756+
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
757+
end doInstantiate
758+
759+
doInstantiate(filterByDeps(toInstantiate))
760+
end instantiateTypeVars
761+
760762
/** If `tvar` represents a parameter of a dependent method type in the current `call`
761763
* approximate it from below with the type of the actual argument. Skolemize that
762764
* 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
@@ -69,13 +69,6 @@ object ProtoTypes {
6969
|constraint was: ${ctx.typerState.constraint}
7070
|constraint now: ${newctx.typerState.constraint}""")
7171
if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then
72-
// Remove all type lambdas and tvars introduced by testCompat
73-
for tvar <- newctx.typerState.ownedVars do
74-
inContext(newctx):
75-
if !tvar.isInstantiated then
76-
tvar.instantiate(fromBelow = false) // any direction
77-
78-
// commit any remaining changes in typer state
7972
newctx.typerState.commit()
8073
result
8174
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)