Skip to content

Commit 28c1ae3

Browse files
committed
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.
1 parent 6feb022 commit 28c1ae3

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)