Skip to content

Perform wildcard capture in overloading resolution #11673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 39 additions & 39 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -612,18 +612,24 @@ trait Applications extends Compatibility {
}
}

/** The degree to which an argument has to match a formal parameter */
enum ArgMatch:
case SubType // argument is a relaxed subtype of formal
case Compatible // argument is compatible with formal
case CompatibleCAP // capture-converted argument is compatible with formal

/** Subclass of Application for the cases where we are interested only
* in a "can/cannot apply" answer, without needing to construct trees or
* issue error messages.
*/
abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(using Context)
abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type, argMatch: ArgMatch)(using Context)
extends Application[Arg](methRef, funType, args, resultType) {
type TypedArg = Arg
type Result = Unit

def applyKind = ApplyKind.Regular

protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match {
protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match
case ref: TermRef if ref.denot.isOverloaded =>
// in this case we could not resolve overloading because no alternative
// matches expected type
Expand All @@ -633,8 +639,17 @@ trait Applications extends Compatibility {
case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined))
case _ => false
}
isCompatible(argtpe, formal) || ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
}
if argMatch == ArgMatch.SubType then
argtpe relaxed_<:< formal.widenExpr
else
isCompatible(argtpe, formal)
|| ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
|| argMatch == ArgMatch.CompatibleCAP
&& {
val argtpe1 = argtpe.widen
val captured = captureWildcards(argtpe1)
(captured ne argtpe1) && isCompatible(captured, formal.widenExpr)
}

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

/** Subclass of Application for applicability tests with type arguments and value
* argument trees.
*/
class ApplicableToTreesDirectly(methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
extends ApplicableToTrees(methRef, args, resultType) {
override def argOK(arg: TypedArg, formal: Type): Boolean =
argType(arg, formal) relaxed_<:< formal.widenExpr
}

/** Subclass of Application for applicability tests with value argument types. */
class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type)(using Context)
extends TestApplication(methRef, methRef, args, resultType) {
class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type, argMatch: ArgMatch)(using Context)
extends TestApplication(methRef, methRef, args, resultType, argMatch) {
def argType(arg: Type, formal: Type): Type = arg
def treeToArg(arg: Tree): Type = arg.tpe
def isVarArg(arg: Type): Boolean = arg.isRepeatedParam
Expand Down Expand Up @@ -1323,38 +1329,32 @@ trait Applications extends Compatibility {
/** Is given method reference applicable to argument trees `args`?
* @param resultType The expected result type of the application
*/
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean)(using Context): Boolean = {
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean, argMatch: ArgMatch)(using Context): Boolean = {
def isApp(using Context): Boolean =
new ApplicableToTrees(methRef, args, resultType).success
new ApplicableToTrees(methRef, args, resultType, argMatch).success
if (keepConstraint) isApp else explore(isApp)
}

/** Is given method reference applicable to argument trees `args` without inferring views?
* @param resultType The expected result type of the application
*/
def isDirectlyApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type)(using Context): Boolean =
explore(new ApplicableToTreesDirectly(methRef, args, resultType).success)

/** Is given method reference applicable to argument types `args`?
* @param resultType The expected result type of the application
*/
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type)(using Context): Boolean =
explore(new ApplicableToTypes(methRef, args, resultType).success)
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type, argMatch: ArgMatch)(using Context): Boolean =
explore(new ApplicableToTypes(methRef, args, resultType, argMatch).success)

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

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

