Skip to content

Commit e5f7272

Browse files
authored
Calm param autotupling for overloads (#21552)
When resolving method overloads, we look to apply the same parameter auto-tupling logic that we have in typedFunctionValue. But we only checked the function was unary without checking whether it was a tuple. So I reused the same precondition. Fixes #16108
2 parents 3097a84 + 9f90ad0 commit e5f7272

File tree

3 files changed

+64
-18
lines changed

3 files changed

+64
-18
lines changed

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

+22-8
Original file line numberDiff line numberDiff line change
@@ -2214,19 +2214,26 @@ trait Applications extends Compatibility {
22142214
case untpd.Function(args: List[untpd.ValDef] @unchecked, body) =>
22152215

22162216
// If ref refers to a method whose parameter at index `idx` is a function type,
2217-
// the arity of that function, otherise -1.
2218-
def paramCount(ref: TermRef) =
2217+
// the parameters of that function, otherwise Nil.
2218+
// We return Nil for both nilary functions and non-functions,
2219+
// because we won't be making tupled functions for nilary functions anyways,
2220+
// seeing as there is no Tuple0.
2221+
def params(ref: TermRef) =
22192222
val formals = ref.widen.firstParamTypes
22202223
if formals.length > idx then
22212224
formals(idx).dealias match
2222-
case defn.FunctionNOf(args, _, _) => args.length
2223-
case _ => -1
2224-
else -1
2225+
case defn.FunctionNOf(args, _, _) => args
2226+
case _ => Nil
2227+
else Nil
2228+
2229+
def isCorrectUnaryFunction(alt: TermRef): Boolean =
2230+
val formals = params(alt)
2231+
formals.length == 1 && ptIsCorrectProduct(formals.head, args)
22252232

22262233
val numArgs = args.length
2227-
if numArgs != 1
2228-
&& !alts.exists(paramCount(_) == numArgs)
2229-
&& alts.exists(paramCount(_) == 1)
2234+
if numArgs > 1
2235+
&& !alts.exists(params(_).lengthIs == numArgs)
2236+
&& alts.exists(isCorrectUnaryFunction)
22302237
then
22312238
desugar.makeTupledFunction(args, body, isGenericTuple = true)
22322239
// `isGenericTuple = true` is the safe choice here. It means the i'th tuple
@@ -2395,6 +2402,13 @@ trait Applications extends Compatibility {
23952402
}
23962403
end resolveOverloaded1
23972404

2405+
/** Is `formal` a product type which is elementwise compatible with `params`? */
2406+
def ptIsCorrectProduct(formal: Type, params: List[untpd.ValDef])(using Context): Boolean =
2407+
isFullyDefined(formal, ForceDegree.flipBottom)
2408+
&& defn.isProductSubType(formal)
2409+
&& tupleComponentTypes(formal).corresponds(params): (argType, param) =>
2410+
param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe
2411+
23982412
/** The largest suffix of `paramss` that has the same first parameter name as `t`,
23992413
* plus the number of term parameters in `paramss` that come before that suffix.
24002414
*/

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

+1-10
Original file line numberDiff line numberDiff line change
@@ -1837,19 +1837,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18371837
if (protoFormals.length == params.length) (protoFormals(i), isDefinedErased(i))
18381838
else (errorType(WrongNumberOfParameters(tree, params.length, pt, protoFormals.length), tree.srcPos), false)
18391839

1840-
/** Is `formal` a product type which is elementwise compatible with `params`? */
1841-
def ptIsCorrectProduct(formal: Type) =
1842-
isFullyDefined(formal, ForceDegree.flipBottom) &&
1843-
defn.isProductSubType(formal) &&
1844-
tupleComponentTypes(formal).corresponds(params) {
1845-
(argType, param) =>
1846-
param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe
1847-
}
1848-
18491840
var desugared: untpd.Tree = EmptyTree
18501841
if protoFormals.length == 1 && params.length != 1 then
18511842
val firstFormal = protoFormals.head.loBound
1852-
if ptIsCorrectProduct(firstFormal) then
1843+
if ptIsCorrectProduct(firstFormal, params) then
18531844
val isGenericTuple =
18541845
firstFormal.derivesFrom(defn.TupleClass)
18551846
&& !defn.isTupleClass(firstFormal.typeSymbol)

tests/run/i16108.scala

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import scala.language.implicitConversions
2+
3+
final class Functoid[+R](val function: Product => R)
4+
5+
object Functoid {
6+
implicit def apply[A, R](function: A => R): Functoid[R] = {
7+
println(s"arity 1")
8+
new Functoid({ case Tuple1(a: A @unchecked) => function(a) })
9+
}
10+
implicit def apply[A, B, R](function: (A, B) => R): Functoid[R] = {
11+
println("arity 2")
12+
new Functoid({ case (a: A @unchecked, b: B @unchecked) => function(a, b) })
13+
}
14+
}
15+
16+
final case class ContainerConfig(image: String, version: Int, cmd: String)
17+
18+
final class ContainerResource
19+
20+
object ContainerResource {
21+
implicit final class DockerProviderExtensions(private val self: Functoid[ContainerResource]) extends AnyVal {
22+
def modifyConfig(modify: Functoid[ContainerConfig => ContainerConfig]): Functoid[ContainerConfig => ContainerConfig] = modify
23+
// removing this overload fixes the implicit conversion and returns `arity 2` print
24+
def modifyConfig(modify: ContainerConfig => ContainerConfig): Functoid[ContainerConfig => ContainerConfig] = new Functoid(_ => modify)
25+
}
26+
}
27+
28+
object Test {
29+
def main(args: Array[String]): Unit = {
30+
val cfg = new Functoid(_ => new ContainerResource)
31+
.modifyConfig {
32+
// applying Functoid.apply explicitly instead of via implicit conversion also avoids untupling
33+
// Functoid {
34+
(image: String, version: Int) => (cfg: ContainerConfig) => cfg.copy(image, version)
35+
// }
36+
}
37+
.function.apply(Tuple2("img", 9))
38+
.apply(ContainerConfig("a", 0, "b"))
39+
println(cfg)
40+
}
41+
}

0 commit comments

Comments
 (0)