@@ -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)
0 commit comments