private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match {
Expand Down Expand Up @@ -1485,9 +1485,9 @@ trait Applications extends Compatibility {
|| {
if tp1.isVarArgsMethod then
tp2.isVarArgsMethod
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType)
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, ArgMatch.Compatible)
else
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType)
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, ArgMatch.Compatible)
}
case tp1: PolyType => // (2)
inContext(ctx.fresh.setExploreTyperState()) {
Expand Down Expand Up @@ -1695,7 +1695,7 @@ trait Applications extends Compatibility {
*/
def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match {
case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType)) =>
val conformingAlts = alts.filter(alt =>
val conformingAlts = alts.filterConserve(alt =>
(alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType)))
conformingAlts match {
case Nil => chosen
Expand Down Expand Up @@ -1796,7 +1796,7 @@ trait Applications extends Compatibility {
}

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

/** Normalization steps before checking arguments:
*
Expand Down Expand Up @@ -1865,11 +1865,11 @@ trait Applications extends Compatibility {

def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
val alts2 = alts.filterConserve(alt =>
isDirectlyApplicableMethodRef(alt, args, resultType)
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.SubType)
)
if (alts2.isEmpty && !ctx.isAfterTyper)
alts.filterConserve(alt =>
isApplicableMethodRef(alt, args, resultType, keepConstraint = false)
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP)
)
else
alts2
Expand All @@ -1890,7 +1890,7 @@ trait Applications extends Compatibility {
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)

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

case pt =>
val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false))
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
if (compat.isEmpty)
/*
* the case should not be moved to the enclosing match
Expand Down Expand Up @@ -1967,15 +1967,15 @@ trait Applications extends Compatibility {
skipParamClause(pt.typedArgs().tpes, Nil), resType)
case _ =>
// prefer alternatives that need no eta expansion
val noCurried = alts.filter(!resultIsMethod(_))
val noCurried = alts.filterConserve(!resultIsMethod(_))
val noCurriedCount = noCurried.length
if noCurriedCount == 1 then
noCurried
else if noCurriedCount > 1 && noCurriedCount < alts.length then
resolveOverloaded1(noCurried, pt)
else
// prefer alternatves that match without default parameters
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams)
val noDefaultsCount = noDefaults.length
if noDefaultsCount == 1 then
noDefaults
Expand Down
30 changes: 30 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,36 @@ object Inferencing {

propagate(accu(SimpleIdentityMap.empty, tp))
}

/** Replace every top-level occurrence of a wildcard type argument by
* a fresh skolem type. The skolem types are of the form $i.CAP, where
* $i is a skolem of type `scala.internal.TypeBox`, and `CAP` is its
* type member. See the documentation of `TypeBox` for a rationale why we do this.
*/
def captureWildcards(tp: Type)(using Context): Type = tp match {
case tp @ AppliedType(tycon, args) if tp.hasWildcardArg =>
tycon.typeParams match {
case tparams @ ((_: Symbol) :: _) =>
val boundss = tparams.map(_.paramInfo.substApprox(tparams.asInstanceOf[List[TypeSymbol]], args))
val args1 = args.zipWithConserve(boundss) { (arg, bounds) =>
arg match {
case TypeBounds(lo, hi) =>
val skolem = SkolemType(defn.TypeBoxClass.typeRef.appliedTo(lo | bounds.loBound, hi & bounds.hiBound))
TypeRef(skolem, defn.TypeBox_CAP)
case arg => arg
}
}
tp.derivedAppliedType(tycon, args1)
case _ =>
tp
}
case tp: AndOrType => tp.derivedAndOrType(captureWildcards(tp.tp1), captureWildcards(tp.tp2))
case tp: RefinedType => tp.derivedRefinedType(captureWildcards(tp.parent), tp.refinedName, tp.refinedInfo)
case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent))
case tp: LazyRef => captureWildcards(tp.ref)
case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot)
case _ => tp
}
}

trait Inferencing { this: Typer =>
Expand Down
30 changes: 0 additions & 30 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3563,36 +3563,6 @@ class Typer extends Namer
case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(pt)).withType(pt)
}

/** Replace every top-level occurrence of a wildcard type argument by
* a fresh skolem type. The skolem types are of the form $i.CAP, where
* $i is a skolem of type `scala.internal.TypeBox`, and `CAP` is its
* type member. See the documentation of `TypeBox` for a rationale why we do this.
*/
def captureWildcards(tp: Type)(using Context): Type = tp match {
case tp: AndOrType => tp.derivedAndOrType(captureWildcards(tp.tp1), captureWildcards(tp.tp2))
case tp: RefinedType => tp.derivedRefinedType(captureWildcards(tp.parent), tp.refinedName, tp.refinedInfo)
case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent))
case tp: LazyRef => captureWildcards(tp.ref)
case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot)
case tp @ AppliedType(tycon, args) if tp.hasWildcardArg =>
tycon.typeParams match {
case tparams @ ((_: Symbol) :: _) =>
val boundss = tparams.map(_.paramInfo.substApprox(tparams.asInstanceOf[List[TypeSymbol]], args))
val args1 = args.zipWithConserve(boundss) { (arg, bounds) =>
arg match {
case TypeBounds(lo, hi) =>
val skolem = SkolemType(defn.TypeBoxClass.typeRef.appliedTo(lo | bounds.loBound, hi & bounds.hiBound))
TypeRef(skolem, defn.TypeBox_CAP)
case arg => arg
}
}
tp.derivedAppliedType(tycon, args1)
case _ =>
tp
}
case _ => tp
}

def adaptToSubType(wtp: Type): Tree =
// try converting a constant to the target type
ConstFold(tree).tpe.widenTermRefExpr.normalized match
Expand Down
12 changes: 12 additions & 0 deletions tests/pos/i8955.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class One[A]{}
def test[A](a: Class[A]) = println(a)
def test[A](as: List[A]) = println(as)
def tost[A](a: Class[A]) = println(a)

@main def main() = {
val one: One[_] = new One()
test(one.getClass(): Class[?]) //this fails
val cls = one.getClass()
test(cls) //this is ok
tost(one.getClass()) //this is also ok
}