Skip to content

Commit 4ef6e32

Browse files
committed
Fix #9132: Align with Scala 2's handling of () infix arguments
A right argument of an infix operation is kept as an empty tuuple, instead of being desugared to an empty parameter list. The most involved part of the change was getting decent error messages, since it required propagating the information that an apply is an infix all the way into application checking. Hopefully, we can profit from the change when we drop auto-tupling, since then similar info will be needed for error messages in more situations.
1 parent 345c8bc commit 4ef6e32

File tree

14 files changed

+87
-36
lines changed

14 files changed

+87
-36
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,13 +1191,14 @@ object desugar {
11911191
def makeOp(fn: Tree, arg: Tree, selectPos: Span) = {
11921192
val args: List[Tree] = arg match {
11931193
case Parens(arg) => assignToNamedArg(arg) :: Nil
1194-
case Tuple(args) => args.mapConserve(assignToNamedArg)
1194+
case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed
1195+
args.mapConserve(assignToNamedArg)
11951196
case _ => arg :: Nil
11961197
}
11971198
val sel = Select(fn, op.name).withSpan(selectPos)
11981199
if (left.sourcePos.endLine < op.sourcePos.startLine)
11991200
sel.pushAttachment(MultiLineInfix, ())
1200-
Apply(sel, args)
1201+
InfixApply(sel, args)
12011202
}
12021203

12031204
if (isLeftAssoc(op.name))

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,15 +443,25 @@ object Trees {
443443
def forwardTo: Tree[T] = fun
444444
}
445445

446+
enum ApplyKind:
447+
case Regular, Using, Infix
448+
446449
/** fun(args) */
447450
case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
448451
extends GenericApply[T] {
449452
type ThisTree[-T >: Untyped] = Apply[T]
450453

451-
def isUsingApply = hasAttachment(untpd.ApplyGiven)
452-
def setUsingApply() = { putAttachment(untpd.ApplyGiven, ()); this }
454+
def setUsingApply() =
455+
putAttachment(untpd.ApplyGiven, ())
456+
this
457+
458+
def applyKind: ApplyKind =
459+
if hasAttachment(untpd.ApplyGiven) then ApplyKind.Using
460+
else ApplyKind.Regular
453461
}
454462

463+
464+
455465
/** fun[args] */
456466
case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
457467
extends GenericApply[T] {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1370,5 +1370,5 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13701370

13711371

13721372
protected def FunProto(args: List[Tree], resType: Type)(using Context) =
1373-
ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, isUsingApply = false)
1373+
ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, ApplyKind.Regular)
13741374
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
134134
case _ => name
135135
}
136136

137+
/** Short-lived class created by desugar.binop and used by typedApply to
138+
* improve error messages for infix applications
139+
*/
140+
class InfixApply(fun: Tree, args: List[Tree])(implicit @constructorOnly src: SourceFile)
141+
extends Apply(fun, args):
142+
override def applyKind = ApplyKind.Infix
143+
137144
case class Number(digits: String, kind: NumberKind)(implicit @constructorOnly src: SourceFile) extends TermTree
138145

139146
enum NumberKind {
@@ -780,5 +787,5 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
780787
}
781788

