diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8171512b7245..f59b73491819 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -359,13 +359,11 @@ object Parsers { recur(false, false) end statSepOrEnd - def rewriteNotice(version: String = "3.0", additionalOption: String = "") = { - val optionStr = if (additionalOption.isEmpty) "" else " " ++ additionalOption - i"\nThis construct can be rewritten automatically under$optionStr -rewrite -source $version-migration." - } + def rewriteNotice(version: SourceVersion = `3.0-migration`, additionalOption: String = "") = + Message.rewriteNotice("This construct", version, additionalOption) def syntaxVersionError(option: String, span: Span) = - syntaxError(em"""This construct is not allowed under $option.${rewriteNotice("3.0", option)}""", span) + syntaxError(em"""This construct is not allowed under $option.${rewriteNotice(`3.0-migration`, option)}""", span) def rewriteToNewSyntax(span: Span = Span(in.offset)): Boolean = { if (in.newSyntax) { @@ -2084,7 +2082,7 @@ object Parsers { in.nextToken() if isVarargSplice then report.errorOrMigrationWarning( - em"The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead${rewriteNotice("future")}", + em"The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead${rewriteNotice(`future-migration`)}", in.sourcePos(uscoreStart), future) if sourceVersion == `future-migration` then @@ -2160,7 +2158,7 @@ object Parsers { val t = if (in.token == COLON && location == Location.InBlock) { report.errorOrMigrationWarning( - s"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice("future")}", + s"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice(`future-migration`)}", source.atSpan(Span(start, in.lastOffset)), from = future) in.nextToken() @@ -3186,7 +3184,7 @@ object Parsers { def wildcardSelector() = if in.token == USCORE && sourceVersion.isAtLeast(future) then report.errorOrMigrationWarning( - em"`_` is no longer supported for a wildcard import; use `*` instead${rewriteNotice("future")}", + em"`_` is no longer supported for a wildcard import; use `*` instead${rewriteNotice(`future-migration`)}", in.sourcePos(), from = future) patch(source, Span(in.offset, in.offset + 1), "*") @@ -3205,7 +3203,7 @@ object Parsers { if in.token == ARROW || isIdent(nme.as) then if in.token == ARROW && sourceVersion.isAtLeast(future) then report.errorOrMigrationWarning( - em"The import renaming `a => b` is no longer supported ; use `a as b` instead${rewriteNotice("future")}", + em"The import renaming `a => b` is no longer supported ; use `a as b` instead${rewriteNotice(`future-migration`)}", in.sourcePos(), from = future) patch(source, Span(in.offset, in.offset + 2), diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index e3f81adeee8a..6498f42a5eb6 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -368,4 +368,9 @@ object Formatting { /** Explicit syntax highlighting */ def hl(s: String)(using Context): String = SyntaxHighlighting.highlight(s) + + /** Explicitly highlight a string with the same formatting as used for keywords */ + def hlAsKeyword(str: String)(using Context): String = + if str.isEmpty || ctx.settings.color.value == "never" then str + else s"${SyntaxHighlighting.KeywordColor}$str${SyntaxHighlighting.NoColor}" } diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 0db5d355e963..830516a2ebfd 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -2,6 +2,9 @@ package dotty.tools package dotc package reporting +import core.Contexts.*, core.Decorators.*, core.Mode +import config.SourceVersion + import scala.language.unsafeNulls import scala.annotation.threadUnsafe @@ -15,6 +18,16 @@ object Message { * see where old errors still exist */ implicit def toNoExplanation(str: => String): Message = NoExplanation(str) + + def rewriteNotice(what: String, version: SourceVersion | Null = null, options: String = "")(using Context): String = + if !ctx.mode.is(Mode.Interactive) then + val sourceStr = if version != null then i"-source $version" else "" + val optionStr = + if options.isEmpty then sourceStr + else if sourceStr.isEmpty then options + else i"$sourceStr $options" + i"\n$what can be rewritten automatically under -rewrite $optionStr." + else "" } /** A `Message` contains all semantic information necessary to easily diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 394aad7a078c..5f0abaf8904d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -37,6 +37,7 @@ import transform.patmat.SpaceEngine.isIrrefutable import config.Feature import config.Feature.sourceVersion import config.SourceVersion._ +import printing.Formatting.hlAsKeyword import transform.TypeUtils.* import collection.mutable @@ -834,11 +835,12 @@ trait Checking { case NonConforming => sel.srcPos case RefutableExtractor => pat.source.atSpan(pat.span union sel.span) else pat.srcPos + def rewriteMsg = Message.rewriteNotice("This patch", `future-migration`) report.warning( em"""$message | |If $usage is intentional, this can be communicated by $fix, - |which $addendum.${err.rewriteNotice}""", + |which $addendum.$rewriteMsg""", pos) false } @@ -992,10 +994,10 @@ trait Checking { ("extractor", (n: Name) => s"prefix syntax $n(...)") else ("method", (n: Name) => s"method syntax .$n(...)") + def rewriteMsg = Message.rewriteNotice("The latter", options = "-deprecation") report.deprecationWarning( - i"""Alphanumeric $kind $name is not declared `infix`; it should not be used as infix operator. - |The operation can be rewritten automatically to `$name` under -deprecation -rewrite. - |Or rewrite to ${alternative(name)} manually.""", + i"""Alphanumeric $kind $name is not declared ${hlAsKeyword("infix")}; it should not be used as infix operator. + |Instead, use ${alternative(name)} or backticked identifier `$name`.$rewriteMsg""", tree.op.srcPos) if (ctx.settings.deprecation.value) { patch(Span(tree.op.span.start, tree.op.span.start), "`") diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index b779df03bd95..b0b8e4563c39 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -153,15 +153,6 @@ object ErrorReporting { | |The tests were made under $constraintText""" - /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing - * all occurrences of `${X}` where `X` is in `paramNames` with the - * corresponding shown type in `args`. - */ - - def rewriteNotice: String = - if Feature.migrateTo3 then "\nThis patch can be inserted automatically under -rewrite." - else "" - def whyFailedStr(fail: FailedExtension) = i""" failed with | @@ -285,6 +276,10 @@ class ImplicitSearchError( } } + /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing + * all occurrences of `${X}` where `X` is in `paramNames` with the + * corresponding shown type in `args`. + */ private def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = { def translate(name: String): Option[String] = { val idx = paramNames.indexOf(name) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f4b744ecf66d..a13ea88f14e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3452,10 +3452,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty then // Under future-migration, don't infer implicit arguments yet for parameters // coming from context bounds. Issue a warning instead and offer a patch. + def rewriteMsg = Message.rewriteNotice("This code", `future-migration`) report.migrationWarning( em"""Context bounds will map to context parameters. - |A `using` clause is needed to pass explicit arguments to them. - |This code can be rewritten automatically using -rewrite""", tree.srcPos) + |A `using` clause is needed to pass explicit arguments to them.$rewriteMsg""", tree.srcPos) patch(Span(pt.args.head.span.start), "using ") tree else diff --git a/compiler/test-resources/repl/rewrite-messages b/compiler/test-resources/repl/rewrite-messages new file mode 100644 index 000000000000..eee2fe034c43 --- /dev/null +++ b/compiler/test-resources/repl/rewrite-messages @@ -0,0 +1,17 @@ +// scalac: -source:future-migration -deprecation -Werror +scala> import scala.util._ +-- Error: ---------------------------------------------------------------------- +1 | import scala.util._ + | ^ + | `_` is no longer supported for a wildcard import; use `*` instead + +scala> extension (x: Int) def foo(y: Int) = x + y +def foo(x: Int)(y: Int): Int + +scala> 2 foo 4 +-- Error: ---------------------------------------------------------------------- +1 | 2 foo 4 + | ^^^ + |Alphanumeric method foo is not declared infix; it should not be used as infix operator. + |Instead, use method syntax .foo(...) or backticked identifier `foo`. +1 error found diff --git a/tests/neg/refutable-pattern-binding-messages.check b/tests/neg/refutable-pattern-binding-messages.check index 05a7e7963a37..6cf67466d507 100644 --- a/tests/neg/refutable-pattern-binding-messages.check +++ b/tests/neg/refutable-pattern-binding-messages.check @@ -5,6 +5,7 @@ | | If this usage is intentional, this can be communicated by adding the `case` keyword before the full pattern, | which will result in a filtering for expression (using `withFilter`). + | This patch can be rewritten automatically under -rewrite -source future-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:11:11 ----------------------------------------------------- 11 | for ((x: String) <- xs) do () // error: pattern type more specialized | ^^^^^^ @@ -12,6 +13,7 @@ | | If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern, | which will result in a filtering for expression (using `withFilter`). + | This patch can be rewritten automatically under -rewrite -source future-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:15:13 ----------------------------------------------------- 15 | for none @ None <- ys do () // error: pattern type does not match | ^^^^ @@ -19,6 +21,7 @@ | | If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern, | which will result in a filtering for expression (using `withFilter`). + | This patch can be rewritten automatically under -rewrite -source future-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:5:14 ------------------------------------------------------ 5 | val Positive(p) = 5 // error: refutable extractor | ^^^^^^^^^^^^^^^ @@ -26,6 +29,7 @@ | | If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression, | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source future-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:10:20 ----------------------------------------------------- 10 | val i :: is = List(1, 2, 3) // error: pattern type more specialized | ^^^^^^^^^^^^^ @@ -33,6 +37,7 @@ | | If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression, | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source future-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:16:10 ----------------------------------------------------- 16 | val 1 = 2 // error: pattern type does not match | ^ @@ -40,3 +45,4 @@ | | If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression, | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source future-migration. diff --git a/tests/neg/rewrite-messages.check b/tests/neg/rewrite-messages.check new file mode 100644 index 000000000000..3ee081edbed2 --- /dev/null +++ b/tests/neg/rewrite-messages.check @@ -0,0 +1,11 @@ +-- Error: tests/neg/rewrite-messages.scala:3:18 ------------------------------------------------------------------------ +3 |import scala.util._ // error + | ^ + | `_` is no longer supported for a wildcard import; use `*` instead + | This construct can be rewritten automatically under -rewrite -source future-migration. +-- Error: tests/neg/rewrite-messages.scala:7:4 ------------------------------------------------------------------------- +7 | 2 foo 4 // error + | ^^^ + | Alphanumeric method foo is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .foo(...) or backticked identifier `foo`. + | The latter can be rewritten automatically under -rewrite -deprecation. diff --git a/tests/neg/rewrite-messages.scala b/tests/neg/rewrite-messages.scala new file mode 100644 index 000000000000..b4f2bf75bfe3 --- /dev/null +++ b/tests/neg/rewrite-messages.scala @@ -0,0 +1,8 @@ +// scalac: -source:future-migration -deprecation -Werror + +import scala.util._ // error + +object Test { + extension (x: Int) def foo(y: Int) = x + y + 2 foo 4 // error +}