From 4ef6e323c72bd58718856c99153b08635d6c261d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Jun 2020 13:53:48 +0200 Subject: [PATCH 1/5] 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. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 5 +-- compiler/src/dotty/tools/dotc/ast/Trees.scala | 14 ++++++-- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 9 ++++- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 36 ++++++++++++++----- .../dotty/tools/dotc/typer/ProtoTypes.scala | 19 +++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 11 +++--- tests/neg/i2033.check | 9 +++++ tests/neg/i2033.scala | 2 +- tests/run/i9132.check | 2 ++ tests/run/i9132.scala | 4 +++ tests/run/iterators.scala | 4 +-- tests/run/runtime.scala | 4 +-- 14 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 tests/neg/i2033.check create mode 100644 tests/run/i9132.check create mode 100644 tests/run/i9132.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c8376564005e..92111d7ac0b0 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1191,13 +1191,14 @@ object desugar { def makeOp(fn: Tree, arg: Tree, selectPos: Span) = { val args: List[Tree] = arg match { case Parens(arg) => assignToNamedArg(arg) :: Nil - case Tuple(args) => args.mapConserve(assignToNamedArg) + case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed + args.mapConserve(assignToNamedArg) case _ => arg :: Nil } val sel = Select(fn, op.name).withSpan(selectPos) if (left.sourcePos.endLine < op.sourcePos.startLine) sel.pushAttachment(MultiLineInfix, ()) - Apply(sel, args) + InfixApply(sel, args) } if (isLeftAssoc(op.name)) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fc53e29ad117..7b7d5bd5ecd4 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -443,15 +443,25 @@ object Trees { def forwardTo: Tree[T] = fun } + enum ApplyKind: + case Regular, Using, Infix + /** fun(args) */ case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { type ThisTree[-T >: Untyped] = Apply[T] - def isUsingApply = hasAttachment(untpd.ApplyGiven) - def setUsingApply() = { putAttachment(untpd.ApplyGiven, ()); this } + def setUsingApply() = + putAttachment(untpd.ApplyGiven, ()) + this + + def applyKind: ApplyKind = + if hasAttachment(untpd.ApplyGiven) then ApplyKind.Using + else ApplyKind.Regular } + + /** fun[args] */ case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 6ecb02a87e74..2a2369e6d53c 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1370,5 +1370,5 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { protected def FunProto(args: List[Tree], resType: Type)(using Context) = - ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, isUsingApply = false) + ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, ApplyKind.Regular) } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 11d494ce039d..4ab403ce7d1a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -134,6 +134,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case _ => name } + /** Short-lived class created by desugar.binop and used by typedApply to + * improve error messages for infix applications + */ + class InfixApply(fun: Tree, args: List[Tree])(implicit @constructorOnly src: SourceFile) + extends Apply(fun, args): + override def applyKind = ApplyKind.Infix + case class Number(digits: String, kind: NumberKind)(implicit @constructorOnly src: SourceFile) extends TermTree enum NumberKind { @@ -780,5 +787,5 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } protected def FunProto(args: List[Tree], resType: Type)(using Context) = - ProtoTypes.FunProto(args, resType)(ctx.typer, isUsingApply = false) + ProtoTypes.FunProto(args, resType)(ctx.typer, ApplyKind.Regular) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 19e24764867e..483a79cb0c71 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -388,7 +388,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else toTextLocal(fun) ~ "(" - ~ Str("using ").provided(app.isUsingApply && !homogenizedView) + ~ Str("using ").provided(app.applyKind == ApplyKind.Using && !homogenizedView) ~ toTextGlobal(args, ", ") ~ ")" case tree: TypeApply => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index fe6ead396536..1e1e13f7c596 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -248,6 +248,9 @@ trait Applications extends Compatibility { /** The type of typed arguments: either tpd.Tree or Type */ type TypedArg + /** The kind of application that gets typed */ + def applyKind: ApplyKind + /** Given an original argument and the type of the corresponding formal * parameter, produce a typed argument. */ @@ -609,7 +612,14 @@ trait Applications extends Compatibility { case nil => args match { - case arg :: args1 => fail(s"too many arguments for $methString", arg) + case arg :: args1 => + val msg = arg match + case untpd.Tuple(Nil) + if applyKind == ApplyKind.Infix && funType.widen.isNullaryMethod => + i"can't supply unit value with infix notation because nullary method $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()" + case _ => + i"too many arguments for $methString" + fail(msg, arg) case nil => } } @@ -624,6 +634,8 @@ trait Applications extends Compatibility { type TypedArg = Arg type Result = Unit + def applyKind = ApplyKind.Regular + protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match { case ref: TermRef if ref.denot.isOverloaded => // in this case we could not resolve overloading because no alternative @@ -687,7 +699,8 @@ trait Applications extends Compatibility { * types of arguments are either known or unknown. */ abstract class TypedApply[T >: Untyped]( - app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type)(using Context) + app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type, + override val applyKind: ApplyKind)(using Context) extends Application(methRef, fun.tpe, args, resultType) { type TypedArg = Tree def isVarArg(arg: Trees.Tree[T]): Boolean = untpd.isWildcardStarArg(arg) @@ -795,16 +808,20 @@ trait Applications extends Compatibility { } /** Subclass of Application for type checking an Apply node with untyped arguments. */ - class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using Context) - extends TypedApply(app, fun, methRef, proto.args, resultType) { + class ApplyToUntyped( + app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, + resultType: Type)(using Context) + extends TypedApply(app, fun, methRef, proto.args, resultType, proto.applyKind) { def typedArg(arg: untpd.Tree, formal: Type): TypedArg = proto.typedArg(arg, formal) def treeToArg(arg: Tree): untpd.Tree = untpd.TypedSplice(arg) def typeOfArg(arg: untpd.Tree): Type = proto.typeOfArg(arg) } /** Subclass of Application for type checking an Apply node with typed arguments. */ - class ApplyToTyped(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(using Context) - extends TypedApply(app, fun, methRef, args, resultType) { + class ApplyToTyped( + app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], + resultType: Type, applyKind: ApplyKind)(using Context) + extends TypedApply(app, fun, methRef, args, resultType, applyKind) { def typedArg(arg: Tree, formal: Type): TypedArg = arg def treeToArg(arg: Tree): Tree = arg def typeOfArg(arg: Tree): Type = arg.tpe @@ -840,7 +857,8 @@ trait Applications extends Compatibility { def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = { def realApply(using Context): Tree = { - val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isUsingApply)(using argCtx(tree)) + val originalProto = + new FunProto(tree.args, IgnoredProto(pt))(this, tree.applyKind)(using argCtx(tree)) record("typedApply") val fun1 = typedFunPart(tree.fun, originalProto) @@ -998,7 +1016,7 @@ trait Applications extends Compatibility { def ApplyTo(app: untpd.Apply, fun: tpd.Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using Context): tpd.Tree = val typer = ctx.typer if (proto.allArgTypesAreCurrent()) - typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType).result + typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType, proto.applyKind).result else typer.ApplyToUntyped(app, fun, methRef, proto, resultType)( using fun.nullableInArgContext(using argCtx(app))).result @@ -1655,7 +1673,7 @@ trait Applications extends Compatibility { def resolve(alts: List[TermRef]): List[TermRef] = pt match case pt: FunProto => - if pt.isUsingApply then + if pt.applyKind == ApplyKind.Using then val alts0 = alts.filterConserve { alt => val mt = alt.widen.stripPoly mt.isImplicitMethod || mt.isContextualMethod diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4c2ce2ed9ee0..ca49908949b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -225,9 +225,8 @@ object ProtoTypes { class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true) trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto - trait FunOrPolyProto extends ProtoType { // common trait of PolyProto and FunProto - def isUsingApply: Boolean = false - } + trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto + def applyKind: ApplyKind = ApplyKind.Regular class FunProtoState { @@ -249,7 +248,7 @@ object ProtoTypes { * [](args): resultType */ case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer, - override val isUsingApply: Boolean, state: FunProtoState = new FunProtoState)(using protoCtx: Context) + override val applyKind: ApplyKind, state: FunProtoState = new FunProtoState)(using protoCtx: Context) extends UncachedGroundType with ApplyingProto with FunOrPolyProto { override def resultType(using Context): Type = resType @@ -263,7 +262,7 @@ object ProtoTypes { def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto = if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this - else new FunProto(args, resultType)(typer, isUsingApply) + else new FunProto(args, resultType)(typer, applyKind) /** @return True if all arguments have types. */ @@ -353,7 +352,7 @@ object ProtoTypes { case pt: FunProto => pt case _ => - state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, isUsingApply) + state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, applyKind) tupled } @@ -388,15 +387,15 @@ object ProtoTypes { override def withContext(newCtx: Context): ProtoType = if newCtx `eq` protoCtx then this - else new FunProto(args, resType)(typer, isUsingApply, state)(using newCtx) + else new FunProto(args, resType)(typer, applyKind, state)(using newCtx) } /** A prototype for expressions that appear in function position * * [](args): resultType, where args are known to be typed */ - class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isUsingApply: Boolean)(using Context) - extends FunProto(args, resultType)(typer, isUsingApply): + class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, applyKind: ApplyKind)(using Context) + extends FunProto(args, resultType)(typer, applyKind): override def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree)(using Context): List[tpd.Tree] = args override def typedArg(arg: untpd.Tree, formal: Type)(using Context): tpd.Tree = arg.asInstanceOf[tpd.Tree] override def allArgTypesAreCurrent()(using Context): Boolean = true @@ -444,7 +443,7 @@ object ProtoTypes { } class UnapplyFunProto(argType: Type, typer: Typer)(using Context) extends FunProto( - untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, isUsingApply = false) + untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, applyKind = ApplyKind.Regular) /** A prototype for expressions [] that are type-parameterized: * diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b89d878e03c1..ec5108f35a54 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1092,7 +1092,7 @@ class Typer extends Namer val nestedCtx = outerCtx.fresh.setNewTyperState() inContext(nestedCtx) { val protoArgs = args map (_ withType WildcardType) - val callProto = FunProto(protoArgs, WildcardType)(this, app.isUsingApply) + val callProto = FunProto(protoArgs, WildcardType)(this, app.applyKind) val expr1 = typedExpr(expr, callProto) if nestedCtx.reporter.hasErrors then NoType else inContext(outerCtx) { @@ -2887,7 +2887,7 @@ class Typer extends Namer errorTree(tree, NoMatchingOverload(altDenots, pt)) def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil pt match { - case pt: FunOrPolyProto if !pt.isUsingApply => + case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using => // insert apply or convert qualifier, but only for a regular application tryInsertApplyOrImplicit(tree, pt, locked)(noMatches) case _ => @@ -3063,7 +3063,7 @@ class Typer extends Namer } } pt.revealIgnored match { - case pt: FunProto if pt.isUsingApply => + case pt: FunProto if pt.applyKind == ApplyKind.Using => // We can end up here if extension methods are called with explicit given arguments. // See for instance #7119. tree @@ -3535,8 +3535,9 @@ class Typer extends Namer * Overridden in `ReTyper`, where all applications are treated the same */ protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean = - methType.isContextualMethod == pt.isUsingApply || - methType.isImplicitMethod && pt.isUsingApply // for a transition allow `with` arguments for regular implicit parameters + val isUsingApply = pt.applyKind == ApplyKind.Using + methType.isContextualMethod == isUsingApply + || methType.isImplicitMethod && isUsingApply // for a transition allow `with` arguments for regular implicit parameters /** Check that `tree == x: pt` is typeable. Used when checking a pattern * against a selector of type `pt`. This implementation accounts for diff --git a/tests/neg/i2033.check b/tests/neg/i2033.check new file mode 100644 index 000000000000..181bf05b3f22 --- /dev/null +++ b/tests/neg/i2033.check @@ -0,0 +1,9 @@ +-- Error: tests/neg/i2033.scala:7:30 ----------------------------------------------------------------------------------- +7 | val arr = bos toByteArray () // error + | ^^ + |can't supply unit value with infix notation because nullary method method toByteArray: (): Array[Byte] takes no arguments; use dotted invocation instead: (...).toByteArray() +-- [E007] Type Mismatch Error: tests/neg/i2033.scala:20:35 ------------------------------------------------------------- +20 | val out = new ObjectOutputStream(println) // error + | ^^^^^^^ + | Found: Unit + | Required: String diff --git a/tests/neg/i2033.scala b/tests/neg/i2033.scala index c21bf80d35dc..16b2690365c7 100644 --- a/tests/neg/i2033.scala +++ b/tests/neg/i2033.scala @@ -4,7 +4,7 @@ object Test { def check(obj: AnyRef): Unit = { val bos = new ByteArrayOutputStream() val out = new ObjectOutputStream(println) - val arr = bos toByteArray () + val arr = bos toByteArray () // error val in = (()) val deser = () val lhs = mutable LinkedHashSet () diff --git a/tests/run/i9132.check b/tests/run/i9132.check new file mode 100644 index 000000000000..05898de45c76 --- /dev/null +++ b/tests/run/i9132.check @@ -0,0 +1,2 @@ + +() diff --git a/tests/run/i9132.scala b/tests/run/i9132.scala new file mode 100644 index 000000000000..75cc71aee1a7 --- /dev/null +++ b/tests/run/i9132.scala @@ -0,0 +1,4 @@ +@main def Test = + scala.collection.mutable.ArrayBuilder.make[Unit] += () + Console.println () + Console println () diff --git a/tests/run/iterators.scala b/tests/run/iterators.scala index fa6cdf6d82b3..cf9dbe5c6893 100644 --- a/tests/run/iterators.scala +++ b/tests/run/iterators.scala @@ -57,8 +57,8 @@ object Test { def check_drop: Int = { val it1 = Iterator.from(0) val it2 = it1 map { 2 * _ } - val n1 = it1 drop 2 next() - val n2 = it2 drop 2 next(); + val n1 = it1.drop(2).next() + val n2 = it2.drop(2).next() n1 + n2 } diff --git a/tests/run/runtime.scala b/tests/run/runtime.scala index 89348b294db8..6ff339030685 100644 --- a/tests/run/runtime.scala +++ b/tests/run/runtime.scala @@ -65,7 +65,7 @@ object Test1Test { // {System.out.print(12); java.lang}.System.out.println(); // {System.out.print(13); java.lang.System}.out.println(); {Console.print(14); Console}.println; - {Console.print(15); (() => Console.println):(() => Unit)} apply (); + {Console.print(15); (() => Console.println):(() => Unit)}.apply(); {Console.print(16); Console.println}; {Console.print(20)}; test1.bar.System.out.println(); @@ -73,7 +73,7 @@ object Test1Test { // {System.out.print(22); test1.bar}.System.out.println(); {Console.print(23); test1.bar.System}.out.println(); {Console.print(24); test1.bar.System.out}.println(); - {Console.print(25); test1.bar.System.out.println:(() => Unit)} apply (); + {Console.print(25); test1.bar.System.out.println:(() => Unit)}.apply(); {Console.print(26); test1.bar.System.out.println()}; } From d4048f27fda277c453b8ebb724470b570477946f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Jun 2020 14:10:37 +0200 Subject: [PATCH 2/5] Additional test to exercise another infix error message --- tests/neg/leading-infix-miss.check | 13 +++++++++++++ tests/neg/leading-infix-miss.scala | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 tests/neg/leading-infix-miss.check create mode 100644 tests/neg/leading-infix-miss.scala diff --git a/tests/neg/leading-infix-miss.check b/tests/neg/leading-infix-miss.check new file mode 100644 index 000000000000..1817290f8065 --- /dev/null +++ b/tests/neg/leading-infix-miss.check @@ -0,0 +1,13 @@ +-- [E008] Not Found Error: tests/neg/leading-infix-miss.scala:3:2 ------------------------------------------------------ +2 | val x = false +3 | ! true // error // error + | ^ + | value ! is not a member of Boolean. + | Note that `!` is treated as an infix operator in Scala 3. + | If you do not want that, insert a `;` or empty line in front + | or drop any spaces behind the operator. +-- [E007] Type Mismatch Error: tests/neg/leading-infix-miss.scala:3:8 -------------------------------------------------- +3 | ! true // error // error + | ^ + | Found: Unit + | Required: Boolean diff --git a/tests/neg/leading-infix-miss.scala b/tests/neg/leading-infix-miss.scala new file mode 100644 index 000000000000..1250f9c8ea67 --- /dev/null +++ b/tests/neg/leading-infix-miss.scala @@ -0,0 +1,3 @@ +def test: Boolean = + val x = false + ! true // error // error From 6a81c4c1abd193e3eb0c9e3e8e1cb29fa8054163 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 9 Jun 2020 20:44:25 +0200 Subject: [PATCH 3/5] Update compiler/src/dotty/tools/dotc/typer/Applications.scala Co-authored-by: Guillaume Martres --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1e1e13f7c596..145fdc5cedff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -616,7 +616,7 @@ trait Applications extends Compatibility { val msg = arg match case untpd.Tuple(Nil) if applyKind == ApplyKind.Infix && funType.widen.isNullaryMethod => - i"can't supply unit value with infix notation because nullary method $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()" + i"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()" case _ => i"too many arguments for $methString" fail(msg, arg) From 7ffc78ce189b61911f8e2c3dbe4287e864d60940 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Jun 2020 21:11:22 +0200 Subject: [PATCH 4/5] Use same kind of Attachment for Using and Infix applies --- .../src/dotty/tools/dotc/ast/Desugar.scala | 21 ++++++++++--------- compiler/src/dotty/tools/dotc/ast/Trees.scala | 11 +++++----- compiler/src/dotty/tools/dotc/ast/untpd.scala | 9 +------- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../dotty/tools/dotc/typer/EtaExpansion.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 22 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 92111d7ac0b0..8de5da6eb390 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -537,7 +537,7 @@ object desugar { val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) => val app = Apply(nu, vparams.map(refOfDef)) vparams match { - case vparam :: _ if vparam.mods.is(Given) => app.setUsingApply() + case vparam :: _ if vparam.mods.is(Given) => app.setApplyKind(ApplyKind.Using) case _ => app } } @@ -1188,18 +1188,19 @@ object desugar { case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs) case _ => arg } - def makeOp(fn: Tree, arg: Tree, selectPos: Span) = { - val args: List[Tree] = arg match { - case Parens(arg) => assignToNamedArg(arg) :: Nil - case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed - args.mapConserve(assignToNamedArg) - case _ => arg :: Nil - } + def makeOp(fn: Tree, arg: Tree, selectPos: Span) = val sel = Select(fn, op.name).withSpan(selectPos) if (left.sourcePos.endLine < op.sourcePos.startLine) sel.pushAttachment(MultiLineInfix, ()) - InfixApply(sel, args) - } + arg match + case Parens(arg) => + Apply(sel, assignToNamedArg(arg) :: Nil) + case Tuple(Nil) => + Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixUnit) + case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed + Apply(sel, args.mapConserve(assignToNamedArg)) + case _ => + Apply(sel, arg :: Nil) if (isLeftAssoc(op.name)) makeOp(left, right, Span(left.span.start, op.span.end, op.span.start)) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 7b7d5bd5ecd4..4b4b3a7ff9de 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -444,20 +444,21 @@ object Trees { } enum ApplyKind: - case Regular, Using, Infix + case Regular // r.f(x) + case Using // r.f(using x) + case InfixUnit // r f (), needs to be treated specially for an error message in typedApply /** fun(args) */ case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { type ThisTree[-T >: Untyped] = Apply[T] - def setUsingApply() = - putAttachment(untpd.ApplyGiven, ()) + def setApplyKind(kind: ApplyKind) = + putAttachment(untpd.KindOfApply, kind) this def applyKind: ApplyKind = - if hasAttachment(untpd.ApplyGiven) then ApplyKind.Using - else ApplyKind.Regular + attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular) } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 4ab403ce7d1a..3b9229a22cd3 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -134,13 +134,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case _ => name } - /** Short-lived class created by desugar.binop and used by typedApply to - * improve error messages for infix applications - */ - class InfixApply(fun: Tree, args: List[Tree])(implicit @constructorOnly src: SourceFile) - extends Apply(fun, args): - override def applyKind = ApplyKind.Infix - case class Number(digits: String, kind: NumberKind)(implicit @constructorOnly src: SourceFile) extends TermTree enum NumberKind { @@ -351,7 +344,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { val OriginalSymbol: Property.Key[Symbol] = Property.Key() /** Property key for contextual Apply trees of the form `fn given arg` */ - val ApplyGiven: Property.StickyKey[Unit] = Property.StickyKey() + val KindOfApply: Property.StickyKey[ApplyKind] = Property.StickyKey() // ------ Creation methods for untyped only ----------------- diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 6c5eaf315ffa..5062b2225369 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2354,7 +2354,7 @@ object Parsers { def mkApply(fn: Tree, args: (List[Tree], Boolean)): Tree = val res = Apply(fn, args._1) - if args._2 then res.setUsingApply() + if args._2 then res.setApplyKind(ApplyKind.Using) res val argumentExpr: () => Tree = () => expr(Location.InArgs) match diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 145fdc5cedff..788f0acd3e99 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -615,7 +615,7 @@ trait Applications extends Compatibility { case arg :: args1 => val msg = arg match case untpd.Tuple(Nil) - if applyKind == ApplyKind.Infix && funType.widen.isNullaryMethod => + if applyKind == ApplyKind.InfixUnit && funType.widen.isNullaryMethod => i"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()" case _ => i"too many arguments for $methString" diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 153315f2ec0f..dea583c072f1 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -242,7 +242,7 @@ object EtaExpansion extends LiftImpure { if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam) ids = ids.init :+ repeated(ids.last) val app = Apply(lifted, ids) - if (mt.isContextualMethod) app.setUsingApply() + if (mt.isContextualMethod) app.setApplyKind(ApplyKind.Using) val body = if (isLastApplication) app else PostfixOp(app, Ident(nme.WILDCARD)) val fn = if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ec5108f35a54..60f628254d40 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3047,7 +3047,7 @@ class Typer extends Namer } tryEither { val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs) - if (wtp.isContextualMethod) app.setUsingApply() + if (wtp.isContextualMethod) app.setApplyKind(ApplyKind.Using) typr.println(i"try with default implicit args $app") typed(app, pt, locked) } { (_, _) => From 6de432ae8bf12391d453e0fb3268ee1d288d083a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Jun 2020 22:09:31 +0200 Subject: [PATCH 5/5] Update check file --- tests/neg/i2033.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i2033.check b/tests/neg/i2033.check index 181bf05b3f22..91ee4434c937 100644 --- a/tests/neg/i2033.check +++ b/tests/neg/i2033.check @@ -1,7 +1,7 @@ -- Error: tests/neg/i2033.scala:7:30 ----------------------------------------------------------------------------------- 7 | val arr = bos toByteArray () // error | ^^ - |can't supply unit value with infix notation because nullary method method toByteArray: (): Array[Byte] takes no arguments; use dotted invocation instead: (...).toByteArray() + |can't supply unit value with infix notation because nullary method toByteArray: (): Array[Byte] takes no arguments; use dotted invocation instead: (...).toByteArray() -- [E007] Type Mismatch Error: tests/neg/i2033.scala:20:35 ------------------------------------------------------------- 20 | val out = new ObjectOutputStream(println) // error | ^^^^^^^