782789
protected def FunProto(args: List[Tree], resType: Type)(using Context) =
783-
ProtoTypes.FunProto(args, resType)(ctx.typer, isUsingApply = false)
790+
ProtoTypes.FunProto(args, resType)(ctx.typer, ApplyKind.Regular)
784791
}

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
388388
else
389389
toTextLocal(fun)
390390
~ "("
391-
~ Str("using ").provided(app.isUsingApply && !homogenizedView)
391+
~ Str("using ").provided(app.applyKind == ApplyKind.Using && !homogenizedView)
392392
~ toTextGlobal(args, ", ")
393393
~ ")"
394394
case tree: TypeApply =>

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ trait Applications extends Compatibility {
248248
/** The type of typed arguments: either tpd.Tree or Type */
249249
type TypedArg
250250

251+
/** The kind of application that gets typed */
252+
def applyKind: ApplyKind
253+
251254
/** Given an original argument and the type of the corresponding formal
252255
* parameter, produce a typed argument.
253256
*/
@@ -609,7 +612,14 @@ trait Applications extends Compatibility {
609612

610613
case nil =>
611614
args match {
612-
case arg :: args1 => fail(s"too many arguments for $methString", arg)
615+
case arg :: args1 =>
616+
val msg = arg match
617+
case untpd.Tuple(Nil)
618+
if applyKind == ApplyKind.Infix && funType.widen.isNullaryMethod =>
619+
i"can't supply unit value with infix notation because nullary method $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()"
620+
case _ =>
621+
i"too many arguments for $methString"
622+
fail(msg, arg)
613623
case nil =>
614624
}
615625
}
@@ -624,6 +634,8 @@ trait Applications extends Compatibility {
624634
type TypedArg = Arg
625635
type Result = Unit
626636

637+
def applyKind = ApplyKind.Regular
638+
627639
protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match {
628640
case ref: TermRef if ref.denot.isOverloaded =>
629641
// in this case we could not resolve overloading because no alternative
@@ -687,7 +699,8 @@ trait Applications extends Compatibility {
687699
* types of arguments are either known or unknown.
688700
*/
689701
abstract class TypedApply[T >: Untyped](
690-
app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type)(using Context)
702+
app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type,
703+
override val applyKind: ApplyKind)(using Context)
691704
extends Application(methRef, fun.tpe, args, resultType) {
692705
type TypedArg = Tree
693706
def isVarArg(arg: Trees.Tree[T]): Boolean = untpd.isWildcardStarArg(arg)
@@ -795,16 +808,20 @@ trait Applications extends Compatibility {
795808
}
796809

797810
/** Subclass of Application for type checking an Apply node with untyped arguments. */
798-
class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using Context)
799-
extends TypedApply(app, fun, methRef, proto.args, resultType) {
811+
class ApplyToUntyped(
812+
app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto,
813+
resultType: Type)(using Context)
814+
extends TypedApply(app, fun, methRef, proto.args, resultType, proto.applyKind) {
800815
def typedArg(arg: untpd.Tree, formal: Type): TypedArg = proto.typedArg(arg, formal)
801816
def treeToArg(arg: Tree): untpd.Tree = untpd.TypedSplice(arg)
802817
def typeOfArg(arg: untpd.Tree): Type = proto.typeOfArg(arg)
803818
}
804819

805820
/** Subclass of Application for type checking an Apply node with typed arguments. */
806-
class ApplyToTyped(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
807-
extends TypedApply(app, fun, methRef, args, resultType) {
821+
class ApplyToTyped(
822+
app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree],
823+
resultType: Type, applyKind: ApplyKind)(using Context)
824+
extends TypedApply(app, fun, methRef, args, resultType, applyKind) {
808825
def typedArg(arg: Tree, formal: Type): TypedArg = arg
809826
def treeToArg(arg: Tree): Tree = arg
810827
def typeOfArg(arg: Tree): Type = arg.tpe
@@ -840,7 +857,8 @@ trait Applications extends Compatibility {
840857
def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = {
841858

842859
def realApply(using Context): Tree = {
843-
val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isUsingApply)(using argCtx(tree))
860+
val originalProto =
861+
new FunProto(tree.args, IgnoredProto(pt))(this, tree.applyKind)(using argCtx(tree))
844862
record("typedApply")
845863
val fun1 = typedFunPart(tree.fun, originalProto)
846864

@@ -998,7 +1016,7 @@ trait Applications extends Compatibility {
9981016
def ApplyTo(app: untpd.Apply, fun: tpd.Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using Context): tpd.Tree =
9991017
val typer = ctx.typer
10001018
if (proto.allArgTypesAreCurrent())
1001-
typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType).result
1019+
typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType, proto.applyKind).result
10021020
else
10031021
typer.ApplyToUntyped(app, fun, methRef, proto, resultType)(
10041022
using fun.nullableInArgContext(using argCtx(app))).result
@@ -1655,7 +1673,7 @@ trait Applications extends Compatibility {
16551673
def resolve(alts: List[TermRef]): List[TermRef] =
16561674
pt match
16571675
case pt: FunProto =>
1658-
if pt.isUsingApply then
1676+
if pt.applyKind == ApplyKind.Using then
16591677
val alts0 = alts.filterConserve { alt =>
16601678
val mt = alt.widen.stripPoly
16611679
mt.isImplicitMethod || mt.isContextualMethod

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,8 @@ object ProtoTypes {
225225
class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true)
226226

227227
trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto
228-
trait FunOrPolyProto extends ProtoType { // common trait of PolyProto and FunProto
229-
def isUsingApply: Boolean = false
230-
}
228+
trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto
229+
def applyKind: ApplyKind = ApplyKind.Regular
231230

232231
class FunProtoState {
233232

@@ -249,7 +248,7 @@ object ProtoTypes {
249248
* [](args): resultType
250249
*/
251250
case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer,
252-
override val isUsingApply: Boolean, state: FunProtoState = new FunProtoState)(using protoCtx: Context)
251+
override val applyKind: ApplyKind, state: FunProtoState = new FunProtoState)(using protoCtx: Context)
253252
extends UncachedGroundType with ApplyingProto with FunOrPolyProto {
254253
override def resultType(using Context): Type = resType
255254

@@ -263,7 +262,7 @@ object ProtoTypes {
263262

264263
def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
265264
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
266-
else new FunProto(args, resultType)(typer, isUsingApply)
265+
else new FunProto(args, resultType)(typer, applyKind)
267266

268267
/** @return True if all arguments have types.
269268
*/
@@ -353,7 +352,7 @@ object ProtoTypes {
353352
case pt: FunProto =>
354353
pt
355354
case _ =>
356-
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, isUsingApply)
355+
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, applyKind)
357356
tupled
358357
}
359358

@@ -388,15 +387,15 @@ object ProtoTypes {
388387

389388
override def withContext(newCtx: Context): ProtoType =
390389
if newCtx `eq` protoCtx then this
391-
else new FunProto(args, resType)(typer, isUsingApply, state)(using newCtx)
390+
else new FunProto(args, resType)(typer, applyKind, state)(using newCtx)
392391
}
393392

394393
/** A prototype for expressions that appear in function position
395394
*
396395
* [](args): resultType, where args are known to be typed
397396
*/
398-
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isUsingApply: Boolean)(using Context)
399-
extends FunProto(args, resultType)(typer, isUsingApply):
397+
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, applyKind: ApplyKind)(using Context)
398+
extends FunProto(args, resultType)(typer, applyKind):
400399
override def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree)(using Context): List[tpd.Tree] = args
401400
override def typedArg(arg: untpd.Tree, formal: Type)(using Context): tpd.Tree = arg.asInstanceOf[tpd.Tree]
402401
override def allArgTypesAreCurrent()(using Context): Boolean = true
@@ -444,7 +443,7 @@ object ProtoTypes {
444443
}
445444

446445
class UnapplyFunProto(argType: Type, typer: Typer)(using Context) extends FunProto(
447-
untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, isUsingApply = false)
446+
untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, applyKind = ApplyKind.Regular)
448447

449448
/** A prototype for expressions [] that are type-parameterized:
450449
*

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ class Typer extends Namer
10921092
val nestedCtx = outerCtx.fresh.setNewTyperState()
10931093
inContext(nestedCtx) {
10941094
val protoArgs = args map (_ withType WildcardType)
1095-
val callProto = FunProto(protoArgs, WildcardType)(this, app.isUsingApply)
1095+
val callProto = FunProto(protoArgs, WildcardType)(this, app.applyKind)
10961096
val expr1 = typedExpr(expr, callProto)
10971097
if nestedCtx.reporter.hasErrors then NoType
10981098
else inContext(outerCtx) {
@@ -2887,7 +2887,7 @@ class Typer extends Namer
28872887
errorTree(tree, NoMatchingOverload(altDenots, pt))
28882888
def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil
28892889
pt match {
2890-
case pt: FunOrPolyProto if !pt.isUsingApply =>
2890+
case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using =>
28912891
// insert apply or convert qualifier, but only for a regular application
28922892
tryInsertApplyOrImplicit(tree, pt, locked)(noMatches)
28932893
case _ =>
@@ -3063,7 +3063,7 @@ class Typer extends Namer
30633063
}
30643064
}
30653065
pt.revealIgnored match {
3066-
case pt: FunProto if pt.isUsingApply =>
3066+
case pt: FunProto if pt.applyKind == ApplyKind.Using =>
30673067
// We can end up here if extension methods are called with explicit given arguments.
30683068
// See for instance #7119.
30693069
tree
@@ -3535,8 +3535,9 @@ class Typer extends Namer
35353535
* Overridden in `ReTyper`, where all applications are treated the same
35363536
*/
35373537
protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean =
3538-
methType.isContextualMethod == pt.isUsingApply ||
3539-
methType.isImplicitMethod && pt.isUsingApply // for a transition allow `with` arguments for regular implicit parameters
3538+
val isUsingApply = pt.applyKind == ApplyKind.Using
3539+
methType.isContextualMethod == isUsingApply
3540+
|| methType.isImplicitMethod && isUsingApply // for a transition allow `with` arguments for regular implicit parameters
35403541

35413542
/** Check that `tree == x: pt` is typeable. Used when checking a pattern
35423543
* against a selector of type `pt`. This implementation accounts for

tests/neg/i2033.check

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Error: tests/neg/i2033.scala:7:30 -----------------------------------------------------------------------------------
2+
7 | val arr = bos toByteArray () // error
3+
| ^^
4+
|can't supply unit value with infix notation because nullary method method toByteArray: (): Array[Byte] takes no arguments; use dotted invocation instead: (...).toByteArray()
5+
-- [E007] Type Mismatch Error: tests/neg/i2033.scala:20:35 -------------------------------------------------------------
6+
20 | val out = new ObjectOutputStream(println) // error
7+
| ^^^^^^^
8+
| Found: Unit
9+
| Required: String

tests/neg/i2033.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ object Test {
44
def check(obj: AnyRef): Unit = {
55
val bos = new ByteArrayOutputStream()
66
val out = new ObjectOutputStream(println)
7-
val arr = bos toByteArray ()
7+
val arr = bos toByteArray () // error
88
val in = (())
99
val deser = ()
1010
val lhs = mutable LinkedHashSet ()

tests/run/i9132.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
()

tests/run/i9132.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@main def Test =
2+
scala.collection.mutable.ArrayBuilder.make[Unit] += ()
3+
Console.println ()
4+
Console println ()

tests/run/iterators.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ object Test {
5757
def check_drop: Int = {
5858
val it1 = Iterator.from(0)
5959
val it2 = it1 map { 2 * _ }
60-
val n1 = it1 drop 2 next()
61-
val n2 = it2 drop 2 next();
60+
val n1 = it1.drop(2).next()
61+
val n2 = it2.drop(2).next()
6262
n1 + n2
6363
}
6464

tests/run/runtime.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ object Test1Test {
6565
// {System.out.print(12); java.lang}.System.out.println();
6666
// {System.out.print(13); java.lang.System}.out.println();
6767
{Console.print(14); Console}.println;
68-
{Console.print(15); (() => Console.println):(() => Unit)} apply ();
68+
{Console.print(15); (() => Console.println):(() => Unit)}.apply();
6969
{Console.print(16); Console.println};
7070

7171
{Console.print(20)}; test1.bar.System.out.println();
7272
// {System.out.print(21); test1}.bar.System.out.println();
7373
// {System.out.print(22); test1.bar}.System.out.println();
7474
{Console.print(23); test1.bar.System}.out.println();
7575
{Console.print(24); test1.bar.System.out}.println();
76-
{Console.print(25); test1.bar.System.out.println:(() => Unit)} apply ();
76+
{Console.print(25); test1.bar.System.out.println:(() => Unit)}.apply();
7777
{Console.print(26); test1.bar.System.out.println()};
7878
}
7979

0 commit comments

Comments
 (0)