Skip to content

Commit a147975

Browse files
authored
Merge pull request #11673 from dotty-staging/fix-8955
Perform wildcard capture in overloading resolution
2 parents 2ab279c + 67491d6 commit a147975

File tree

4 files changed

+81
-69
lines changed

4 files changed

+81
-69
lines changed

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

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -612,18 +612,24 @@ trait Applications extends Compatibility {
612612
}
613613
}
614614

615+
/** The degree to which an argument has to match a formal parameter */
616+
enum ArgMatch:
617+
case SubType // argument is a relaxed subtype of formal
618+
case Compatible // argument is compatible with formal
619+
case CompatibleCAP // capture-converted argument is compatible with formal
620+
615621
/** Subclass of Application for the cases where we are interested only
616622
* in a "can/cannot apply" answer, without needing to construct trees or
617623
* issue error messages.
618624
*/
619-
abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(using Context)
625+
abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type, argMatch: ArgMatch)(using Context)
620626
extends Application[Arg](methRef, funType, args, resultType) {
621627
type TypedArg = Arg
622628
type Result = Unit
623629

624630
def applyKind = ApplyKind.Regular
625631

626-
protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match {
632+
protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match
627633
case ref: TermRef if ref.denot.isOverloaded =>
628634
// in this case we could not resolve overloading because no alternative
629635
// matches expected type
@@ -633,8 +639,17 @@ trait Applications extends Compatibility {
633639
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
634640
case _ => false
635641
}
636-
isCompatible(argtpe, formal) || ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
637-
}
642+
if argMatch == ArgMatch.SubType then
643+
argtpe relaxed_<:< formal.widenExpr
644+
else
645+
isCompatible(argtpe, formal)
646+
|| ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
647+
|| argMatch == ArgMatch.CompatibleCAP
648+
&& {
649+
val argtpe1 = argtpe.widen
650+
val captured = captureWildcards(argtpe1)
651+
(captured ne argtpe1) && isCompatible(captured, formal.widenExpr)
652+
}
638653

639654
/** The type of the given argument */
640655
protected def argType(arg: Arg, formal: Type): Type
@@ -654,8 +669,8 @@ trait Applications extends Compatibility {
654669
/** Subclass of Application for applicability tests with type arguments and value
655670
* argument trees.
656671
*/
657-
class ApplicableToTrees(methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
658-
extends TestApplication(methRef, methRef.widen, args, resultType) {
672+
class ApplicableToTrees(methRef: TermRef, args: List[Tree], resultType: Type, argMatch: ArgMatch)(using Context)
673+
extends TestApplication(methRef, methRef.widen, args, resultType, argMatch) {
659674
def argType(arg: Tree, formal: Type): Type =
660675
if untpd.isContextualClosure(arg) && defn.isContextFunctionType(formal) then arg.tpe
661676
else normalize(arg.tpe, formal)
@@ -665,18 +680,9 @@ trait Applications extends Compatibility {
665680
def harmonizeArgs(args: List[Tree]): List[Tree] = harmonize(args)
666681
}
667682

668-
/** Subclass of Application for applicability tests with type arguments and value
669-
* argument trees.
670-
*/
671-
class ApplicableToTreesDirectly(methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
672-
extends ApplicableToTrees(methRef, args, resultType) {
673-
override def argOK(arg: TypedArg, formal: Type): Boolean =
674-
argType(arg, formal) relaxed_<:< formal.widenExpr
675-
}
676-
677683
/** 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) {
684+
class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type, argMatch: ArgMatch)(using Context)
685+
extends TestApplication(methRef, methRef, args, resultType, argMatch) {
680686
def argType(arg: Type, formal: Type): Type = arg
681687
def treeToArg(arg: Tree): Type = arg.tpe
682688
def isVarArg(arg: Type): Boolean = arg.isRepeatedParam
@@ -1323,38 +1329,32 @@ trait Applications extends Compatibility {
13231329
/** Is given method reference applicable to argument trees `args`?
13241330
* @param resultType The expected result type of the application
13251331
*/
1326-
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean)(using Context): Boolean = {
1332+
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean, argMatch: ArgMatch)(using Context): Boolean = {
13271333
def isApp(using Context): Boolean =
1328-
new ApplicableToTrees(methRef, args, resultType).success
1334+
new ApplicableToTrees(methRef, args, resultType, argMatch).success
13291335
if (keepConstraint) isApp else explore(isApp)
13301336
}
13311337

1332-
/** Is given method reference applicable to argument trees `args` without inferring views?
1333-
* @param resultType The expected result type of the application
1334-
*/
1335-
def isDirectlyApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type)(using Context): Boolean =
1336-
explore(new ApplicableToTreesDirectly(methRef, args, resultType).success)
1337-
13381338
/** Is given method reference applicable to argument types `args`?
13391339
* @param resultType The expected result type of the application
13401340
*/
1341-
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type)(using Context): Boolean =
1342-
explore(new ApplicableToTypes(methRef, args, resultType).success)
1341+
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type, argMatch: ArgMatch)(using Context): Boolean =
1342+
explore(new ApplicableToTypes(methRef, args, resultType, argMatch).success)
13431343

13441344
/** Is given type applicable to argument trees `args`, possibly after inserting an `apply`?
13451345
* @param resultType The expected result type of the application
13461346
*/
13471347
def isApplicableType(tp: Type, args: List[Tree], resultType: Type, keepConstraint: Boolean)(using Context): Boolean =
13481348
onMethod(tp, args.nonEmpty) {
1349-
isApplicableMethodRef(_, args, resultType, keepConstraint)
1349+
isApplicableMethodRef(_, args, resultType, keepConstraint, ArgMatch.Compatible)
13501350
}
13511351

13521352
/** Is given type applicable to argument types `args`, possibly after inserting an `apply`?
13531353
* @param resultType The expected result type of the application
13541354
*/
13551355
def isApplicableType(tp: Type, args: List[Type], resultType: Type)(using Context): Boolean =
13561356
onMethod(tp, args.nonEmpty) {
1357-
isApplicableMethodRef(_, args, resultType)
1357+
isApplicableMethodRef(_, args, resultType, ArgMatch.Compatible)
13581358
}
13591359

13601360
private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match {
@@ -1485,9 +1485,9 @@ trait Applications extends Compatibility {
14851485
|| {
14861486
if tp1.isVarArgsMethod then
14871487
tp2.isVarArgsMethod
1488-
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType)
1488+
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, ArgMatch.Compatible)
14891489
else
1490-
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType)
1490+
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, ArgMatch.Compatible)
14911491
}
14921492
case tp1: PolyType => // (2)
14931493
inContext(ctx.fresh.setExploreTyperState()) {
@@ -1695,7 +1695,7 @@ trait Applications extends Compatibility {
16951695
*/
16961696
def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match {
16971697
case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType)) =>
1698-
val conformingAlts = alts.filter(alt =>
1698+
val conformingAlts = alts.filterConserve(alt =>
16991699
(alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType)))
17001700
conformingAlts match {
17011701
case Nil => chosen
@@ -1796,7 +1796,7 @@ trait Applications extends Compatibility {
17961796
}
17971797

17981798
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
1799-
alts filter (isApplicableMethodRef(_, argTypes, resultType))
1799+
alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP))
18001800

