From ca3f7c6ea9b9c7f37cb2d07158652868dc3d084e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Mar 2023 14:33:53 +0100 Subject: [PATCH 1/2] Improve missing argument list error Fixes #17123 --- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 10 +++ .../tools/dotc/typer/ErrorReporting.scala | 22 +++-- tests/neg/i16820.check | 24 ++++-- tests/neg/i17123.check | 86 +++++++++++++++++++ tests/neg/i17123.scala | 22 +++++ tests/neg/i7816.scala | 2 +- tests/neg/indent-colons.check | 28 ++++-- 8 files changed, 177 insertions(+), 18 deletions(-) create mode 100644 tests/neg/i17123.check create mode 100644 tests/neg/i17123.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 65ebeb02c667..1fe38ce5e801 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -191,6 +191,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ValueDiscardingID // errorNumber 175 case UnusedNonUnitValueID // errorNumber 176 case ConstrProxyShadowsID // errorNumber 177 + case MissingArgumentListID // errorNumber: 178 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 9875c508c9fd..fe2df5ca6016 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1501,6 +1501,16 @@ class MissingArgument(pname: Name, methString: String)(using Context) else s"missing argument for parameter $pname of $methString" def explain(using Context) = "" +class MissingArgumentList(method: String, sym: Symbol)(using Context) + extends TypeMsg(MissingArgumentListID) { + def msg(using Context) = + val symDcl = if sym.exists then "\n\n " + sym.showDcl else "" + i"missing argument list for $method$symDcl" + def explain(using Context) = { + i"""Unapplied methods are only converted to functions when a function type is expected.""" + } +} + class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context) extends TypeMismatchMsg( if which == "lower" then bound else tpe, diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 32b5fde689ec..126d109889e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -41,12 +41,24 @@ object ErrorReporting { errorType(WrongNumberOfTypeArgs(fntpe, expectedArgs, actual), pos) def missingArgs(tree: Tree, mt: Type)(using Context): Unit = + def isCallableWithoutArgumentsLists(mt: Type): Boolean = mt match + case pt: PolyType => isCallableWithoutArgumentsLists(pt.resType) + case mt: MethodType if mt.isImplicitMethod => isCallableWithoutArgumentsLists(mt.resType) + case mt: MethodType => false + case _ => true + def isCallableWithSingleEmptyArgumentList(mt: Type): Boolean = + mt match + case mt: MethodType if mt.paramNames.isEmpty => isCallableWithoutArgumentsLists(mt.resType) + case mt: MethodType if mt.isImplicitMethod => isCallableWithSingleEmptyArgumentList(mt.resType) + case pt: PolyType => isCallableWithSingleEmptyArgumentList(pt.resType) + case _ => false val meth = err.exprStr(methPart(tree)) - mt match - case mt: MethodType if mt.paramNames.isEmpty => - report.error(MissingEmptyArgumentList(meth), tree.srcPos) - case _ => - report.error(em"missing arguments for $meth", tree.srcPos) + val info = if tree.symbol.exists then tree.symbol.info else mt + if isCallableWithSingleEmptyArgumentList(info) then + report.error(MissingEmptyArgumentList(meth), tree.srcPos) + else + report.error(MissingArgumentList(meth, tree.symbol), tree.srcPos) + def matchReductionAddendum(tps: Type*)(using Context): String = val collectMatchTrace = new TypeAccumulator[String]: diff --git a/tests/neg/i16820.check b/tests/neg/i16820.check index f51bfafef899..48824d683244 100644 --- a/tests/neg/i16820.check +++ b/tests/neg/i16820.check @@ -1,18 +1,30 @@ --- Error: tests/neg/i16820.scala:5:11 ---------------------------------------------------------------------------------- +-- [E178] Type Error: tests/neg/i16820.scala:5:11 ---------------------------------------------------------------------- 5 | val x1 = f // error | ^ - | missing arguments for method f in object Test + | missing argument list for method f in object Test + | + | def f(xs: Int*): Int + | + | longer explanation available when compiling with `-explain` -- [E100] Syntax Error: tests/neg/i16820.scala:6:11 -------------------------------------------------------------------- 6 | val x2 = g // error | ^ | method g in object Test must be called with () argument | | longer explanation available when compiling with `-explain` --- Error: tests/neg/i16820.scala:7:40 ---------------------------------------------------------------------------------- +-- [E178] Type Error: tests/neg/i16820.scala:7:40 ---------------------------------------------------------------------- 7 | val x3 = java.nio.file.Paths.get(".").toRealPath // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | missing arguments for method toRealPath in trait Path --- Error: tests/neg/i16820.scala:11:14 --------------------------------------------------------------------------------- + | missing argument list for method toRealPath in trait Path + | + | def toRealPath(x$0: java.nio.file.LinkOption*): java.nio.file.Path + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i16820.scala:11:14 --------------------------------------------------------------------- 11 |def test = Foo(3) // error | ^^^^^^ - | missing arguments for method apply in object Foo + | missing argument list for method apply in object Foo + | + | def apply(x: Int)(xs: String*): Foo + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i17123.check b/tests/neg/i17123.check new file mode 100644 index 000000000000..e858de67b73a --- /dev/null +++ b/tests/neg/i17123.check @@ -0,0 +1,86 @@ +-- [E100] Syntax Error: tests/neg/i17123.scala:7:2 --------------------------------------------------------------------- +7 | m1 // error + | ^^ + | method m1 in object ConfusingErrorMessage must be called with () argument + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:9:2 ----------------------------------------------------------------------- +9 | m2 // error + | ^^ + | missing argument list for method m2 in object ConfusingErrorMessage + | + | def m2()(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:10:4 ---------------------------------------------------------------------- +10 | m2() // error + | ^^^^ + | missing argument list for method m2 in object ConfusingErrorMessage + | + | def m2()(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:11:2 ---------------------------------------------------------------------- +11 | m3 // error + | ^^ + | missing argument list for method m3 in object ConfusingErrorMessage + | + | def m3()()(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:12:4 ---------------------------------------------------------------------- +12 | m3() // error + | ^^^^ + | missing argument list for method m3 in object ConfusingErrorMessage + | + | def m3()()(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:13:6 ---------------------------------------------------------------------- +13 | m3()() // error + | ^^^^^^ + | missing argument list for method m3 in object ConfusingErrorMessage + | + | def m3()()(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:15:2 ---------------------------------------------------------------------- +15 | f3 // error + | ^^ + | missing argument list for method f3 in object ConfusingErrorMessage + | + | def f3()(i: Int)(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:16:2 ---------------------------------------------------------------------- +16 | f3() // error + | ^^^^ + | missing argument list for method f3 in object ConfusingErrorMessage + | + | def f3()(i: Int)(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:17:6 ---------------------------------------------------------------------- +17 | f3()(2) // error + | ^^^^^^^ + | missing argument list for method f3 in object ConfusingErrorMessage + | + | def f3()(i: Int)(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:19:2 ---------------------------------------------------------------------- +19 | i3 // error + | ^^ + | missing argument list for method i3 in object ConfusingErrorMessage + | + | def i3()(using d: DummyImplicit)(): Unit + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i17123.scala:20:2 ---------------------------------------------------------------------- +20 | i3() // error + | ^^^^ + | missing argument list for method i3 in object ConfusingErrorMessage + | + | def i3()(using d: DummyImplicit)(): Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i17123.scala b/tests/neg/i17123.scala new file mode 100644 index 000000000000..6547a375fec3 --- /dev/null +++ b/tests/neg/i17123.scala @@ -0,0 +1,22 @@ +object ConfusingErrorMessage { + def m1() = () + def m2()() = () + def m3()()() = () + def f3()(i: Int)() = () + def i3()(using d: DummyImplicit)() = () + m1 // error + m1() + m2 // error + m2() // error + m3 // error + m3() // error + m3()() // error + m3()()() + f3 // error + f3() // error + f3()(2) // error + f3()(2)() + i3 // error + i3() // error + i3()() +} diff --git a/tests/neg/i7816.scala b/tests/neg/i7816.scala index f1eed694a085..41dd6c2ea98e 100644 --- a/tests/neg/i7816.scala +++ b/tests/neg/i7816.scala @@ -1,4 +1,4 @@ object A { def f()(>) = ??? // error - import f.NonExistent // error + import f.NonExistent } \ No newline at end of file diff --git a/tests/neg/indent-colons.check b/tests/neg/indent-colons.check index 102d41592014..f77d491f8b8f 100644 --- a/tests/neg/indent-colons.check +++ b/tests/neg/indent-colons.check @@ -47,15 +47,31 @@ | Not found: file | | longer explanation available when compiling with `-explain` --- Error: tests/neg/indent-colons.scala:5:2 ---------------------------------------------------------------------------- +-- [E178] Type Error: tests/neg/indent-colons.scala:5:2 ---------------------------------------------------------------- 5 | tryEither: // error | ^^^^^^^^^ - | missing arguments for method tryEither --- Error: tests/neg/indent-colons.scala:11:2 --------------------------------------------------------------------------- + | missing argument list for method tryEither + | + | def tryEither[T](x: T)(y: Int => T): T + | + | where: T is a type variable + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/indent-colons.scala:11:2 --------------------------------------------------------------- 11 | tryEither: // error | ^^^^^^^^^ - | missing arguments for method tryEither --- Error: tests/neg/indent-colons.scala:18:2 --------------------------------------------------------------------------- + | missing argument list for method tryEither + | + | def tryEither[T](x: T)(y: Int => T): T + | + | where: T is a type variable + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/indent-colons.scala:18:2 --------------------------------------------------------------- 18 | Some(3).fold: // error | ^^^^^^^^^^^^ - | missing arguments for method fold in class Option + | missing argument list for method fold in class Option + | + | final def fold[B](ifEmpty: => B)(f: A => B): B + | + | longer explanation available when compiling with `-explain` From 3420f1b400af1b1ba8f3e51bc384c718156f041d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Mar 2023 16:53:20 +0100 Subject: [PATCH 2/2] Improve highlight of decl --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 ++++++ compiler/src/dotty/tools/dotc/inlines/Inlines.scala | 3 +-- compiler/src/dotty/tools/dotc/reporting/messages.scala | 2 +- compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala | 2 +- .../dotty/tools/dotc/printing/SyntaxHighlightingTests.scala | 3 +-- .../test/dotty/tools/dotc/reporting/ErrorMessagesTest.scala | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 2f28975dd066..e0e43169820a 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -444,6 +444,12 @@ object Contexts { def useColors: Boolean = base.settings.color.value == "always" + def withColors: FreshContext = + fresh.setSetting(ctx.settings.color, "always") + + def withoutColors: FreshContext = + fresh.setSetting(ctx.settings.color, "never") + /** Is the explicit nulls option set? */ def explicitNulls: Boolean = base.settings.YexplicitNulls.value diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index f504a4034631..36dc8a642afc 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -381,8 +381,7 @@ object Inlines: /** Expand call to scala.compiletime.codeOf */ def codeOf(arg: Tree, pos: SrcPos)(using Context): Tree = - val ctx1 = ctx.fresh.setSetting(ctx.settings.color, "never") - Literal(Constant(arg.show(using ctx1))).withSpan(pos.span) + Literal(Constant(arg.show(using ctx.withoutColors))).withSpan(pos.span) end Intrinsics /** Produces an inlined version of `call` via its `inlined` method. diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index fe2df5ca6016..fba08fd84d0c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1504,7 +1504,7 @@ class MissingArgument(pname: Name, methString: String)(using Context) class MissingArgumentList(method: String, sym: Symbol)(using Context) extends TypeMsg(MissingArgumentListID) { def msg(using Context) = - val symDcl = if sym.exists then "\n\n " + sym.showDcl else "" + val symDcl = if sym.exists then "\n\n " + hl(sym.showDcl(using ctx.withoutColors)) else "" i"missing argument list for $method$symDcl" def explain(using Context) = { i"""Unapplied methods are only converted to functions when a function type is expected.""" diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index a575238f7cd4..1949304ca287 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -3080,7 +3080,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler lazy val ConstantCode: Printer[Constant] = new Printer[Constant]: def show(const: Constant): String = - const.show(using ctx.fresh.setSetting(ctx.settings.color, "never")) + const.show(using ctx.withoutColors) lazy val ConstantStructure: Printer[Constant] = new Printer[Constant]: def show(const: Constant): String = diff --git a/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala b/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala index 2f35ccb35434..2e4b7bf1bb3f 100644 --- a/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala +++ b/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala @@ -12,8 +12,7 @@ class SyntaxHighlightingTests extends DottyTest { import SyntaxHighlighting._ private def test(source: String, expected: String): Unit = { - val testCtx = ctx.fresh.setSetting(ctx.settings.color, "always") - val highlighted = SyntaxHighlighting.highlight(source)(using testCtx) + val highlighted = SyntaxHighlighting.highlight(source)(using ctx.withColors) .replace(NoColor, ">") .replace(CommentColor, "