diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2df78186c057..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)(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,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 @@ -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) @@ -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 @@ -1323,30 +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)(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`? @@ -1354,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) + isApplicableMethodRef(_, args, resultType, ArgMatch.Compatible) } private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match { @@ -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()) { @@ -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 @@ -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: * @@ -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 @@ -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 @@ -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 @@ -1967,7 +1967,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 +1975,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/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 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 +}