Skip to content

Commit d2c3d75

Browse files
dwijnandtgodzik
authored andcommitted
Calm param autotupling for overloads
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. [Cherry-picked 9f90ad0]
1 parent e04ea4a commit d2c3d75

File tree

3 files changed

+64
-18
lines changed

3 files changed

+64
-18
lines changed

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,19 +2065,26 @@ trait Applications extends Compatibility {
20652065
case untpd.Function(args: List[untpd.ValDef] @unchecked, body) =>
20662066

20672067
// If ref refers to a method whose parameter at index `idx` is a function type,
2068-
// the arity of that function, otherise -1.
2069-
def paramCount(ref: TermRef) =
2068+
// the parameters of that function, otherwise Nil.
2069+
// We return Nil for both nilary functions and non-functions,
2070+
// because we won't be making tupled functions for nilary functions anyways,
2071+
// seeing as there is no Tuple0.
2072+
def params(ref: TermRef) =
20702073
val formals = ref.widen.firstParamTypes
20712074
if formals.length > idx then
20722075
formals(idx).dealias match
2073-
case defn.FunctionNOf(args, _, _) => args.length
2074-
case _ => -1
2075-
else -1
2076+
case defn.FunctionNOf(args, _, _) => args
2077+
case _ => Nil
2078+
else Nil
2079+
2080+
def isCorrectUnaryFunction(alt: TermRef): Boolean =
2081+
val formals = params(alt)
2082+
formals.length == 1 && ptIsCorrectProduct(formals.head, args)
20762083

20772084
val numArgs = args.length
2078-
if numArgs != 1
2079-
&& !alts.exists(paramCount(_) == numArgs)
2080-
&& alts.exists(paramCount(_) == 1)
2085+
if numArgs > 1
2086+
&& !alts.exists(params(_).lengthIs == numArgs)
2087+
&& alts.exists(isCorrectUnaryFunction)
20812088
then
20822089
desugar.makeTupledFunction(args, body, isGenericTuple = true)
20832090
// `isGenericTuple = true` is the safe choice here. It means the i'th tuple
@@ -2246,6 +2253,13 @@ trait Applications extends Compatibility {
22462253
}
22472254
end resolveOverloaded1
22482255

2256+
/** Is `formal` a product type which is elementwise compatible with `params`? */
2257+
def ptIsCorrectProduct(formal: Type, params: List[untpd.ValDef])(using Context): Boolean =
2258+
isFullyDefined(formal, ForceDegree.flipBottom)
2259+
&& defn.isProductSubType(formal)
2260+
&& tupleComponentTypes(formal).corresponds(params): (argType, param) =>
2261+
param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe
2262+
22492263
/** The largest suffix of `paramss` that has the same first parameter name as `t`,
22502264
* plus the number of term parameters in `paramss` that come before that suffix.
22512265
*/

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,19 +1615,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
16151615
if (protoFormals.length == params.length) (protoFormals(i), isDefinedErased(i))
16161616
else (errorType(WrongNumberOfParameters(tree, params.length, pt, protoFormals.length), tree.srcPos), false)
16171617

1618-
/** Is `formal` a product type which is elementwise compatible with `params`? */
1619-
def ptIsCorrectProduct(formal: Type) =
1620-
isFullyDefined(formal, ForceDegree.flipBottom) &&
1621-
defn.isProductSubType(formal) &&
1622-
tupleComponentTypes(formal).corresponds(params) {
1623-
(argType, param) =>
1624-
param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe
1625-
}
1626-
16271618
var desugared: untpd.Tree = EmptyTree
16281619
if protoFormals.length == 1 && params.length != 1 then
16291620
val firstFormal = protoFormals.head.loBound
1630-
if ptIsCorrectProduct(firstFormal) then
1621+
if ptIsCorrectProduct(firstFormal, params) then
16311622
val isGenericTuple =
16321623
firstFormal.derivesFrom(defn.TupleClass)
16331624
&& !defn.isTupleClass(firstFormal.typeSymbol)

tests/run/i16108.scala

Lines changed: 41 additions & 0 deletions
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)