Skip to content

Commit f83943d

Browse files
authored
Merge pull request #9159 from dotty-staging/change-infix-tuple
Leave arguments of infix operations tupled.
2 parents 5710f1f + 2acf776 commit f83943d

File tree

7 files changed

+51
-30
lines changed

7 files changed

+51
-30
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,10 +1207,10 @@ object desugar {
12071207
arg match
12081208
case Parens(arg) =>
12091209
Apply(sel, assignToNamedArg(arg) :: Nil)
1210-
case Tuple(Nil) =>
1211-
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixUnit)
1212-
case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed
1210+
case Tuple(args) if args.exists(_.isInstanceOf[Assign]) =>
12131211
Apply(sel, args.mapConserve(assignToNamedArg))
1212+
case Tuple(args) =>
1213+
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixTuple)
12141214
case _ =>
12151215
Apply(sel, arg :: Nil)
12161216

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ object Trees {
448448
enum ApplyKind:
449449
case Regular // r.f(x)
450450
case Using // r.f(using x)
451-
case InfixUnit // r f (), needs to be treated specially for an error message in typedApply
451+
case InfixTuple // r f (x1, ..., xN) where N != 1; needs to be treated specially for an error message in typedApply
452452

453453
/** fun(args) */
454454
case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,12 @@ class SymUtils(val self: Symbol) extends AnyVal {
218218
def isScalaStatic(using Context): Boolean =
219219
self.hasAnnotation(ctx.definitions.ScalaStaticAnnot)
220220

221+
/** Is symbol assumed or declared as an infix symbol? */
222+
def isDeclaredInfix(using Context): Boolean =
223+
self.hasAnnotation(defn.InfixAnnot)
224+
|| defn.isInfix(self)
225+
|| self.name.isUnapplyName
226+
&& self.owner.is(Module)
227+
&& self.owner.linkedClass.is(Case)
228+
&& self.owner.linkedClass.isDeclaredInfix
221229
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ trait Applications extends Compatibility {
615615
case arg :: args1 =>
616616
val msg = arg match
617617
case untpd.Tuple(Nil)
618-
if applyKind == ApplyKind.InfixUnit && funType.widen.isNullaryMethod =>
618+
if applyKind == ApplyKind.InfixTuple && funType.widen.isNullaryMethod =>
619619
i"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()"
620620
case _ =>
621621
i"too many arguments for $methString"
@@ -862,14 +862,15 @@ trait Applications extends Compatibility {
862862
record("typedApply")
863863
val fun1 = typedFunPart(tree.fun, originalProto)
864864

865-
// Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as
866-
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application,
865+
// Warning: The following lines are dirty and fragile.
866+
// We record that auto-tupling or untupling was demanded as a side effect in adapt.
867+
// If it was, we assume the tupled-dual proto-type in the rest of the application,
867868
// until, possibly, we have to fall back to insert an implicit on the qualifier.
868869
// This crucially relies on he fact that `proto` is used only in a single call of `adapt`,
869870
// otherwise we would get possible cross-talk between different `adapt` calls using the same
870871
// prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with
871872
// a modified tree but this would be more convoluted and less efficient.
872-
val proto = if (originalProto.isTupled) originalProto.tupled else originalProto
873+
val proto = if (originalProto.hasTupledDual) originalProto.tupledDual else originalProto
873874

874875
// If some of the application's arguments are function literals without explicitly declared
875876
// parameter types, relate the normalized result type of the application with the

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -810,21 +810,13 @@ trait Checking {
810810
* operator is alphanumeric, it must be declared `@infix`.
811811
*/
812812
def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(using Context): Unit = {
813-
814-
def isInfix(sym: Symbol): Boolean =
815-
sym.hasAnnotation(defn.InfixAnnot) ||
816-
defn.isInfix(sym) ||
817-
sym.name.isUnapplyName &&
818-
sym.owner.is(Module) && sym.owner.linkedClass.is(Case) &&
819-
isInfix(sym.owner.linkedClass)
820-
821813
tree.op match {
822814
case id @ Ident(name: Name) =>
823815
name.toTermName match {
824816
case name: SimpleName
825817
if !untpd.isBackquoted(id) &&
826818
!name.isOperatorName &&
827-
!isInfix(meth) &&
819+
!meth.isDeclaredInfix &&
828820
!meth.maybeOwner.is(Scala2x) &&
829821
!infixOKSinceFollowedBy(tree.right) &&
830822
sourceVersion.isAtLeast(`3.1`) =>

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ object ProtoTypes {
236236
/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
237237
var typedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.Empty
238238

239-
/** The tupled version of this prototype, if it has been computed */
240-
var tupled: Type = NoType
239+
/** The tupled or untupled version of this prototype, if it has been computed */
240+
var tupledDual: Type = NoType
241241

242242
/** If true, the application of this prototype was canceled. */
243243
var toDrop: Boolean = false
@@ -348,16 +348,19 @@ object ProtoTypes {
348348
}
349349

350350
/** The same proto-type but with all arguments combined in a single tuple */
351-
def tupled: FunProto = state.tupled match {
351+
def tupledDual: FunProto = state.tupledDual match {
352352
case pt: FunProto =>
353353
pt
354354
case _ =>
355-
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, applyKind)
356-
tupled
355+
val dualArgs = args match
356+
case untpd.Tuple(elems) :: Nil => elems
357+
case _ => untpd.Tuple(args) :: Nil
358+
state.tupledDual = new FunProto(dualArgs, resultType)(typer, applyKind)
359+
tupledDual
357360
}
358361

359-
/** Somebody called the `tupled` method of this prototype */
360-
def isTupled: Boolean = state.tupled.isInstanceOf[FunProto]
362+
/** Somebody called the `tupledDual` method of this prototype */
363+
def hasTupledDual: Boolean = state.tupledDual.isInstanceOf[FunProto]
361364

362365
/** Cancel the application of this prototype. This can happen for a nullary
363366
* application `f()` if `f` refers to a symbol that exists both in parameterless

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,14 +2935,31 @@ class Typer extends Namer
29352935
false
29362936
}
29372937

2938+
/** Should we tuple or untuple the argument before application?
2939+
* If auto-tupling is enabled then
2940+
*
2941+
* - we tuple n-ary arguments where n > 0 if the function consists
2942+
* only of unary alternatives
2943+
* - we untuple tuple arguments of infix operations if the function
2944+
* does not consist only of unary alternatives.
2945+
*/
2946+
def needsTupledDual(funType: Type, pt: FunProto): Boolean =
2947+
pt.args match
2948+
case untpd.Tuple(elems) :: Nil =>
2949+
elems.length > 1
2950+
&& pt.applyKind == ApplyKind.InfixTuple
2951+
&& !isUnary(funType)
2952+
case args =>
2953+
args.lengthCompare(1) > 0
2954+
&& isUnary(funType)
2955+
&& autoTuplingEnabled
2956+
29382957
def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
29392958
case wtp: MethodOrPoly =>
29402959
def methodStr = methPart(tree).symbol.showLocated
29412960
if (matchingApply(wtp, pt))
2942-
if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && autoTuplingEnabled)
2943-
adapt(tree, pt.tupled, locked)
2944-
else
2945-
tree
2961+
if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked)
2962+
else tree
29462963
else if (wtp.isContextualMethod)
29472964
def isContextBoundParams = wtp.stripPoly match
29482965
case MethodType(EvidenceParamName(_) :: _) => true
@@ -3472,8 +3489,8 @@ class Typer extends Namer
34723489
case ref: TermRef =>
34733490
pt match {
34743491
case pt: FunProto
3475-
if pt.args.lengthCompare(1) > 0 && isUnary(ref) && autoTuplingEnabled =>
3476-
adapt(tree, pt.tupled, locked)
3492+
if needsTupledDual(ref, pt) && autoTuplingEnabled =>
3493+
adapt(tree, pt.tupledDual, locked)
34773494
case _ =>
34783495
adaptOverloaded(ref)
34793496
}

0 commit comments

Comments
 (0)