Skip to content

Commit b08133b

Browse files
committed
Perform wildcard capture in overloading resolution
Fixes #8955 This kicks in if an argument is of wildcard type, say, C[?}, and one or more alternatives have a poymorphic method type with type parameter `T` and corresponding parameter type `C[T]`. We did wildcard capture for regular applicatiins, but not for applicability testing of overloaded alternatives.
1 parent c4dea7b commit b08133b

File tree

3 files changed

+73
-23
lines changed

3 files changed

+73
-23
lines changed

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

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ trait Applications extends Compatibility {
616616
* in a "can/cannot apply" answer, without needing to construct trees or
617617
* issue error messages.
618618
*/
619-
abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(using Context)
619+
abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type, captureWild: Boolean)(using Context)
620620
extends Application[Arg](methRef, funType, args, resultType) {
621621
type TypedArg = Arg
622622
type Result = Unit
@@ -633,7 +633,14 @@ trait Applications extends Compatibility {
633633
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
634634
case _ => false
635635
}
636-
isCompatible(argtpe, formal) || ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
636+
isCompatible(argtpe, formal)
637+
|| ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
638+
|| captureWild
639+
&& {
640+
val argtpe1 = argtpe.widen
641+
val captured = captureWildcards(argtpe1)
642+
(captured ne argtpe1) && (captured <:< formal.widenExpr)
643+
}
637644
}
638645

