Skip to content

Commit 1734cc1

Browse files
committed
Interpolate any type vars from comparing against SelectionProto
If the method being selected is polymorphic, then it is added, along with fresh type vars, to the constraint. Normally this is fine, because when the selection is then applied, the type vars are interpolated (during tree simplification). But if the method being selected is a macro that's being jointly compiled, then the compilation may be suspended, which leaves a trailing type lambda in the constraint. I tried addressing this in a few different ways. Initially with an exemption in TreeChecker, but that seemed too remote and too liberal. Later I tried addressing it specifically in inlining (in inlineIfNeeded) but that also seemed too remote and extensive. In the end I thought handling this around the testSubType call seemed most suitable. Interpolating on all the new type vars in general is too eager, so I ended up guarding it only for SelectionProto, sadly.
1 parent ab2ed31 commit 1734cc1

File tree

4 files changed

+168
-144
lines changed

4 files changed

+168
-144
lines changed

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

+155-143
Original file line numberDiff line numberDiff line change
@@ -585,160 +585,172 @@ trait Inferencing { this: Typer =>
585585
if (ownedVars ne locked) && !ownedVars.isEmpty then
586586
val qualifying = ownedVars -- locked
587587
if (!qualifying.isEmpty) {
588-
typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, pt = $pt, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}")
589588
val resultAlreadyConstrained =
590589
tree.isInstanceOf[Apply] || tree.tpe.isInstanceOf[MethodOrPoly]
591590
if (!resultAlreadyConstrained)
592591
constrainResult(tree.symbol, tree.tpe, pt)
593592
// This is needed because it could establish singleton type upper bounds. See i2998.scala.
594593

595-
val tp = tree.tpe.widen
596-
val vs = variances(tp, pt)
597-
598-
// Avoid interpolating variables occurring in tree's type if typerstate has unreported errors.
599-
// Reason: The errors might reflect unsatisfiable constraints. In that
600-
// case interpolating without taking account the constraints risks producing
601-
// nonsensical types that then in turn produce incomprehensible errors.
602-
// An example is in neg/i1240.scala. Without the condition in the next code line
603-
// we get for
604-
//
605-
// val y: List[List[String]] = List(List(1))
606-
//
607-
// i1430.scala:5: error: type mismatch:
608-
// found : Int(1)
609-
// required: Nothing
610-
// val y: List[List[String]] = List(List(1))
611-
// ^
612-
// With the condition, we get the much more sensical:
613-
//
614-
// i1430.scala:5: error: type mismatch:
615-
// found : Int(1)
616-
// required: String
617-
// val y: List[List[String]] = List(List(1))
618-
if state.reporter.hasUnreportedErrors then return tree
619-
620-
def constraint = state.constraint
621-
622-
/** Values of this type report type variables to instantiate with variance indication:
623-
* +1 variable appears covariantly, can be instantiated from lower bound
624-
* -1 variable appears contravariantly, can be instantiated from upper bound
625-
* 0 variable does not appear at all, can be instantiated from either bound
626-
*/
627-
type ToInstantiate = List[(TypeVar, Int)]
628-
629-
val toInstantiate: ToInstantiate =
630-
val buf = new mutable.ListBuffer[(TypeVar, Int)]
631-
for tvar <- qualifying do
632-
if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then
633-
constrainIfDependentParamRef(tvar, tree)
634-
if !tvar.isInstantiated then
635-
// isInstantiated needs to be checked again, since previous interpolations could already have
636-
// instantiated `tvar` through unification.
637-
val v = vs(tvar)
638-
if v == null then buf += ((tvar, 0))
639-
else if v.intValue != 0 then buf += ((tvar, v.intValue))
640-
else comparing(cmp =>
641-
if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then
642-
// Invariant: The type of a tree whose enclosing scope is level
643-
// N only contains type variables of level <= N.
644-
typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint")
645-
cmp.atLevel(ctx.nestingLevel, tvar.origin)
646-
else
647-
typr.println(i"no interpolation for nonvariant $tvar in $state")
648-
)
649-
buf.toList
650-
651-
def typeVarsIn(xs: ToInstantiate): TypeVars =
652-
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
653-
654-
/** Filter list of proposed instantiations so that they don't constrain further
655-
* the current constraint.
656-
*/
657-
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
658-
val excluded = // ignore dependencies from other variables that are being instantiated
659-
typeVarsIn(tvs0)
660-
def step(tvs: ToInstantiate): ToInstantiate = tvs match
661-
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
662-
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
663-
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
664-
if v == 0 && !aboveOK then
665-
step((tvar, 1) :: tvs1)
666-
else if v == 0 && !belowOK then
667-
step((tvar, -1) :: tvs1)
668-
else if v == -1 && !aboveOK || v == 1 && !belowOK then
669-
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
670-
step(tvs1)
671-
else // no conflict, keep the instantiation proposal
672-
tvs.derivedCons(hd, step(tvs1))
673-
case Nil =>
674-
Nil
675-
val tvs1 = step(tvs0)
676-
if tvs1 eq tvs0 then tvs1
677-
else filterByDeps(tvs1) // filter again with smaller excluded set
678-
end filterByDeps
679-
680-
/** Instantiate all type variables in `tvs` in the indicated directions,
681-
* as described in the doc comment of `ToInstantiate`.
682-
* If a type variable A is instantiated from below, and there is another
683-
* type variable B in `buf` that is known to be smaller than A, wait and
684-
* instantiate all other type variables before trying to instantiate A again.
685-
* Dually, wait instantiating a type variable from above as long as it has
686-
* upper bounds in `buf`.
687-
*
688-
* This is done to avoid loss of precision when forming unions. An example
689-
* is in i7558.scala:
690-
*
691-
* type Tr[+V1, +O1 <: V1]
692-
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
693-
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
694-
*
695-
* Here we interpolate at some point V2 and O2 given the constraint
696-
*
697-
* V2 >: V3, O2 >: O3, O2 <: V2
698-
*
699-
* where O3 and V3 are type refs with O3 <: V3.
700-
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
701-
* instantiate O2 to V3, leading to the final constraint
702-
*
703-
* V2 := V3, O2 := V3
704-
*
705-
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
706-
* more flexible instantiation
707-
*
708-
* V2 := V3, O2 := O3
709-
*/
710-
def doInstantiate(tvs: ToInstantiate): Unit =
711-
712-
/** Try to instantiate `tvs`, return any suspended type variables */
713-
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
714-
case (hd @ (tvar, v)) :: tvs1 =>
715-
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
716-
typr.println(
717-
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
718-
if tvar.isInstantiated then
719-
tryInstantiate(tvs1)
720-
else
721-
val suspend = tvs1.exists{ (following, _) =>
722-
if fromBelow
723-
then constraint.isLess(following.origin, tvar.origin)
724-
else constraint.isLess(tvar.origin, following.origin)
725-
}
726-
if suspend then
727-
typr.println(i"suspended: $hd")
728-
hd :: tryInstantiate(tvs1)
729-
else
730-
tvar.instantiate(fromBelow)
731-
tryInstantiate(tvs1)
732-
case Nil => Nil
733-
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
734-
end doInstantiate
735-
736-
doInstantiate(filterByDeps(toInstantiate))
594+
interpolateTypeVarsUnconstrained(tree, pt, locked)
737595
}
738596
end if
739597
tree
740598
end interpolateTypeVars
741599

600+
def interpolateTypeVarsUnconstrained(tree: Tree, pt: Type, locked: TypeVars)(using Context): Unit =
601+
val state = ctx.typerState
602+
val ownedVars = state.ownedVars
603+
if (ownedVars eq locked) || ownedVars.isEmpty then return
604+
val qualifying = ownedVars -- locked
605+
if qualifying.isEmpty then return
606+
607+
val tp = tree.tpe.widen
608+
609+
typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, pt = $pt, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}")
610+
611+
val vs = variances(tp, pt)
612+
613+
// Avoid interpolating variables occurring in tree's type if typerstate has unreported errors.
614+
// Reason: The errors might reflect unsatisfiable constraints. In that
615+
// case interpolating without taking account the constraints risks producing
616+
// nonsensical types that then in turn produce incomprehensible errors.
617+
// An example is in neg/i1240.scala. Without the condition in the next code line
618+
// we get for
619+
//
620+
// val y: List[List[String]] = List(List(1))
621+
//
622+
// i1430.scala:5: error: type mismatch:
623+
// found : Int(1)
624+
// required: Nothing
625+
// val y: List[List[String]] = List(List(1))
626+
// ^
627+
// With the condition, we get the much more sensical:
628+
//
629+
// i1430.scala:5: error: type mismatch:
630+
// found : Int(1)
631+
// required: String
632+
// val y: List[List[String]] = List(List(1))
633+
if state.reporter.hasUnreportedErrors then return
634+
635+
def constraint = state.constraint
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(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+
buf.toList
665+
666+
def typeVarsIn(xs: ToInstantiate): TypeVars =
667+
xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1)
668+
669+
/** Filter list of proposed instantiations so that they don't constrain further
670+
* the current constraint.
671+
*/
672+
def filterByDeps(tvs0: ToInstantiate): ToInstantiate =
673+
val excluded = // ignore dependencies from other variables that are being instantiated
674+
typeVarsIn(tvs0)
675+
def step(tvs: ToInstantiate): ToInstantiate = tvs match
676+
case tvs @ (hd @ (tvar, v)) :: tvs1 =>
677+
def aboveOK = !constraint.dependsOn(tvar, excluded, co = true)
678+
def belowOK = !constraint.dependsOn(tvar, excluded, co = false)
679+
if v == 0 && !aboveOK then
680+
step((tvar, 1) :: tvs1)
681+
else if v == 0 && !belowOK then
682+
step((tvar, -1) :: tvs1)
683+
else if v == -1 && !aboveOK || v == 1 && !belowOK then
684+
typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint")
685+
step(tvs1)
686+
else // no conflict, keep the instantiation proposal
687+
tvs.derivedCons(hd, step(tvs1))
688+
case Nil =>
689+
Nil
690+
val tvs1 = step(tvs0)
691+
if tvs1 eq tvs0 then tvs1
692+
else filterByDeps(tvs1) // filter again with smaller excluded set
693+
end filterByDeps
694+
695+
/** Instantiate all type variables in `tvs` in the indicated directions,
696+
* as described in the doc comment of `ToInstantiate`.
697+
* If a type variable A is instantiated from below, and there is another
698+
* type variable B in `buf` that is known to be smaller than A, wait and
699+
* instantiate all other type variables before trying to instantiate A again.
700+
* Dually, wait instantiating a type variable from above as long as it has
701+
* upper bounds in `buf`.
702+
*
703+
* This is done to avoid loss of precision when forming unions. An example
704+
* is in i7558.scala:
705+
*
706+
* type Tr[+V1, +O1 <: V1]
707+
* extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ???
708+
* def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl
709+
*
710+
* Here we interpolate at some point V2 and O2 given the constraint
711+
*
712+
* V2 >: V3, O2 >: O3, O2 <: V2
713+
*
714+
* where O3 and V3 are type refs with O3 <: V3.
715+
* If we interpolate V2 first to V3 | O2, the widenUnion algorithm will
716+
* instantiate O2 to V3, leading to the final constraint
717+
*
718+
* V2 := V3, O2 := V3
719+
*
720+
* But if we instantiate O2 first to O3, and V2 next to V3, we get the
721+
* more flexible instantiation
722+
*
723+
* V2 := V3, O2 := O3
724+
*/
725+
def doInstantiate(tvs: ToInstantiate): Unit =
726+
727+
/** Try to instantiate `tvs`, return any suspended type variables */
728+
def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match
729+
case (hd @ (tvar, v)) :: tvs1 =>
730+
val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound)
731+
typr.println(
732+
i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint")
733+
if tvar.isInstantiated then
734+
tryInstantiate(tvs1)
735+
else
736+
val suspend = tvs1.exists{ (following, _) =>
737+
if fromBelow
738+
then constraint.isLess(following.origin, tvar.origin)
739+
else constraint.isLess(tvar.origin, following.origin)
740+
}
741+
if suspend then
742+
typr.println(i"suspended: $hd")
743+
hd :: tryInstantiate(tvs1)
744+
else
745+
tvar.instantiate(fromBelow)
746+
tryInstantiate(tvs1)
747+
case Nil => Nil
748+
if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs))
749+
end doInstantiate
750+
751+
doInstantiate(filterByDeps(toInstantiate))
752+
end interpolateTypeVarsUnconstrained
753+
742754
/** If `tvar` represents a parameter of a dependent method type in the current `call`
743755
* approximate it from below with the type of the actual argument. Skolemize that
744756
* type if necessary to make it a Singleton.

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -3767,7 +3767,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
37673767
|To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""".stripMargin, tree.srcPos.startPos)
37683768
tree
37693769
}
3770-
else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match
3770+
else
3771+
val locked1 = locked ++ ctx.typerState.ownedVars
3772+
val cmpRes = TypeComparer.testSubType(tree.tpe.widenExpr, pt)
3773+
if pt.isInstanceOf[SelectionProto] then
3774+
interpolateTypeVarsUnconstrained(tree, pt, locked1)
3775+
cmpRes match
37713776
case CompareResult.Fail =>
37723777
wtp match
37733778
case wtp: MethodType => missingArgs(wtp)

tests/pos/i16331/Macro.scala

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import scala.quoted._
2+
object Macro:
3+
transparent inline def macroDef[A](): Int = ${ macroDefImpl() }
4+
def macroDefImpl()(using q: Quotes): Expr[Int] = '{0}

tests/pos/i16331/Main.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Main:
2+
inline def inlineDef[A](): Any = Macro.macroDef()
3+
def main(args: Array[String]) = inlineDef()

0 commit comments

Comments
 (0)