Skip to content

Commit 841975b

Browse files
committed
Revised scheme
Revised scheme for checking applied types containing wildcards in F-bounds.
1 parent 861e59b commit 841975b

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

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

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,18 +257,94 @@ 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 =>
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)
269-
val loBound = instantiate(bounds.lo, argTypes)
270-
if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound))
271-
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
325+
}
326+
def narrowBound(bound: Type, fromBelow: Boolean): Unit = {
327+
val bound1 = massage(bound)
328+
if (bound1 ne bound) {
329+
if (checkCtx eq ctx) checkCtx = ctx.fresh.setFreshGADTBounds
330+
if (!checkCtx.gadt.contains(sym)) checkCtx.gadt.addEmptyBounds(sym)
331+
checkCtx.gadt.addBound(sym, bound1, fromBelow)
332+
typr.println("install GADT bound $bound1 for when checking F-bounded $sym")
333+
}
334+
}
335+
narrowBound(sym.info.loBound, fromBelow = true)
336+
narrowBound(sym.info.hiBound, fromBelow = false)
337+
}
338+
}
339+
340+
val hiBound = instantiate(bounds.hi, skolemizedArgTypes)
341+
val loBound = instantiate(bounds.lo, skolemizedArgTypes)
342+
343+
def check(implicit ctx: Context) = {
344+
if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound))
345+
if (!(loBound <:< hi)) violations += ((arg, "lower", loBound))
346+
}
347+
check(checkCtx)
272348
}
273349
arg.tpe match {
274350
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, _) =>

0 commit comments

Comments
 (0)