Skip to content

Commit 3c56b3e

Browse files
authored
Merge pull request #9137 from dotty-staging/fix-#9132
Fix #9132: Align with Scala 2's handling of () infix arguments
2 parents a3b417b + 6de432a commit 3c56b3e

18 files changed

+109
-47
lines changed

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

+11-9
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ object desugar {
537537
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
538538
val app = Apply(nu, vparams.map(refOfDef))
539539
vparams match {
540-
case vparam :: _ if vparam.mods.is(Given) => app.setUsingApply()
540+
case vparam :: _ if vparam.mods.is(Given) => app.setApplyKind(ApplyKind.Using)
541541
case _ => app
542542
}
543543
}
@@ -1188,17 +1188,19 @@ object desugar {
11881188
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
11891189
case _ => arg
11901190
}
1191-
def makeOp(fn: Tree, arg: Tree, selectPos: Span) = {
1192-
val args: List[Tree] = arg match {
1193-
case Parens(arg) => assignToNamedArg(arg) :: Nil
1194-
case Tuple(args) => args.mapConserve(assignToNamedArg)
1195-
case _ => arg :: Nil
1196-
}
1191+
def makeOp(fn: Tree, arg: Tree, selectPos: Span) =
11971192
val sel = Select(fn, op.name).withSpan(selectPos)
11981193
if (left.sourcePos.endLine < op.sourcePos.startLine)
11991194
sel.pushAttachment(MultiLineInfix, ())
1200-
Apply(sel, args)
1201-
}
1195+
arg match
1196+
case Parens(arg) =>
1197+
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
1201+
Apply(sel, args.mapConserve(assignToNamedArg))
1202+
case _ =>
1203+
Apply(sel, arg :: Nil)
12021204

12031205
if (isLeftAssoc(op.name))
12041206
makeOp(left, right, Span(left.span.start, op.span.end, op.span.start))

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -445,15 +445,26 @@ object Trees {
445445
def forwardTo: Tree[T] = fun
446446
}
447447

448+
enum ApplyKind:
449+
case Regular // r.f(x)
450+
case Using // r.f(using x)
451+
case InfixUnit // r f (), needs to be treated specially for an error message in typedApply
452+
448453
/** fun(args) */
449454
case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
450455
extends GenericApply[T] {
451456
type ThisTree[-T >: Untyped] = Apply[T]
452457

453-
def isUsingApply = hasAttachment(untpd.ApplyGiven)
454-
def setUsingApply() = { putAttachment(untpd.ApplyGiven, ()); this }
458+
def setApplyKind(kind: ApplyKind) =
459+
putAttachment(untpd.KindOfApply, kind)
460+
this
461+
462+
def applyKind: ApplyKind =
463+
attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular)
455464
}
456465

466+
467+
457468
/** fun[args] */
458469
case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
459470
extends GenericApply[T] {

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

+1-1
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
344344
val OriginalSymbol: Property.Key[Symbol] = Property.Key()
345345

346346
/** Property key for contextual Apply trees of the form `fn given arg` */
347-
val ApplyGiven: Property.StickyKey[Unit] = Property.StickyKey()
347+
val KindOfApply: Property.StickyKey[ApplyKind] = Property.StickyKey()
348348

349349
// ------ Creation methods for untyped only -----------------
350350

@@ -773,5 +773,5 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
773773
}
774774

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

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2354,7 +2354,7 @@ object Parsers {
23542354

23552355
def mkApply(fn: Tree, args: (List[Tree], Boolean)): Tree =
23562356
val res = Apply(fn, args._1)
2357-
if args._2 then res.setUsingApply()
2357+
if args._2 then res.setApplyKind(ApplyKind.Using)
23582358
res
23592359

23602360
val argumentExpr: () => Tree = () => expr(Location.InArgs) match

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
386386
else
387387
toTextLocal(fun)
388388
~ "("
389-
~ Str("using ").provided(app.isUsingApply && !homogenizedView)
389+
~ Str("using ").provided(app.applyKind == ApplyKind.Using && !homogenizedView)
390390
~ toTextGlobal(args, ", ")
391391
~ ")"
392392
case tree: TypeApply =>

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

+27-9
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.InfixUnit && funType.widen.isNullaryMethod =>
619+
i"can't supply unit value with infix notation because nullary $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/EtaExpansion.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ object EtaExpansion extends LiftImpure {
242242
if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam)
243243
ids = ids.init :+ repeated(ids.last)
244244
val app = Apply(lifted, ids)
245-
if (mt.isContextualMethod) app.setUsingApply()
245+
if (mt.isContextualMethod) app.setApplyKind(ApplyKind.Using)
246246
val body = if (isLastApplication) app else PostfixOp(app, Ident(nme.WILDCARD))
247247
val fn =
248248
if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given))

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

+9-10
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

+7-6
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 _ =>
@@ -3047,7 +3047,7 @@ class Typer extends Namer
30473047
}
30483048
tryEither {
30493049
val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs)
3050-
if (wtp.isContextualMethod) app.setUsingApply()
3050+
if (wtp.isContextualMethod) app.setApplyKind(ApplyKind.Using)
30513051
typr.println(i"try with default implicit args $app")
30523052
typed(app, pt, locked)
30533053
} { (_, _) =>
@@ -3065,7 +3065,7 @@ class Typer extends Namer
30653065
}
30663066
}
30673067
pt.revealIgnored match {
3068-
case pt: FunProto if pt.isUsingApply =>
3068+
case pt: FunProto if pt.applyKind == ApplyKind.Using =>
30693069
// We can end up here if extension methods are called with explicit given arguments.
30703070
// See for instance #7119.
30713071
tree
@@ -3513,8 +3513,9 @@ class Typer extends Namer
35133513
* Overridden in `ReTyper`, where all applications are treated the same
35143514
*/
35153515
protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean =
3516-
methType.isContextualMethod == pt.isUsingApply ||
3517-
methType.isImplicitMethod && pt.isUsingApply // for a transition allow `with` arguments for regular implicit parameters
3516+
val isUsingApply = pt.applyKind == ApplyKind.Using
3517+
methType.isContextualMethod == isUsingApply
3518+
|| methType.isImplicitMethod && isUsingApply // for a transition allow `with` arguments for regular implicit parameters
35183519

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

tests/neg/i2033.check

+9
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 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

+1-1
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/neg/leading-infix-miss.check

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- [E008] Not Found Error: tests/neg/leading-infix-miss.scala:3:2 ------------------------------------------------------
2+
2 | val x = false
3+
3 | ! true // error // error
4+
| ^
5+
| value ! is not a member of Boolean.
6+
| Note that `!` is treated as an infix operator in Scala 3.
7+
| If you do not want that, insert a `;` or empty line in front
8+
| or drop any spaces behind the operator.
9+
-- [E007] Type Mismatch Error: tests/neg/leading-infix-miss.scala:3:8 --------------------------------------------------
10+
3 | ! true // error // error
11+
| ^
12+
| Found: Unit
13+
| Required: Boolean

tests/neg/leading-infix-miss.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def test: Boolean =
2+
val x = false
3+
! true // error // error

tests/run/i9132.check

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
()

tests/run/i9132.scala

+4
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

+2-2
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

0 commit comments

Comments
 (0)