From b08133b3702bc122f699058c17703f218aa7b192 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Mar 2021 18:31:07 +0100 Subject: [PATCH 1/3] 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. --- .../dotty/tools/dotc/typer/Applications.scala | 54 +++++++++++-------- .../dotty/tools/dotc/typer/Inferencing.scala | 30 +++++++++++ tests/pos/i8955.scala | 12 +++++ 3 files changed, 73 insertions(+), 23 deletions(-) create mode 100644 tests/pos/i8955.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2df78186c057..05bbeb301c47 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -616,7 +616,7 @@ trait Applications extends Compatibility { * 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, captureWild: Boolean)(using Context) extends Application[Arg](methRef, funType, args, resultType) { type TypedArg = Arg type Result = Unit @@ -633,7 +633,14 @@ 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 + isCompatible(argtpe, formal) + || ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK + || captureWild + && { + val argtpe1 = argtpe.widen + val captured = captureWildcards(argtpe1) + (captured ne argtpe1) && (captured <:< formal.widenExpr) + } } /** The type of the given argument */ @@ -654,8 +661,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, captureWild: Boolean)(using Context) + extends TestApplication(methRef, methRef.widen, args, resultType, captureWild) { def argType(arg: Tree, formal: Type): Type = if untpd.isContextualClosure(arg) && defn.isContextFunctionType(formal) then arg.tpe else normalize(arg.tpe, formal) @@ -669,14 +676,14 @@ trait Applications extends Compatibility { * argument trees. */ class ApplicableToTreesDirectly(methRef: TermRef, args: List[Tree], resultType: Type)(using Context) - extends ApplicableToTrees(methRef, args, resultType) { + extends ApplicableToTrees(methRef, args, resultType, captureWild = false) { 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, captureWild: Boolean)(using Context) + extends TestApplication(methRef, methRef, args, resultType, captureWild) { def argType(arg: Type, formal: Type): Type = arg def treeToArg(arg: Tree): Type = arg.tpe def isVarArg(arg: Type): Boolean = arg.isRepeatedParam @@ -1323,13 +1330,14 @@ 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, captureWild: Boolean)(using Context): Boolean = { def isApp(using Context): Boolean = - new ApplicableToTrees(methRef, args, resultType).success + new ApplicableToTrees(methRef, args, resultType, captureWild).success if (keepConstraint) isApp else explore(isApp) } - /** Is given method reference applicable to argument trees `args` without inferring views? + /** Is given method reference applicable to argument trees `args` without inferring views + * or capturing wildcards? * @param resultType The expected result type of the application */ def isDirectlyApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type)(using Context): Boolean = @@ -1338,15 +1346,15 @@ trait Applications extends Compatibility { /** 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, captureWild: Boolean)(using Context): Boolean = + explore(new ApplicableToTypes(methRef, args, resultType, captureWild).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, captureWild = false) } /** Is given type applicable to argument types `args`, possibly after inserting an `apply`? @@ -1354,7 +1362,7 @@ trait Applications extends Compatibility { */ def isApplicableType(tp: Type, args: List[Type], resultType: Type)(using Context): Boolean = onMethod(tp, args.nonEmpty) { - isApplicableMethodRef(_, args, resultType) + isApplicableMethodRef(_, args, resultType, captureWild = false) } private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match { @@ -1485,9 +1493,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, captureWild = false) else - isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType) + isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, captureWild = false) } case tp1: PolyType => // (2) inContext(ctx.fresh.setExploreTyperState()) { @@ -1695,7 +1703,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 @@ -1796,7 +1804,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, captureWild = true)) /** Normalization steps before checking arguments: * @@ -1869,7 +1877,7 @@ trait Applications extends Compatibility { ) if (alts2.isEmpty && !ctx.isAfterTyper) alts.filterConserve(alt => - isApplicableMethodRef(alt, args, resultType, keepConstraint = false) + isApplicableMethodRef(alt, args, resultType, keepConstraint = false, captureWild = true) ) else alts2 @@ -1890,7 +1898,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 @@ -1904,7 +1912,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 @@ -1967,7 +1975,7 @@ 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 @@ -1975,7 +1983,7 @@ trait Applications extends Compatibility { 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 diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 277daf799065..18a181c6667a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -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 => diff --git a/tests/pos/i8955.scala b/tests/pos/i8955.scala new file mode 100644 index 000000000000..d9f0a1ee9f73 --- /dev/null +++ b/tests/pos/i8955.scala @@ -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 +} From 9f5878dd6ce9001288f1e845d8178467b951a061 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Mar 2021 18:33:06 +0100 Subject: [PATCH 2/3] Drop duplicated method --- .../src/dotty/tools/dotc/typer/Typer.scala | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 579f90e0522c..e0cb2829273d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 From 67491d643a9a56c22dd601e3c9584418a86ca324 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Mar 2021 18:53:28 +0100 Subject: [PATCH 3/3] Make applicability degrees more systematic --- .../dotty/tools/dotc/typer/Applications.scala | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 05bbeb301c47..48a5738b4074 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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, captureWild: Boolean)(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 @@ -633,15 +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 - || captureWild - && { - val argtpe1 = argtpe.widen - val captured = captureWildcards(argtpe1) - (captured ne argtpe1) && (captured <:< formal.widenExpr) - } - } + 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 @@ -661,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, captureWild: Boolean)(using Context) - extends TestApplication(methRef, methRef.widen, args, resultType, captureWild) { + 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) @@ -672,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, captureWild = false) { - 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, captureWild: Boolean)(using Context) - extends TestApplication(methRef, methRef, args, resultType, captureWild) { + 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 @@ -1330,31 +1329,24 @@ 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, captureWild: 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, captureWild).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 - * or capturing wildcards? - * @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, captureWild: Boolean)(using Context): Boolean = - explore(new ApplicableToTypes(methRef, args, resultType, captureWild).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, captureWild = false) + isApplicableMethodRef(_, args, resultType, keepConstraint, ArgMatch.Compatible) } /** Is given type applicable to argument types `args`, possibly after inserting an `apply`? @@ -1362,7 +1354,7 @@ trait Applications extends Compatibility { */ def isApplicableType(tp: Type, args: List[Type], resultType: Type)(using Context): Boolean = onMethod(tp, args.nonEmpty) { - isApplicableMethodRef(_, args, resultType, captureWild = false) + isApplicableMethodRef(_, args, resultType, ArgMatch.Compatible) } private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match { @@ -1493,9 +1485,9 @@ trait Applications extends Compatibility { || { if tp1.isVarArgsMethod then tp2.isVarArgsMethod - && isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, captureWild = false) + && isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, ArgMatch.Compatible) else - isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, captureWild = false) + isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, ArgMatch.Compatible) } case tp1: PolyType => // (2) inContext(ctx.fresh.setExploreTyperState()) { @@ -1804,7 +1796,7 @@ trait Applications extends Compatibility { } def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = - alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, captureWild = true)) + alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP)) /** Normalization steps before checking arguments: * @@ -1873,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, captureWild = true) + isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP) ) else alts2