Skip to content

Commit c9f21ba

Browse files
committed
More robust handling of auto-tupling
Instead of a state based hidden side channel between adapt and realApply, use only a hint, and check again. Fies #12133
1 parent 401df67 commit c9f21ba

File tree

3 files changed

+55
-40
lines changed

3 files changed

+55
-40
lines changed

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -851,15 +851,12 @@ trait Applications extends Compatibility {
851851
record("typedApply")
852852
val fun1 = typedExpr(tree.fun, originalProto)
853853

854-
// Warning: The following lines are dirty and fragile.
855-
// We record that auto-tupling or untupling was demanded as a side effect in adapt.
856-
// If it was, we assume the tupled-dual proto-type in the rest of the application,
857-
// until, possibly, we have to fall back to insert an implicit on the qualifier.
858-
// This crucially relies on he fact that `proto` is used only in a single call of `adapt`,
859-
// otherwise we would get possible cross-talk between different `adapt` calls using the same
860-
// prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with
861-
// a modified tree but this would be more convoluted and less efficient.
862-
val proto = if (originalProto.hasTupledDual) originalProto.tupledDual else originalProto
854+
// If adaptation created a tupled dual of `originalProto`, pick the right version
855+
// (tupled or not) of originalProto to proceed.
856+
val proto =
857+
if originalProto.hasTupledDual && needsTupledDual(fun1.tpe, originalProto)
858+
then originalProto.tupledDual
859+
else originalProto
863860

864861
// If some of the application's arguments are function literals without explicitly declared
865862
// parameter types, relate the normalized result type of the application with the
@@ -1097,6 +1094,40 @@ trait Applications extends Compatibility {
10971094
tree
10981095
}
10991096

1097+
/** Is `tp` a unary function type or an overloaded type with with only unary function
1098+
* types as alternatives?
1099+
*/
1100+
def isUnary(tp: Type)(using Context): Boolean = tp match {
1101+
case tp: MethodicType =>
1102+
tp.firstParamTypes match {
1103+
case ptype :: Nil => !ptype.isRepeatedParam
1104+
case _ => false
1105+
}
1106+
case tp: TermRef =>
1107+
tp.denot.alternatives.forall(alt => isUnary(alt.info))
1108+
case _ =>
1109+
false
1110+
}
1111+
1112+
/** Should we tuple or untuple the argument before application?
1113+
* If auto-tupling is enabled then
1114+
*
1115+
* - we tuple n-ary arguments where n > 0 if the function consists
1116+
* only of unary alternatives
1117+
* - we untuple tuple arguments of infix operations if the function
1118+
* does not consist only of unary alternatives.
1119+
*/
1120+
def needsTupledDual(funType: Type, pt: FunProto)(using Context): Boolean =
1121+
pt.args match
1122+
case untpd.Tuple(elems) :: Nil =>
1123+
elems.length > 1
1124+
&& pt.applyKind == ApplyKind.InfixTuple
1125+
&& !isUnary(funType)
1126+
case args =>
1127+
args.lengthCompare(1) > 0
1128+
&& isUnary(funType)
1129+
&& Feature.autoTuplingEnabled
1130+
11001131
/** If `tree` is a complete application of a compiler-generated `apply`
11011132
* or `copy` method of an enum case, widen its type to the underlying
11021133
* type by means of a type ascription, as long as the widened type is

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

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3202,37 +3202,6 @@ class Typer extends Namer
32023202
}
32033203
}
32043204

3205-
def isUnary(tp: Type): Boolean = tp match {
3206-
case tp: MethodicType =>
3207-
tp.firstParamTypes match {
3208-
case ptype :: Nil => !ptype.isRepeatedParam
3209-
case _ => false
3210-
}
3211-
case tp: TermRef =>
3212-
tp.denot.alternatives.forall(alt => isUnary(alt.info))
3213-
case _ =>
3214-
false
3215-
}
3216-
3217-
/** Should we tuple or untuple the argument before application?
3218-
* If auto-tupling is enabled then
3219-
*
3220-
* - we tuple n-ary arguments where n > 0 if the function consists
3221-
* only of unary alternatives
3222-
* - we untuple tuple arguments of infix operations if the function
3223-
* does not consist only of unary alternatives.
3224-
*/
3225-
def needsTupledDual(funType: Type, pt: FunProto): Boolean =
3226-
pt.args match
3227-
case untpd.Tuple(elems) :: Nil =>
3228-
elems.length > 1
3229-
&& pt.applyKind == ApplyKind.InfixTuple
3230-
&& !isUnary(funType)
3231-
case args =>
3232-
args.lengthCompare(1) > 0
3233-
&& isUnary(funType)
3234-
&& autoTuplingEnabled
3235-
32363205
def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
32373206
case wtp: MethodOrPoly =>
32383207
def methodStr = methPart(tree).symbol.showLocated

tests/pos/i12133.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class B
2+
3+
class A {
4+
def foo(x: B) = ???
5+
def foo(str: String) = ???
6+
}
7+
8+
//implicit class C(x: A) {
9+
// def foo(s: Int*) = s.size
10+
//}
11+
extension (x: A) def foo(s: Int*) = s.size
12+
13+
val a = new A
14+
15+
def test: Unit = a.foo(1, 2)

0 commit comments

Comments
 (0)