18011801
/** Normalization steps before checking arguments:
18021802
*
@@ -1865,11 +1865,11 @@ trait Applications extends Compatibility {
18651865

18661866
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
18671867
val alts2 = alts.filterConserve(alt =>
1868-
isDirectlyApplicableMethodRef(alt, args, resultType)
1868+
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.SubType)
18691869
)
18701870
if (alts2.isEmpty && !ctx.isAfterTyper)
18711871
alts.filterConserve(alt =>
1872-
isApplicableMethodRef(alt, args, resultType, keepConstraint = false)
1872+
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP)
18731873
)
18741874
else
18751875
alts2
@@ -1890,7 +1890,7 @@ trait Applications extends Compatibility {
18901890
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)
18911891

18921892
case pt @ PolyProto(targs1, pt1) =>
1893-
val alts1 = alts.filter(pt.canInstantiate)
1893+
val alts1 = alts.filterConserve(pt.canInstantiate)
18941894
if isDetermined(alts1) then alts1
18951895
else
18961896
def withinBounds(alt: TermRef) = alt.widen match
@@ -1904,7 +1904,7 @@ trait Applications extends Compatibility {
19041904
narrowByTypes(alts, args, resultType)
19051905

19061906
case pt =>
1907-
val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false))
1907+
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
19081908
if (compat.isEmpty)
19091909
/*
19101910
* the case should not be moved to the enclosing match
@@ -1967,15 +1967,15 @@ trait Applications extends Compatibility {
19671967
skipParamClause(pt.typedArgs().tpes, Nil), resType)
19681968
case _ =>
19691969
// prefer alternatives that need no eta expansion
1970-
val noCurried = alts.filter(!resultIsMethod(_))
1970+
val noCurried = alts.filterConserve(!resultIsMethod(_))
19711971
val noCurriedCount = noCurried.length
19721972
if noCurriedCount == 1 then
19731973
noCurried
19741974
else if noCurriedCount > 1 && noCurriedCount < alts.length then
19751975
resolveOverloaded1(noCurried, pt)
19761976
else
19771977
// prefer alternatves that match without default parameters
1978-
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
1978+
val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams)
19791979
val noDefaultsCount = noDefaults.length
19801980
if noDefaultsCount == 1 then
19811981
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 =>

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

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3585,36 +3585,6 @@ class Typer extends Namer
35853585
case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(pt)).withType(pt)
35863586
}
35873587

3588-
/** Replace every top-level occurrence of a wildcard type argument by
3589-
* a fresh skolem type. The skolem types are of the form $i.CAP, where
3590-
* $i is a skolem of type `scala.internal.TypeBox`, and `CAP` is its
3591-
* type member. See the documentation of `TypeBox` for a rationale why we do this.
3592-
*/
3593-
def captureWildcards(tp: Type)(using Context): Type = tp match {
3594-
case tp: AndOrType => tp.derivedAndOrType(captureWildcards(tp.tp1), captureWildcards(tp.tp2))
3595-
case tp: RefinedType => tp.derivedRefinedType(captureWildcards(tp.parent), tp.refinedName, tp.refinedInfo)
3596-
case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent))
3597-
case tp: LazyRef => captureWildcards(tp.ref)
3598-
case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot)
3599-
case tp @ AppliedType(tycon, args) if tp.hasWildcardArg =>
3600-
tycon.typeParams match {
3601-
case tparams @ ((_: Symbol) :: _) =>
3602-
val boundss = tparams.map(_.paramInfo.substApprox(tparams.asInstanceOf[List[TypeSymbol]], args))
3603-
val args1 = args.zipWithConserve(boundss) { (arg, bounds) =>
3604-
arg match {
3605-
case TypeBounds(lo, hi) =>
3606-
val skolem = SkolemType(defn.TypeBoxClass.typeRef.appliedTo(lo | bounds.loBound, hi & bounds.hiBound))
3607-
TypeRef(skolem, defn.TypeBox_CAP)
3608-
case arg => arg
3609-
}
3610-
}
3611-
tp.derivedAppliedType(tycon, args1)
3612-
case _ =>
3613-
tp
3614-
}
3615-
case _ => tp
3616-
}
3617-
36183588
def adaptToSubType(wtp: Type): Tree =
36193589
// try converting a constant to the target type
36203590
ConstFold(tree).tpe.widenTermRefExpr.normalized match

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)