diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fdefc14aadd6..4e7761cff98a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1908,7 +1908,7 @@ object Trees { case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod case _ => true }} - val alternatives = ctx.typer.resolveOverloaded(allAlts, proto) + val alternatives = ctx.typer.resolveOverloaded(allAlts, proto, receiver.srcPos) assert(alternatives.size == 1, i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " + i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, %; expectedType: $expectedType." + diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98bfbe69ff8c..ebb3d9efc43b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2112,12 +2112,71 @@ trait Applications extends Compatibility { } } + /** Overloadoing Priority Schemes, changing in `scala-3.7`. + * + * Old Scheme, until 3.7: + * - First, determines the _candidates_ (subset of the alternatives) by: + * + looking at the first paramater clause + * - Second, determine the best candidate by: + * + doing narrowMostSpecific on the first argument list; + * + if still ambigous and there is another parameter clause, then + * restarts narrowMostSpecific by mapping the problem onto the next parameter clause, + * but still considering all candidates. + * + * New Scheme, from 3.7 + * - First, determines the candidates by: + * + looking at first paramater clause; + * + if still no determined and there is another parameter clause, then + * looks at the next argument lists to narrow the candidates (not restarting from alts). + * If that finds no alternative is applicable, fallback to the previous iteration + * (see comment in narrowByNextParamClause for details). + * - Second, determine the best candidate by, restoring all parameter clauses, and: + * + doing narrowMostSpecific on the first argument list; + * + if still ambigous and there is another parameter clause, then + * continue narrowMostSpecific by mapping the problem onto the next parameter clause, + * but only considering the alternatives found until now. + */ + enum ResolveScheme: + case Old + case New + def isNewPriority: Boolean = this == New + + /** Resolve overloaded alternative `alts`, given expected type `pt`. Determines the current + * priority scheme and emits warnings for changes in resolution in migration version. + */ + def resolveOverloaded(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + record("resolveOverloaded") + lazy val oldRes = resolveOverloaded(resolveOverloaded1(ResolveScheme.Old))(alts, pt) + lazy val newRes = resolveOverloaded(resolveOverloaded1(ResolveScheme.New))(alts, pt) + + val sv = Feature.sourceVersion + val isNewPriorityVersion = sv.isAtLeast(SourceVersion.`3.7`) + val isWarnPriorityChangeVersion = sv == SourceVersion.`3.7-migration` + + def doWarn(oldChoice: String, newChoice: String): Unit = report.warning( + em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives + | ${alts map (_.info)}%\n % + |has changed. + |Previous choice : $oldChoice + |New choice from Scala 3.7: $newChoice""", srcPos) + + if isWarnPriorityChangeVersion then (oldRes, newRes) match + case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show) + case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none") + case (Nil, newAlt :: Nil) => doWarn("none", newAlt.info.show) + case _ => // neither scheme has determined an alternative + + if isNewPriorityVersion then newRes else oldRes + end resolveOverloaded + /** Resolve overloaded alternative `alts`, given expected type `pt`. * Two trials: First, without implicits or SAM conversions enabled. Then, * if the first finds no eligible candidates, with implicits and SAM conversions enabled. + * Each trial applies the `resolve` parameter. */ - def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = - record("resolveOverloaded") + def resolveOverloaded + (resolve: (List[TermRef], Type) => Context ?=> List[TermRef]) + (alts: List[TermRef], pt: Type)(using Context): List[TermRef] = /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` @@ -2154,7 +2213,7 @@ trait Applications extends Compatibility { case Nil => chosen case alt2 :: Nil => alt2 case alts2 => - resolveOverloaded(alts2, pt) match { + resolveOverloaded(resolve)(alts2, pt) match { case alt2 :: Nil => alt2 case _ => chosen } @@ -2162,23 +2221,23 @@ trait Applications extends Compatibility { case _ => chosen } - def resolve(alts: List[TermRef]): List[TermRef] = + def resolve1(alts: List[TermRef]): List[TermRef] = pt match case pt: FunProto => if pt.applyKind == ApplyKind.Using then val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) - if alts0 ne alts then return resolve(alts0) + if alts0 ne alts then return resolve1(alts0) else if alts.exists(_.widen.stripPoly.isContextualMethod) then - return resolveMapped(alts, alt => stripImplicit(alt.widen), pt) + return resolveMapped(alt => stripImplicit(alt.widen), resolve)(alts, pt) case _ => - var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt)) + var found = withoutMode(Mode.ImplicitsEnabled)(resolve(alts, pt)) if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then - found = resolveOverloaded1(alts, pt) + found = resolve(alts, pt) found match case alt :: Nil => adaptByResult(alt, alts) :: Nil case _ => found - end resolve + end resolve1 /** Try an apply method, if * - the result is applied to value arguments and alternative is not a method, or @@ -2211,9 +2270,9 @@ trait Applications extends Compatibility { if (alts.exists(tryApply)) { val expanded = alts.flatMap(applyMembers) - resolve(expanded).map(retract) + resolve1(expanded).map(retract) } - else resolve(alts) + else resolve1(alts) end resolveOverloaded /** This private version of `resolveOverloaded` does the bulk of the work of @@ -2221,11 +2280,13 @@ trait Applications extends Compatibility { * It might be called twice from the public `resolveOverloaded` method, once with * implicits and SAM conversions enabled, and once without. */ - private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = - trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) { + private def resolveOverloaded1(scheme: ResolveScheme)(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + trace(i"resolve over $alts%, %, pt = $pt", typr, show = true): record(s"resolveOverloaded1", alts.length) - def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty + extension (self: List[TermRef]) + def isDetermined: Boolean = self.isEmpty || self.tail.isEmpty + inline def fallbackTo(inline alts: List[TermRef]) = if self.nonEmpty then self else alts /** The shape of given tree as a type; cannot handle named arguments. */ def typeShape(tree: untpd.Tree): Type = tree match { @@ -2253,6 +2314,48 @@ trait Applications extends Compatibility { def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP)) + def narrowByNextParamClause + (resolve: (List[TermRef], Type) => Context ?=> List[TermRef]) + (alts: List[TermRef], args: List[Tree], resultType: FunOrPolyProto): List[TermRef] = + + /** The type of alternative `alt` after instantiating its first parameter + * clause with `argTypes`. In addition, if the resulting type is a PolyType + * and `typeArgs` matches its parameter list, instantiate the result with `typeArgs`. + */ + def skipParamClause(typeArgs: List[Type])(alt: TermRef): Type = + def skip(tp: Type): Type = tp match + case tp: PolyType => + skip(tp.resultType) match + case NoType => + NoType + case rt: PolyType if typeArgs.length == rt.paramInfos.length => + tp.derivedLambdaType(resType = rt.instantiate(typeArgs)) + case rt => + tp.derivedLambdaType(resType = rt).asInstanceOf[PolyType].flatten + case tp: MethodType => + tp.instantiate(args.tpes) + case _ => + NoType + skip(alt.widen) + + resultType match + case PolyProto(targs, resType) => + // try to narrow further with snd argument list and following type params + resolveMapped(skipParamClause(targs.tpes), resolve)(alts, resType) + case resType => + // try to narrow further with snd argument list + resolveMapped(skipParamClause(Nil), resolve)(alts, resType) + + // Note that `resolvedMapped` applies `resolveOverloaded(resolve)`, _not_ `resolve` directly. + // This benefits from the insertion of implicit parameters, apply methods, etc. + // But there are still some adaptations, e.g. auto-tupling, that are not performed at this stage. + // In those cases, it is possible that we find that no alternatives are applicable. + // So, in resolveCandidates, we fallback to the `alts` we had before considering the next parameter clause. + // Resolution will succeed (only) if narrowMostSpecific finds an unambiguous alternative + // by considering (only) the prior argument lists, after which adaptation can be performed. + // See tests/run/tupled-function-extension-method.scala for an example. + end narrowByNextParamClause + /** Normalization steps before checking arguments: * * { expr } --> expr @@ -2309,7 +2412,10 @@ trait Applications extends Compatibility { case _ => arg end normArg - val candidates = pt match { + // Note it is important not to capture the outer ctx, for when it is passed to resolveMapped + // (through narrowByNextParamClause) which retracts the Mode.SynthesizeExtMethodReceiver from the ctx + // before continuing to `resolveCandidates`. + def resolveCandidates(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = pt match case pt @ FunProto(args, resultType) => val numArgs = args.length def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { @@ -2361,7 +2467,14 @@ trait Applications extends Compatibility { else record("resolveOverloaded.narrowedByShape", alts2.length) pretypeArgs(alts2, pt) - narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType) + val alts3 = narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType) + + resultType.deepenProto match + case resultType: FunOrPolyProto if scheme.isNewPriority => + narrowByNextParamClause(resolveCandidates)(alts3, pt.typedArgs(), resultType) + .fallbackTo(alts3) // see comment in narrowByNextParamClause + case _ => + alts3 case pt @ PolyProto(targs1, pt1) => val alts1 = alts.filterConserve(pt.canInstantiate) @@ -2372,7 +2485,7 @@ trait Applications extends Compatibility { TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty val alts2 = alts1.filter(withinBounds) if isDetermined(alts2) then alts2 - else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1) + else resolveMapped(_.widen.appliedTo(targs1.tpes), resolveCandidates)(alts1, pt1) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) @@ -2396,74 +2509,53 @@ trait Applications extends Compatibility { normalize(alt, IgnoredProto(pt)).widenSingleton.isInstanceOf[MethodType]) if convertible.length == 1 then convertible else compat else compat - } - - /** The type of alternative `alt` after instantiating its first parameter - * clause with `argTypes`. In addition, if the resulting type is a PolyType - * and `typeArgs` matches its parameter list, instantiate the result with `typeArgs`. - */ - def skipParamClause(argTypes: List[Type], typeArgs: List[Type])(alt: TermRef): Type = - def skip(tp: Type): Type = tp match { - case tp: PolyType => - skip(tp.resultType) match - case NoType => - NoType - case rt: PolyType if typeArgs.length == rt.paramInfos.length => - tp.derivedLambdaType(resType = rt.instantiate(typeArgs)) - case rt => - tp.derivedLambdaType(resType = rt).asInstanceOf[PolyType].flatten - case tp: MethodType => - tp.instantiate(argTypes) - case _ => - NoType - } - skip(alt.widen) + end resolveCandidates def resultIsMethod(tp: Type): Boolean = tp.widen.stripPoly match case tp: MethodType => stripInferrable(tp.resultType).isInstanceOf[MethodType] case _ => false - record("resolveOverloaded.narrowedApplicable", candidates.length) - if pt.unusableForInference then - // `pt` might have become erroneous by typing arguments of FunProtos. - // If `pt` is erroneous, don't try to go further; report the error in `pt` instead. - candidates - else - val found = narrowMostSpecific(candidates) - if found.length <= 1 then found + // Like resolveCandidates, we should not capture the outer ctx parameter. + def resolveOverloaded2(candidates: List[TermRef], pt: Type)(using Context): List[TermRef] = + if pt.unusableForInference then + // `pt` might have become erroneous by typing arguments of FunProtos. + // If `pt` is erroneous, don't try to go further; report the error in `pt` instead. + candidates else - val deepPt = pt.deepenProto - deepPt match - case pt @ FunProto(_, PolyProto(targs, resType)) => - // try to narrow further with snd argument list and following type params - resolveMapped(candidates, - skipParamClause(pt.typedArgs().tpes, targs.tpes), resType) - case pt @ FunProto(_, resType: FunOrPolyProto) => - // try to narrow further with snd argument list - resolveMapped(candidates, - skipParamClause(pt.typedArgs().tpes, Nil), resType) - case _ => - // prefer alternatives that need no eta expansion - 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.filterConserve(!_.symbol.hasDefaultParams) - val noDefaultsCount = noDefaults.length - if noDefaultsCount == 1 then - noDefaults - else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded1(noDefaults, pt) - else if deepPt ne pt then - // try again with a deeper known expected type - resolveOverloaded1(alts, deepPt) + val found = narrowMostSpecific(candidates) + if isDetermined(found) then found + else + val deepPt = pt.deepenProto + deepPt match + case pt @ FunProto(_, resType: FunOrPolyProto) => + val alts1 = if scheme.isNewPriority then found else candidates + narrowByNextParamClause(resolveOverloaded1(scheme))(alts1, pt.typedArgs(), resType) + case _ => + // prefer alternatives that need no eta expansion + val noCurried = alts.filterConserve(!resultIsMethod(_)) + val noCurriedCount = noCurried.length + if noCurriedCount == 1 then + noCurried + else if noCurriedCount > 1 && noCurriedCount < alts.length then + resolveOverloaded1(scheme)(noCurried, pt) else - candidates - } + // prefer alternatves that match without default parameters + val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) + val noDefaultsCount = noDefaults.length + if noDefaultsCount == 1 then + noDefaults + else if noDefaultsCount > 1 && noDefaultsCount < alts.length then + resolveOverloaded1(scheme)(noDefaults, pt) + else if deepPt ne pt then + // try again with a deeper known expected type + resolveOverloaded1(scheme)(alts, deepPt) + else + candidates + end resolveOverloaded2 + + val candidates = resolveCandidates(alts, pt) + record("resolveOverloaded.narrowedApplicable", candidates.length) + resolveOverloaded2(candidates, pt) end resolveOverloaded1 /** Is `formal` a product type which is elementwise compatible with `params`? */ @@ -2490,11 +2582,13 @@ trait Applications extends Compatibility { recur(paramss, 0) case _ => (Nil, 0) - /** Resolve overloading by mapping to a different problem where each alternative's - * type is mapped with `f`, alternatives with non-existing types or symbols are dropped, and the - * expected type is `pt`. Map the results back to the original alternatives. + /** ResolveOverloaded using `resolve` by mapping to a different problem where each alternative's + * type is mapped with `f`, alternatives with non-existing types or symbols are dropped, + * and the expected type is `pt`. Map the results back to the original alternatives. */ - def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type)(using Context): List[TermRef] = + def resolveMapped + (f: TermRef => Type, resolve: (List[TermRef], Type) => Context ?=> List[TermRef]) + (alts: List[TermRef], pt: Type)(using Context): List[TermRef] = val reverseMapping = alts.flatMap { alt => val t = f(alt) if t.exists && alt.symbol.exists then @@ -2517,8 +2611,9 @@ trait Applications extends Compatibility { } val mapped = reverseMapping.map(_._1) overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt") - resolveOverloaded(mapped, pt)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) + resolveOverloaded(resolve)(mapped, pt)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) .map(reverseMapping.toMap) + end resolveMapped /** Try to typecheck any arguments in `pt` that are function values missing a * parameter type. If the formal parameter types corresponding to a closure argument diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 85f44ead5f28..ab0807284d18 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -375,7 +375,7 @@ object ProtoTypes { * [](args): resultType * * @param args The untyped arguments to which the function is applied - * @param resType The expeected result type + * @param resType The expected result type * @param typer The typer to use for typing the arguments * @param applyKind The kind of application (regular/using/tupled infix operand) * @param state The state object to use for tracking the changes to this prototype diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8ba63dfc1e67..eb26f58358b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4108,7 +4108,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt) val alts = altDenots.map(altRef) - resolveOverloaded(alts, pt) match + resolveOverloaded(alts, pt, tree.srcPos) match case alt :: Nil => readaptSimplified(tree.withType(alt)) case Nil => diff --git a/compiler/test/dotc/pos-test-pickling.excludelist b/compiler/test/dotc/pos-test-pickling.excludelist index 23c79affada0..960d3431d8ea 100644 --- a/compiler/test/dotc/pos-test-pickling.excludelist +++ b/compiler/test/dotc/pos-test-pickling.excludelist @@ -126,6 +126,7 @@ i7445b.scala i15525.scala i19955a.scala i19955b.scala +i20053.scala i20053b.scala # alias types at different levels of dereferencing diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check index 325cdccc6aab..45e2e7f763dd 100644 --- a/tests/neg/i10901.check +++ b/tests/neg/i10901.check @@ -1,39 +1,3 @@ --- [E008] Not Found Error: tests/neg/i10901.scala:45:38 ---------------------------------------------------------------- -45 | val pos1: Point2D[Int,Double] = x º y // error - | ^^^ - | value º is not a member of object BugExp4Point2D.IntT. - | An extension method was tried, but could not be fully constructed: - | - | º(x) - | - | failed with: - | - | Ambiguous overload. The overloaded alternatives of method º in object dsl with types - | [T1, T2] - | (x: BugExp4Point2D.ColumnType[T1]) - | (y: BugExp4Point2D.ColumnType[T2]) - | (using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | [T1, T2] - | (x: T1) - | (y: BugExp4Point2D.ColumnType[T2]) - | (using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | both match arguments ((x : BugExp4Point2D.IntT.type))((y : BugExp4Point2D.DoubleT.type)) --- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ---------------------------------------------------------------- -48 | val pos4: Point2D[Int,Double] = x º 201.1 // error - | ^^^ - |value º is not a member of object BugExp4Point2D.IntT. - |An extension method was tried, but could not be fully constructed: - | - | º(x) - | - | failed with: - | - | Ambiguous overload. The overloaded alternatives of method º in object dsl with types - | [T1, T2] - | (x: BugExp4Point2D.ColumnType[T1]) - | (y: T2)(using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | [T1, T2](x: T1)(y: T2)(using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | both match arguments ((x : BugExp4Point2D.IntT.type))((201.1d : Double)) -- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ---------------------------------------------------------------- 62 | val y = "abc".foo // error | ^^^^^^^^^ diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala index 996a0753c2e7..da9b09916268 100644 --- a/tests/neg/i10901.scala +++ b/tests/neg/i10901.scala @@ -42,10 +42,10 @@ object BugExp4Point2D { val x = IntT val y = DoubleT - val pos1: Point2D[Int,Double] = x º y // error + val pos1: Point2D[Int,Double] = x º y // ok val pos2: Point2D[Int,Double] = 100 º 200.1 // ok val pos3: Point2D[Int,Double] = 101 º y // ok - val pos4: Point2D[Int,Double] = x º 201.1 // error + val pos4: Point2D[Int,Double] = x º 201.1 // ok } } diff --git a/tests/neg/multiparamlist-overload-3.6.check b/tests/neg/multiparamlist-overload-3.6.check new file mode 100755 index 000000000000..9ad8f1a9a78d --- /dev/null +++ b/tests/neg/multiparamlist-overload-3.6.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.6.scala:41:21 --------------------------------------- +41 | val r = f(new B)(new A) // error: resolves to: R2 in 3.6, R1 in 3.7 + | ^^^^^ + | Found: A + | Required: B + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/multiparamlist-overload-3.6.scala b/tests/neg/multiparamlist-overload-3.6.scala new file mode 100755 index 000000000000..b94a2ae55a21 --- /dev/null +++ b/tests/neg/multiparamlist-overload-3.6.scala @@ -0,0 +1,44 @@ +import scala.language.`3.6` + +class A +class B extends A +class C extends B + +class R1 +class R2 +class R3 + +// In each test, the alternatives are defined from most genereal to most specific, +// with respect to a lexicographic ordering by parameter list. +// In each of the cases tested here, scala-3.7 resolves to the most specific applicable alternative. +// See tests/pos/multiparamlist-overload-3.7.scala for the comparison. + +object Test1: + def f(x: A)(y: C) = new R1 + def f(x: B)(y: A) = new R2 + def f(x: B)(y: B) = new R3 + + val r = f(new B)(new C) // all alternatives are applicable; resolves to: R1 in 3.6, R3 in 3.7 + val _: R1 = r +end Test1 + +object Test2: + // R1 is the only applicable alternative in both parts + // but (in 3.7) it is only resolved to in Part1 by adding (an unapplicable) R3 + + object Part1: + def f(x: A)(y: A) = new R1 + def f(x: B)(y: B) = new R2 + def f(x: B)(y: C) = new R3 + + val r = f(new B)(new A) // resolves to: R1 in 3.6, R1 in 3.7 + val _: R1 = r + + object Part2: + def f(x: A)(y: A) = new R1 + def f(x: B)(y: B) = new R2 + + val r = f(new B)(new A) // error: resolves to: R2 in 3.6, R1 in 3.7 + val _: R2 = r + +end Test2 diff --git a/tests/pos/i20053.scala b/tests/pos/i20053.scala new file mode 100644 index 000000000000..bae639c2a83c --- /dev/null +++ b/tests/pos/i20053.scala @@ -0,0 +1,73 @@ + +trait Summon[R, T <: R]: + type Out +object Summon: + given [R, T <: R]: Summon[R, T] with + type Out = R + +sealed class Modifier[+A, +P] +type ModifierAny = Modifier[Any, Any] +sealed trait ISCONST[T <: Boolean] +type CONST = ISCONST[true] + +trait DFTypeAny +trait DFBits[W <: Int] extends DFTypeAny +trait DFVal[+T <: DFTypeAny, +M <: ModifierAny] +type DFValAny = DFVal[DFTypeAny, ModifierAny] +type DFValTP[+T <: DFTypeAny, +P] = DFVal[T, Modifier[Any, P]] +type DFConstOf[+T <: DFTypeAny] = DFVal[T, Modifier[Any, CONST]] + +trait Candidate[R]: + type OutW <: Int + type OutP +object Candidate: + given [W <: Int, P, R <: DFValTP[DFBits[W], P]]: Candidate[R] with + type OutW = W + type OutP = P + +extension [L <: DFValAny](lhs: L)(using icL: Candidate[L]) + def ^^^[R](rhs: R)(using + icR: Candidate[R] + ): DFValTP[DFBits[icL.OutW], icL.OutP | icR.OutP] = ??? + def ^^^ : Unit = ??? +extension [L](lhs: L) + def ^^^[RW <: Int, RP]( + rhs: DFValTP[DFBits[RW], RP] + )(using es: Summon[L, lhs.type])(using + c: Candidate[L] + )(using check: c.OutW =:= c.OutW): DFValTP[DFBits[c.OutW], c.OutP | RP] = ??? + +val x: DFConstOf[DFBits[8]] = ??? +val zzz = x ^^^ x ^^^ x + + +object Minimized: + + trait Sub[T, R >: T] + given [T, R >: T]: Sub[T, R] with {} + + trait Candidate[-R]: + type OutP + given [P]: Candidate[Option[P]] with + type OutP = P + + extension [L <: Option[Any]](lhs: L)(using icL: Candidate[L]) + def ^^^[R](rhs: R)(using icR: Candidate[R]): Option[icL.OutP | icR.OutP] = ??? + def ^^^ : Unit = ??? + + extension [L](lhs: L) + def ^^^[R](rhs: Option[R]) + (using es: Sub[lhs.type, L]) + (using c: Candidate[L]) + (using check: c.OutP =:= c.OutP): Option[c.OutP | R] = ??? + + val x: Option[true] = ??? + val z1 = x ^^^ x // Ok + val z2 = z1 ^^^ x // Ok + val zzz = x ^^^ x ^^^ x // Error before changes + + /* Before the changes, when `def ^^^ : Unit = ???` is present, + * all of z1, z2, zzz attempt to use the last `def ^^^`, + * despite it being less specific than the 1st one. + */ +end Minimized diff --git a/tests/pos/scalatest-overload-3.6.scala b/tests/pos/scalatest-overload-3.6.scala new file mode 100644 index 000000000000..9e3bf343867c --- /dev/null +++ b/tests/pos/scalatest-overload-3.6.scala @@ -0,0 +1,22 @@ +import scala.language.`3.6` + +class TestBody1 +class TestBody2 + +class StartWithWord +class EndWithWord + +class Matchers: + extension (leftSideString: String) + def should(body: TestBody1): Unit = () + def should(body: TestBody2): Unit = () + + extension [T](leftSideValue: T) + def should(word: StartWithWord)(using T <:< String): Unit = () + def should(word: EndWithWord)(using T <:< String): Unit = () + + def endWith(rightSideString: String): EndWithWord = new EndWithWord + +class Test extends Matchers: + def test(): Unit = + "hello world" should endWith ("world") // ok, error in 3.7 diff --git a/tests/pos/scalatest-overload-3.7.scala b/tests/pos/scalatest-overload-3.7.scala new file mode 100644 index 000000000000..9ee6188829e5 --- /dev/null +++ b/tests/pos/scalatest-overload-3.7.scala @@ -0,0 +1,23 @@ +import scala.language.`3.7` + +class TestBody1 +class TestBody2 + +class StartWithWord +class EndWithWord + +class Matchers: + extension (leftSideString: String) + def should(body: TestBody1): Unit = () + def should(body: TestBody2): Unit = () + + extension [T](leftSideValue: T) + def should(word: StartWithWord)(using T <:< String): Unit = () + def should(word: EndWithWord)(using T <:< String): Unit = () + + def endWith(rightSideString: String): EndWithWord = new EndWithWord + +class Test extends Matchers: + def test(): Unit = + "hello world" should endWith ("world") // no overloading resolution change + // related to tests/warn/multiparamlist-overload-3.7.scala diff --git a/tests/warn/multiparamlist-overload-3.7.check b/tests/warn/multiparamlist-overload-3.7.check new file mode 100755 index 000000000000..2e0f5a53e233 --- /dev/null +++ b/tests/warn/multiparamlist-overload-3.7.check @@ -0,0 +1,19 @@ +-- Warning: tests/warn/multiparamlist-overload-3.7.scala:21:10 --------------------------------------------------------- +21 | val r = f(new B)(new C) // warn: all alternatives are applicable; resolves to: R1 in 3.6, R3 in 3.7 + | ^ + | Overloading resolution for arguments (B)(C) between alternatives + | (x: B)(y: B): R3 + | (x: B)(y: A): R2 + | (x: A)(y: C): R1 + | has changed. + | Previous choice : (x: A)(y: C): R1 + | New choice from Scala 3.7: (x: B)(y: B): R3 +-- Warning: tests/warn/multiparamlist-overload-3.7.scala:41:12 --------------------------------------------------------- +41 | val r = f(new B)(new A) // warn: resolves to: R2 in 3.6, R1 in 3.7 + | ^ + | Overloading resolution for arguments (B)(A) between alternatives + | (x: B)(y: B): R2 + | (x: A)(y: A): R1 + | has changed. + | Previous choice : (x: B)(y: B): R2 + | New choice from Scala 3.7: (x: A)(y: A): R1 diff --git a/tests/warn/multiparamlist-overload-3.7.scala b/tests/warn/multiparamlist-overload-3.7.scala new file mode 100755 index 000000000000..8a1e8cafeb1b --- /dev/null +++ b/tests/warn/multiparamlist-overload-3.7.scala @@ -0,0 +1,44 @@ +import scala.language.`3.7-migration` + +class A +class B extends A +class C extends B + +class R1 +class R2 +class R3 + +// In each test, the alternatives are defined from most genereal to most specific, +// with respect to a lexicographic ordering by parameter list. +// In each of the cases tested here, scala-3.7 resolves to the most specific applicable alternative. +// See tests/neg/multiparamlist-overload-3.6.scala for the comparison. + +object Test1: + def f(x: A)(y: C) = new R1 + def f(x: B)(y: A) = new R2 + def f(x: B)(y: B) = new R3 + + val r = f(new B)(new C) // warn: all alternatives are applicable; resolves to: R1 in 3.6, R3 in 3.7 + val _: R3 = r +end Test1 + +object Test2: + // R1 is the only applicable alternative in both parts + // but (in 3.7) it is only resolved to in Part1 by adding (an unapplicable) R3 + + object Part1: + def f(x: A)(y: A) = new R1 + def f(x: B)(y: B) = new R2 + def f(x: B)(y: C) = new R3 + + val r = f(new B)(new A) // resolves to: R1 in 3.6, R1 in 3.7 + val _: R1 = r + + object Part2: + def f(x: A)(y: A) = new R1 + def f(x: B)(y: B) = new R2 + + val r = f(new B)(new A) // warn: resolves to: R2 in 3.6, R1 in 3.7 + val _: R1 = r + +end Test2