From 9f90ad0a6f2057e6c32ae0a030b029f435d37eb3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 6 Sep 2024 12:11:39 +0100 Subject: [PATCH] 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. --- .../dotty/tools/dotc/typer/Applications.scala | 30 ++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 11 +---- tests/run/i16108.scala | 41 +++++++++++++++++++ 3 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 tests/run/i16108.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c6d8fd80fd60..11a95ce23f93 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2214,19 +2214,26 @@ trait Applications extends Compatibility { case untpd.Function(args: List[untpd.ValDef] @unchecked, body) => // If ref refers to a method whose parameter at index `idx` is a function type, - // the arity of that function, otherise -1. - def paramCount(ref: TermRef) = + // the parameters of that function, otherwise Nil. + // We return Nil for both nilary functions and non-functions, + // because we won't be making tupled functions for nilary functions anyways, + // seeing as there is no Tuple0. + def params(ref: TermRef) = val formals = ref.widen.firstParamTypes if formals.length > idx then formals(idx).dealias match - case defn.FunctionNOf(args, _, _) => args.length - case _ => -1 - else -1 + case defn.FunctionNOf(args, _, _) => args + case _ => Nil + else Nil + + def isCorrectUnaryFunction(alt: TermRef): Boolean = + val formals = params(alt) + formals.length == 1 && ptIsCorrectProduct(formals.head, args) val numArgs = args.length - if numArgs != 1 - && !alts.exists(paramCount(_) == numArgs) - && alts.exists(paramCount(_) == 1) + if numArgs > 1 + && !alts.exists(params(_).lengthIs == numArgs) + && alts.exists(isCorrectUnaryFunction) then desugar.makeTupledFunction(args, body, isGenericTuple = true) // `isGenericTuple = true` is the safe choice here. It means the i'th tuple @@ -2395,6 +2402,13 @@ trait Applications extends Compatibility { } end resolveOverloaded1 + /** Is `formal` a product type which is elementwise compatible with `params`? */ + def ptIsCorrectProduct(formal: Type, params: List[untpd.ValDef])(using Context): Boolean = + isFullyDefined(formal, ForceDegree.flipBottom) + && defn.isProductSubType(formal) + && tupleComponentTypes(formal).corresponds(params): (argType, param) => + param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe + /** The largest suffix of `paramss` that has the same first parameter name as `t`, * plus the number of term parameters in `paramss` that come before that suffix. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1125e09539b6..2b0e0c764398 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1837,19 +1837,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (protoFormals.length == params.length) (protoFormals(i), isDefinedErased(i)) else (errorType(WrongNumberOfParameters(tree, params.length, pt, protoFormals.length), tree.srcPos), false) - /** Is `formal` a product type which is elementwise compatible with `params`? */ - def ptIsCorrectProduct(formal: Type) = - isFullyDefined(formal, ForceDegree.flipBottom) && - defn.isProductSubType(formal) && - tupleComponentTypes(formal).corresponds(params) { - (argType, param) => - param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe - } - var desugared: untpd.Tree = EmptyTree if protoFormals.length == 1 && params.length != 1 then val firstFormal = protoFormals.head.loBound - if ptIsCorrectProduct(firstFormal) then + if ptIsCorrectProduct(firstFormal, params) then val isGenericTuple = firstFormal.derivesFrom(defn.TupleClass) && !defn.isTupleClass(firstFormal.typeSymbol) diff --git a/tests/run/i16108.scala b/tests/run/i16108.scala new file mode 100644 index 000000000000..de7396be8f9f --- /dev/null +++ b/tests/run/i16108.scala @@ -0,0 +1,41 @@ +import scala.language.implicitConversions + +final class Functoid[+R](val function: Product => R) + +object Functoid { + implicit def apply[A, R](function: A => R): Functoid[R] = { + println(s"arity 1") + new Functoid({ case Tuple1(a: A @unchecked) => function(a) }) + } + implicit def apply[A, B, R](function: (A, B) => R): Functoid[R] = { + println("arity 2") + new Functoid({ case (a: A @unchecked, b: B @unchecked) => function(a, b) }) + } +} + +final case class ContainerConfig(image: String, version: Int, cmd: String) + +final class ContainerResource + +object ContainerResource { + implicit final class DockerProviderExtensions(private val self: Functoid[ContainerResource]) extends AnyVal { + def modifyConfig(modify: Functoid[ContainerConfig => ContainerConfig]): Functoid[ContainerConfig => ContainerConfig] = modify + // removing this overload fixes the implicit conversion and returns `arity 2` print + def modifyConfig(modify: ContainerConfig => ContainerConfig): Functoid[ContainerConfig => ContainerConfig] = new Functoid(_ => modify) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val cfg = new Functoid(_ => new ContainerResource) + .modifyConfig { + // applying Functoid.apply explicitly instead of via implicit conversion also avoids untupling +// Functoid { + (image: String, version: Int) => (cfg: ContainerConfig) => cfg.copy(image, version) +// } + } + .function.apply(Tuple2("img", 9)) + .apply(ContainerConfig("a", 0, "b")) + println(cfg) + } +}