Skip to content

Commit 5ec25bd

Browse files
committed
Leave arguments of infix operations tupled.
Leave arguments of infix operations tupled in desugaring. Untuple them in typer unless the function type is unary. This allows us to simply omit the operation in typer when auto tupling is disabled.
1 parent 0ee0557 commit 5ec25bd

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
@@ -1195,10 +1195,10 @@ object desugar {
11951195
arg match
11961196
case Parens(arg) =>
11971197
Apply(sel, assignToNamedArg(arg) :: Nil)
1198-
case Tuple(Nil) =>
1199-
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixUnit)
1200-
case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed
1198+
case Tuple(args) if args.exists(_.isInstanceOf[Assign]) =>
12011199
Apply(sel, args.mapConserve(assignToNamedArg))
1200+
case Tuple(args) =>
1201+
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixTuple)
12021202
case _ =>
12031203
Apply(sel, arg :: Nil)
12041204

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
@@ -2930,14 +2930,31 @@ class Typer extends Namer
29302930
false
29312931
}
29322932

2933+
/** Should we tuple or untuple the argument before application?
2934+
* If auto-tupling is enabled then
2935+
*
2936+
* - we tuple n-ary arguments where n > 0 if the function consists
2937+
* only of unary alternatives
2938+
* - we untuple tuple arguments of infix operations if the function
2939+
* does not consist only of unary alternatives.
2940+
*/
2941+
def needsTupledDual(funType: Type, pt: FunProto): Boolean = {
2942+
pt.args match
2943+
case untpd.Tuple(elems) :: Nil =>
2944+
elems.length > 1
2945+
&& pt.applyKind == ApplyKind.InfixTuple
2946+
&& !isUnary(funType)
2947+
case args =>
2948+
args.lengthCompare(1) > 0
2949+
&& isUnary(funType)
2950+
} && autoTuplingEnabled
2951+
29332952
def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
29342953
case wtp: MethodOrPoly =>
29352954
def methodStr = methPart(tree).symbol.showLocated
29362955
if (matchingApply(wtp, pt))
2937-
if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && autoTuplingEnabled)
2938-
adapt(tree, pt.tupled, locked)
2939-
else
2940-
tree
2956+
if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked)
2957+
else tree
29412958
else if (wtp.isContextualMethod)
29422959
def isContextBoundParams = wtp.stripPoly match
29432960
case MethodType(EvidenceParamName(_) :: _) => true
@@ -3465,8 +3482,8 @@ class Typer extends Namer
34653482
case ref: TermRef =>
34663483
pt match {
34673484
case pt: FunProto
3468-
if pt.args.lengthCompare(1) > 0 && isUnary(ref) && autoTuplingEnabled =>
3469-
adapt(tree, pt.tupled, locked)
3485+
if needsTupledDual(ref, pt) && autoTuplingEnabled =>
3486+
adapt(tree, pt.tupledDual, locked)
34703487
case _ =>
34713488
adaptOverloaded(ref)
34723489
}

0 commit comments

Comments
 (0)