diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c8376564005e..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,17 +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) => 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, ()) - Apply(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 fc53e29ad117..4b4b3a7ff9de 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -443,15 +443,26 @@ object Trees { def forwardTo: Tree[T] = fun } + enum ApplyKind: + 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 isUsingApply = hasAttachment(untpd.ApplyGiven) - def setUsingApply() = { putAttachment(untpd.ApplyGiven, ()); this } + def setApplyKind(kind: ApplyKind) = + putAttachment(untpd.KindOfApply, kind) + this + + def applyKind: ApplyKind = + attachmentOrElse(untpd.KindOfApply, 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..3b9229a22cd3 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -344,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 ----------------- @@ -780,5 +780,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/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/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..788f0acd3e99 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.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" + 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/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/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..60f628254d40 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 _ => @@ -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) } { (_, _) => @@ -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..91ee4434c937 --- /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 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/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 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()}; }