Skip to content

Commit 803b52e

Browse files
authored
Merge pull request #279 from scala/backport-lts-3.3-22697
Backport "Revert lambda cleanup" to 3.3 LTS
2 parents bc573a8 + 87eff62 commit 803b52e

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)