Skip to content

Commit 8ecc927

Browse files
authored
Merge pull request #6147 from dotty-staging/fix-fbounds-wildcards
Fix #6146: Fix bounds check involving wildcards and f-bounds
2 parents acb2ceb + 8f57ade commit 8ecc927

File tree

3 files changed

+102
-12
lines changed

3 files changed

+102
-12
lines changed

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

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,20 +257,96 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
257257
* @param boundss The list of type bounds
258258
* @param instantiate A function that maps a bound type and the list of argument types to a resulting type.
259259
* Needed to handle bounds that refer to other bounds.
260+
* @param app The applied type whose arguments are checked, or NoType if
261+
* arguments are for a TypeApply.
262+
*
263+
* This is particularly difficult for F-bounds that also contain wildcard arguments (see below).
264+
* In fact the current treatment for this sitiuation can so far only be classified as "not obviously wrong",
265+
* (maybe it still needs to be revised).
260266
*/
261-
def boundsViolations(args: List[Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): List[BoundsViolation] = {
267+
def boundsViolations(args: List[Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type)(implicit ctx: Context): List[BoundsViolation] = {
262268
val argTypes = args.tpes
269+
270+
/** Replace all wildcards in `tps` with `<app>#<tparam>` where `<tparam>` is the
271+
* type parameter corresponding to the wildcard.
272+
*/
273+
def skolemizeWildcardArgs(tps: List[Type], app: Type) = app match {
274+
case AppliedType(tycon, args) if tycon.typeSymbol.isClass && !scala2Mode =>
275+
tps.zipWithConserve(tycon.typeSymbol.typeParams) {
276+
(tp, tparam) => tp match {
277+
case _: TypeBounds => app.select(tparam)
278+
case _ => tp
279+
}
280+
}
281+
case _ => tps
282+
}
283+
284+
// Skolemized argument types are used to substitute in F-bounds.
285+
val skolemizedArgTypes = skolemizeWildcardArgs(argTypes, app)
263286
val violations = new mutable.ListBuffer[BoundsViolation]
287+
264288
for ((arg, bounds) <- args zip boundss) {
265289
def checkOverlapsBounds(lo: Type, hi: Type): Unit = {
266-
//println(i"instantiating ${bounds.hi} with $argTypes")
267290
//println(i" = ${instantiate(bounds.hi, argTypes)}")
268-
val hiBound = instantiate(bounds.hi, argTypes.mapConserve(_.bounds.hi))
269-
val loBound = instantiate(bounds.lo, argTypes.mapConserve(_.bounds.lo))
270-
// Note that argTypes can contain a TypeBounds type for arguments that are
271-
// not fully determined. In that case we need to check against the hi bound of the argument.
272-
if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound))
273-
if (!(loBound <:< hi)) violations += ((arg, "lower", bounds.lo))
291+
292+
var checkCtx = ctx // the context to be used for bounds checking
293+
if (argTypes ne skolemizedArgTypes) { // some of the arguments are wildcards
294+
295+
/** Is there a `LazyRef(TypeRef(_, sym))` reference in `tp`? */
296+
def isLazyIn(sym: Symbol, tp: Type): Boolean = {
297+
def isReference(tp: Type) = tp match {
298+
case tp: LazyRef => tp.ref.isInstanceOf[TypeRef] && tp.ref.typeSymbol == sym
299+
case _ => false
300+
}
301+
tp.existsPart(isReference, forceLazy = false)
302+
}
303+
304+
/** The argument types of the form `TypeRef(_, sym)` which appear as a LazyRef in `bounds`.
305+
* This indicates that the application is used as an F-bound for the symbol referred to in the LazyRef.
306+
*/
307+
val lazyRefs = skolemizedArgTypes collect {
308+
case tp: TypeRef if isLazyIn(tp.symbol, bounds) => tp.symbol
309+
}
310+
311+
for (sym <- lazyRefs) {
312+
313+
// If symbol `S` has an F-bound such as `C[_, S]` that contains wildcards,
314+
// add a modifieed bound where wildcards are skolemized as a GADT bound for `S`.
315+
// E.g. for `C[_, S]` we would add `C[C[_, S]#T0, S]` where `T0` is the first
316+
// type parameter of `C`. The new bound is added as a GADT bound for `S` in
317+
// `checkCtx`.
318+
// This mirrors what we do for the bounds that are checked and allows us thus
319+
// to bounds-check F-bounds with wildcards. A test case is pos/i6146.scala.
320+
321+
def massage(tp: Type): Type = tp match {
322+
case tp @ AppliedType(tycon, args) =>
323+
tp.derivedAppliedType(tycon, skolemizeWildcardArgs(args, tp))
324+
case tp: AndOrType =>
325+
tp.derivedAndOrType(massage(tp.tp1), massage(tp.tp2))
326+
case _ => tp
327+
}
328+
def narrowBound(bound: Type, fromBelow: Boolean): Unit = {
329+
val bound1 = massage(bound)
330+
if (bound1 ne bound) {
331+
if (checkCtx eq ctx) checkCtx = ctx.fresh.setFreshGADTBounds
332+
if (!checkCtx.gadt.contains(sym)) checkCtx.gadt.addEmptyBounds(sym)
333+
checkCtx.gadt.addBound(sym, bound1, fromBelow)
334+
typr.println("install GADT bound $bound1 for when checking F-bounded $sym")
335+
}
336+
}
337+
narrowBound(sym.info.loBound, fromBelow = true)
338+
narrowBound(sym.info.hiBound, fromBelow = false)
339+
}
340+
}
341+
342+
val hiBound = instantiate(bounds.hi, skolemizedArgTypes)
343+
val loBound = instantiate(bounds.lo, skolemizedArgTypes)
344+
345+
def check(implicit ctx: Context) = {
346+
if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound))
347+
if (!(loBound <:< hi)) violations += ((arg, "lower", loBound))
348+
}
349+
check(checkCtx)
274350
}
275351
arg.tpe match {
276352
case TypeBounds(lo, hi) => checkOverlapsBounds(lo, hi)

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,24 @@ object Checking {
3636
/** A general checkBounds method that can be used for TypeApply nodes as
3737
* well as for AppliedTypeTree nodes. Also checks that type arguments to
3838
* *-type parameters are fully applied.
39+
* See TypeOps.boundsViolations for an explanation of the parameters.
3940
*/
40-
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = {
41+
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type = NoType)(implicit ctx: Context): Unit = {
4142
(args, boundss).zipped.foreach { (arg, bound) =>
4243
if (!bound.isLambdaSub && !arg.tpe.hasSimpleKind) {
4344
// see MissingTypeParameterFor
4445
ctx.error(ex"missing type parameter(s) for $arg", arg.sourcePos)
4546
}
4647
}
47-
for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate))
48+
for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate, app))
4849
ctx.error(
4950
DoesNotConformToBound(arg.tpe, which, bound)(err),
5051
arg.sourcePos.focus)
5152
}
5253