639646
/** The type of the given argument */
@@ -654,8 +661,8 @@ trait Applications extends Compatibility {
654661
/** Subclass of Application for applicability tests with type arguments and value
655662
* argument trees.
656663
*/
657-
class ApplicableToTrees(methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
658-
extends TestApplication(methRef, methRef.widen, args, resultType) {
664+
class ApplicableToTrees(methRef: TermRef, args: List[Tree], resultType: Type, captureWild: Boolean)(using Context)
665+
extends TestApplication(methRef, methRef.widen, args, resultType, captureWild) {
659666
def argType(arg: Tree, formal: Type): Type =
660667
if untpd.isContextualClosure(arg) && defn.isContextFunctionType(formal) then arg.tpe
661668
else normalize(arg.tpe, formal)
@@ -669,14 +676,14 @@ trait Applications extends Compatibility {
669676
* argument trees.
670677
*/
671678
class ApplicableToTreesDirectly(methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
672-
extends ApplicableToTrees(methRef, args, resultType) {
679+
extends ApplicableToTrees(methRef, args, resultType, captureWild = false) {
673680
override def argOK(arg: TypedArg, formal: Type): Boolean =
674681
argType(arg, formal) relaxed_<:< formal.widenExpr
675682
}
676683

677684
/** Subclass of Application for applicability tests with value argument types. */
678-
class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type)(using Context)
679-
extends TestApplication(methRef, methRef, args, resultType) {
685+
class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type, captureWild: Boolean)(using Context)
686+
extends TestApplication(methRef, methRef, args, resultType, captureWild) {
680687
def argType(arg: Type, formal: Type): Type = arg
681688
def treeToArg(arg: Tree): Type = arg.tpe
682689
def isVarArg(arg: Type): Boolean = arg.isRepeatedParam
@@ -1323,13 +1330,14 @@ trait Applications extends Compatibility {
13231330
/** Is given method reference applicable to argument trees `args`?
13241331
* @param resultType The expected result type of the application
13251332
*/
1326-
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean)(using Context): Boolean = {
1333+
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean, captureWild: Boolean)(using Context): Boolean = {
13271334
def isApp(using Context): Boolean =
1328-
new ApplicableToTrees(methRef, args, resultType).success
1335+
new ApplicableToTrees(methRef, args, resultType, captureWild).success
13291336
if (keepConstraint) isApp else explore(isApp)
13301337
}
13311338

1332-
/** Is given method reference applicable to argument trees `args` without inferring views?
1339+
/** Is given method reference applicable to argument trees `args` without inferring views
1340+
* or capturing wildcards?
13331341
* @param resultType The expected result type of the application
13341342
*/
13351343
def isDirectlyApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type)(using Context): Boolean =
@@ -1338,23 +1346,23 @@ trait Applications extends Compatibility {
13381346
/** Is given method reference applicable to argument types `args`?
13391347
* @param resultType The expected result type of the application
13401348
*/
1341-
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type)(using Context): Boolean =
1342-
explore(new ApplicableToTypes(methRef, args, resultType).success)
1349+
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type, captureWild: Boolean)(using Context): Boolean =
1350+
explore(new ApplicableToTypes(methRef, args, resultType, captureWild).success)
13431351

13441352
/** Is given type applicable to argument trees `args`, possibly after inserting an `apply`?
13451353
* @param resultType The expected result type of the application
13461354
*/
13471355
def isApplicableType(tp: Type, args: List[Tree], resultType: Type, keepConstraint: Boolean)(using Context): Boolean =
13481356
onMethod(tp, args.nonEmpty) {
1349-
isApplicableMethodRef(_, args, resultType, keepConstraint)
1357+
isApplicableMethodRef(_, args, resultType, keepConstraint, captureWild = false)
13501358
}
13511359

13521360
/** Is given type applicable to argument types `args`, possibly after inserting an `apply`?
13531361
* @param resultType The expected result type of the application
13541362
*/
13551363
def isApplicableType(tp: Type, args: List[Type], resultType: Type)(using Context): Boolean =
13561364
onMethod(tp, args.nonEmpty) {
1357-
isApplicableMethodRef(_, args, resultType)
1365+
isApplicableMethodRef(_, args, resultType, captureWild = false)
13581366
}
13591367

13601368
private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match {
@@ -1485,9 +1493,9 @@ trait Applications extends Compatibility {
14851493
|| {
14861494
if tp1.isVarArgsMethod then
14871495
tp2.isVarArgsMethod
1488-
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType)
1496+
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, captureWild = false)
14891497
else
1490-
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType)
1498+
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, captureWild = false)
14911499
}
14921500
case tp1: PolyType => // (2)
14931501
inContext(ctx.fresh.setExploreTyperState()) {
@@ -1695,7 +1703,7 @@ trait Applications extends Compatibility {
16951703
*/
16961704
def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match {
16971705
case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType)) =>
1698-
val conformingAlts = alts.filter(alt =>
1706+
val conformingAlts = alts.filterConserve(alt =>
16991707
(alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType)))
17001708
conformingAlts match {
17011709
case Nil => chosen
@@ -1796,7 +1804,7 @@ trait Applications extends Compatibility {
17961804
}
17971805

17981806
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
1799-
alts filter (isApplicableMethodRef(_, argTypes, resultType))
1807+
alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, captureWild = true))
18001808

18011809
/** Normalization steps before checking arguments:
18021810
*
@@ -1869,7 +1877,7 @@ trait Applications extends Compatibility {
18691877
)
18701878
if (alts2.isEmpty && !ctx.isAfterTyper)
18711879
alts.filterConserve(alt =>
1872-
isApplicableMethodRef(alt, args, resultType, keepConstraint = false)
1880+
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, captureWild = true)
18731881
)
18741882
else
18751883
alts2
@@ -1890,7 +1898,7 @@ trait Applications extends Compatibility {
18901898
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)
18911899

18921900
case pt @ PolyProto(targs1, pt1) =>
1893-
val alts1 = alts.filter(pt.canInstantiate)
1901+
val alts1 = alts.filterConserve(pt.canInstantiate)
18941902
if isDetermined(alts1) then alts1
18951903
else
18961904
def withinBounds(alt: TermRef) = alt.widen match
@@ -1904,7 +1912,7 @@ trait Applications extends Compatibility {
19041912
narrowByTypes(alts, args, resultType)
19051913

19061914
case pt =>
1907-
val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false))
1915+
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
19081916
if (compat.isEmpty)
19091917
/*
19101918
* the case should not be moved to the enclosing match
@@ -1967,15 +1975,15 @@ trait Applications extends Compatibility {
19671975
skipParamClause(pt.typedArgs().tpes, Nil), resType)
19681976
case _ =>
19691977
// prefer alternatives that need no eta expansion
1970-
val noCurried = alts.filter(!resultIsMethod(_))
1978+
val noCurried = alts.filterConserve(!resultIsMethod(_))
19711979
val noCurriedCount = noCurried.length
19721980
if noCurriedCount == 1 then
19731981
noCurried
19741982
else if noCurriedCount > 1 && noCurriedCount < alts.length then
19751983
resolveOverloaded1(noCurried, pt)
19761984
else
19771985
// prefer alternatves that match without default parameters
1978-
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
1986+
val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams)
19791987
val noDefaultsCount = noDefaults.length
19801988
if noDefaultsCount == 1 then
19811989
noDefaults

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,36 @@ object Inferencing {
489489

490490
propagate(accu(SimpleIdentityMap.empty, tp))
491491
}
492+
493+
/** Replace every top-level occurrence of a wildcard type argument by
494+
* a fresh skolem type. The skolem types are of the form $i.CAP, where
495+
* $i is a skolem of type `scala.internal.TypeBox`, and `CAP` is its
496+
* type member. See the documentation of `TypeBox` for a rationale why we do this.
497+
*/
498+
def captureWildcards(tp: Type)(using Context): Type = tp match {
499+
case tp @ AppliedType(tycon, args) if tp.hasWildcardArg =>
500+
tycon.typeParams match {
501+
case tparams @ ((_: Symbol) :: _) =>
502+
val boundss = tparams.map(_.paramInfo.substApprox(tparams.asInstanceOf[List[TypeSymbol]], args))
503+
val args1 = args.zipWithConserve(boundss) { (arg, bounds) =>
504+
arg match {
505+
case TypeBounds(lo, hi) =>
506+
val skolem = SkolemType(defn.TypeBoxClass.typeRef.appliedTo(lo | bounds.loBound, hi & bounds.hiBound))
507+
TypeRef(skolem, defn.TypeBox_CAP)
508+
case arg => arg
509+
}
510+
}
511+
tp.derivedAppliedType(tycon, args1)
512+
case _ =>
513+
tp
514+
}
515+
case tp: AndOrType => tp.derivedAndOrType(captureWildcards(tp.tp1), captureWildcards(tp.tp2))
516+
case tp: RefinedType => tp.derivedRefinedType(captureWildcards(tp.parent), tp.refinedName, tp.refinedInfo)
517+
case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent))
518+
case tp: LazyRef => captureWildcards(tp.ref)
519+
case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot)
520+
case _ => tp
521+
}
492522
}
493523

494524
trait Inferencing { this: Typer =>

tests/pos/i8955.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class One[A]{}
2+
def test[A](a: Class[A]) = println(a)
3+
def test[A](as: List[A]) = println(as)
4+
def tost[A](a: Class[A]) = println(a)
5+
6+
@main def main() = {
7+
val one: One[_] = new One()
8+
test(one.getClass(): Class[?]) //this fails
9+
val cls = one.getClass()
10+
test(cls) //this is ok
11+
tost(one.getClass()) //this is also ok
12+
}

0 commit comments

Comments
 (0)