5354
/** Check that type arguments `args` conform to corresponding bounds in `tl`
5455
* Note: This does not check the bounds of AppliedTypeTrees. These
55-
* are handled by method checkBounds in FirstTransform
56+
* are handled by method checkAppliedType below.
5657
*/
5758
def checkBounds(args: List[tpd.Tree], tl: TypeLambda)(implicit ctx: Context): Unit =
5859
checkBounds(args, tl.paramInfos, _.substParams(tl, _))
@@ -77,7 +78,7 @@ object Checking {
7778
val bounds = tparams.map(_.paramInfoAsSeenFrom(tree.tpe).bounds)
7879
def instantiate(bound: Type, args: List[Type]) =
7980
HKTypeLambda.fromParams(tparams, bound).appliedTo(args)
80-
if (boundsCheck) checkBounds(orderedArgs, bounds, instantiate)
81+
if (boundsCheck) checkBounds(orderedArgs, bounds, instantiate, tree.tpe)
8182

8283
def checkWildcardApply(tp: Type): Unit = tp match {
8384
case tp @ AppliedType(tycon, _) =>

tests/pos/i6146.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
trait BS[T, S <: BS[T, S]]
2+
trait IS extends BS[Int, IS]
3+
4+
sealed trait BSElem[T, S <: BS[_, S]]
5+
// old error: Type argument S does not conform to upper bound BS[Any, LazyRef(S)]
6+
7+
object BSElem {
8+
implicit val intStreamShape: BSElem[Int, IS] = ???
9+
}
10+
class Ops[A] {
11+
def asJavaSeqStream[S <: BS[_, S]](implicit s: BSElem[A, S]): S = ???
12+
// old error: Type argument S does not conform to upper bound BS[Any, LazyRef(S)]
13+
}

0 commit comments

Comments
 (0)