From 55a7d78efe557d48d746db94bcf4304834656306 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 11 Sep 2025 11:08:28 -0700 Subject: [PATCH 1/6] Unused param due to revert --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 77369c828113..5d23162d3f1c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1523,7 +1523,7 @@ object Parsers { if MigrationVersion.Scala2to3.needsPatch then patch(source, Span(in.offset), " ") - def possibleTemplateStart(isNew: Boolean = false): Unit = + def possibleTemplateStart(): Unit = in.observeColonEOL(inTemplate = true) if in.token == COLONeol then if in.lookahead.token == END then in.token = NEWLINE @@ -2866,7 +2866,7 @@ object Parsers { val parents = if in.isNestedStart then Nil else constrApps(exclude = COMMA) - possibleTemplateStart(isNew = true) + possibleTemplateStart() parents match { case parent :: Nil if !in.isNestedStart => reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent) From 8770f9d9da55ce20115348e881e0e3a8cd81e6de Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 11 Sep 2025 14:02:49 -0700 Subject: [PATCH 2/6] Warn if empty template name has trailing colon This is to guard the edge case `object X_:` where the user may or may not have intended colon syntax. The next line does not tell us, since it may be indented yet not nested. Therefore, any empty template with a suspicious name will warn. Non-empty templates are given a pass even if written `object X_: :`. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../src/dotty/tools/dotc/core/NameOps.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 39 +++++++++++++------ .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 14 ++++--- tests/neg/i16072.scala | 3 ++ tests/warn/i16072.check | 24 ++++++++++++ tests/warn/i16072.scala | 26 +++++++++++++ 8 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 tests/neg/i16072.scala create mode 100644 tests/warn/i16072.check create mode 100644 tests/warn/i16072.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 89933fcab8a2..bf52f24b75b2 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -44,6 +44,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { extends MemberDef { type ThisTree[+T <: Untyped] <: Trees.NameTree[T] & Trees.MemberDef[T] & ModuleDef def withName(name: Name)(using Context): ModuleDef = cpy.ModuleDef(this)(name.toTermName, impl) + def isBackquoted: Boolean = hasAttachment(Backquoted) } /** An untyped template with a derives clause. Derived parents are added to the end diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 766cf4abf8c4..318eeab1a1cd 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -89,7 +89,7 @@ object NameOps { // Ends with operator characters while i >= 0 && isOperatorPart(name(i)) do i -= 1 if i == -1 then return true - // Optionnally prefixed with alpha-numeric characters followed by `_` + // Optionally prefixed with alpha-numeric characters followed by `_` if name(i) != '_' then return false while i >= 0 && isIdentifierPart(name(i)) do i -= 1 i == -1 diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5d23162d3f1c..1041de22847e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3784,6 +3784,18 @@ object Parsers { /* -------- DEFS ------------------------------------------- */ def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] = + def checkName(): Unit = + def checkName(name: Name): Unit = + if !name.isEmpty + && !Chars.isOperatorPart(name.firstCodePoint) // warn a_: not :: + && name.endsWith(":") + then + report.warning(AmbiguousTemplateName(md), md.namePos) + md match + case md @ TypeDef(name, impl: Template) if impl.body.isEmpty && !md.isBackquoted => checkName(name) + case md @ ModuleDef(name, impl) if impl.body.isEmpty && !md.isBackquoted => checkName(name) + case _ => + checkName() md.withMods(mods).setComment(in.getDocComment(start)) type ImportConstr = (Tree, List[ImportSelector]) => Tree @@ -4233,14 +4245,15 @@ object Parsers { /** ClassDef ::= id ClassConstr TemplateOpt */ - def classDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) { - classDefRest(start, mods, ident().toTypeName) - } + def classDef(start: Offset, mods: Modifiers): TypeDef = + val td = atSpan(start, nameStart): + classDefRest(mods, ident().toTypeName) + finalizeDef(td, mods, start) - def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = + def classDefRest(mods: Modifiers, name: TypeName): TypeDef = val constr = classConstr(if mods.is(Case) then ParamOwner.CaseClass else ParamOwner.Class) val templ = templateOpt(constr) - finalizeDef(TypeDef(name, templ), mods, start) + TypeDef(name, templ) /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsTermParamClauses */ @@ -4258,11 +4271,15 @@ object Parsers { /** ObjectDef ::= id TemplateOpt */ - def objectDef(start: Offset, mods: Modifiers): ModuleDef = atSpan(start, nameStart) { - val name = ident() - val templ = templateOpt(emptyConstructor) - finalizeDef(ModuleDef(name, templ), mods, start) - } + def objectDef(start: Offset, mods: Modifiers): ModuleDef = + val md = atSpan(start, nameStart): + val nameIdent = termIdent() + val templ = templateOpt(emptyConstructor) + ModuleDef(nameIdent.name.asTermName, templ) + .tap: md => + if nameIdent.isBackquoted then + md.pushAttachment(Backquoted, ()) + finalizeDef(md, mods, start) private def checkAccessOnly(mods: Modifiers, caseStr: String): Modifiers = // We allow `infix` and `into` on `enum` definitions. @@ -4494,7 +4511,7 @@ object Parsers { Template(constr, parents, Nil, EmptyValDef, Nil) else if !newSyntaxAllowed || in.token == WITH && tparams.isEmpty && vparamss.isEmpty - // if new syntax is still allowed and there are parameters, they mist be new style conditions, + // if new syntax is still allowed and there are parameters, they must be new style conditions, // so old with-style syntax would not be allowed. then withTemplate(constr, parents) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 103687abdbff..02da5453c2f5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -235,6 +235,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case CannotInstantiateQuotedTypeVarID // errorNumber: 219 case DefaultShadowsGivenID // errorNumber: 220 case RecurseWithDefaultID // errorNumber: 221 + case AmbiguousTemplateNameID // errorNumber: 222 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 2bad86f8967b..34aca931c08f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -13,8 +13,10 @@ import parsing.Tokens import printing.Highlighting.* import printing.Formatting import ErrorMessageID.* -import ast.Trees +import ast.Trees.* import ast.desugar +import ast.tpd +import ast.untpd import config.{Feature, MigrationVersion, ScalaVersion} import transform.patmat.Space import transform.patmat.SpaceEngine @@ -25,9 +27,6 @@ import typer.Inferencing import scala.util.control.NonFatal import StdNames.nme import Formatting.{hl, delay} -import ast.Trees.* -import ast.untpd -import ast.tpd import scala.util.matching.Regex import java.util.regex.Matcher.quoteReplacement import cc.CaptureSet.IdentityCaptRefMap @@ -3109,7 +3108,7 @@ class MissingImplicitArgument( def msg(using Context): String = def formatMsg(shortForm: String)(headline: String = shortForm) = arg match - case arg: Trees.SearchFailureIdent[?] => + case arg: SearchFailureIdent[?] => arg.tpe match case _: NoMatchingImplicits => headline case tpe: SearchFailureType => @@ -3741,3 +3740,8 @@ final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(Recurs i"Recursive call used a default argument for parameter $name." override protected def explain(using Context): String = "It's more explicit to pass current or modified arguments in a recursion." + +class AmbiguousTemplateName(tree: NamedDefTree[?])(using Context) extends SyntaxMsg(AmbiguousTemplateNameID): + override protected def msg(using Context) = i"name `${tree.name}` should be enclosed in backticks" + override protected def explain(using Context): String = + "Names with trailing operator characters may fuse with a subsequent colon if not set off by backquotes or spaces." diff --git a/tests/neg/i16072.scala b/tests/neg/i16072.scala new file mode 100644 index 000000000000..870a9710c9b9 --- /dev/null +++ b/tests/neg/i16072.scala @@ -0,0 +1,3 @@ + +enum Oops_: + case Z // error // error expected { and } diff --git a/tests/warn/i16072.check b/tests/warn/i16072.check new file mode 100644 index 000000000000..decc43f99146 --- /dev/null +++ b/tests/warn/i16072.check @@ -0,0 +1,24 @@ +-- Warning: tests/warn/i16072.scala:4:2 -------------------------------------------------------------------------------- +4 | def x = 1 // warn too far right + | ^ + | Line is indented too far to the right, or a `{` or `:` is missing +-- [E222] Syntax Warning: tests/warn/i16072.scala:3:7 ------------------------------------------------------------------ +3 |object Hello_: // warn colon in name without backticks because the body is empty + | ^^^^^^^ + | name `Hello_:` should be enclosed in backticks + | + | longer explanation available when compiling with `-explain` +-- Deprecation Warning: tests/warn/i16072.scala:12:10 ------------------------------------------------------------------ +12 |object :: : // warn deprecated colon without backticks for operator name + | ^ + | `:` after symbolic operator is deprecated; use backticks around operator instead +-- Warning: tests/warn/i16072.scala:21:2 ------------------------------------------------------------------------------- +21 | def y = 1 // warn + | ^ + | Line is indented too far to the right, or a `{` or `:` is missing +-- [E222] Syntax Warning: tests/warn/i16072.scala:20:6 ----------------------------------------------------------------- +20 |class Uhoh_: // warn + | ^^^^^^ + | name `Uhoh_:` should be enclosed in backticks + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i16072.scala b/tests/warn/i16072.scala new file mode 100644 index 000000000000..7bfdbbe3813d --- /dev/null +++ b/tests/warn/i16072.scala @@ -0,0 +1,26 @@ +//> using options -deprecation + +object Hello_: // warn colon in name without backticks because the body is empty + def x = 1 // warn too far right + +object Goodbye_: : // nowarn if non-empty body without nit-picking about backticks + def x = 2 + +object `Byte_`: + def x = 3 + +object :: : // warn deprecated colon without backticks for operator name + def x = 42 + +object ::: // nowarn + +object Braces_: { // nowarn because body is non-empty with an EmptyTree +} + +class Uhoh_: // warn + def y = 1 // warn + +@main def hello = + println(Byte_) + println(Hello_:) // apparently user did forget a colon, see https://youforgotapercentagesignoracolon.com/ + println(x) From 2a2e053104eb706f01422e4f9f0de29996705197 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 11 Sep 2025 17:36:40 -0700 Subject: [PATCH 3/6] Improve message for colon fusion When the user accidentally writes `val x_: Int` where the colon belongs to the identifier as an operator suffix, tell them so. --- .../dotty/tools/dotc/parsing/Parsers.scala | 9 ++++++- tests/neg/i18020b.check | 26 +++++++++++++++++++ tests/neg/i18020b.scala | 8 ++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i18020b.check create mode 100644 tests/neg/i18020b.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 1041de22847e..2e7d599a5d76 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -4008,7 +4008,14 @@ object Parsers { val tpt = typedOpt() val rhs = if tpt.isEmpty || in.token == EQUALS then - accept(EQUALS) + if tpt.isEmpty && in.token != EQUALS then + lhs match + case Ident(name) :: Nil if name.endsWith(":") => + val help = i"; identifier ends in colon, did you mean `${name.toSimpleName.dropRight(1)}`: in backticks?" + syntaxErrorOrIncomplete(ExpectedTokenButFound(EQUALS, in.token, suffix = help)) + case _ => accept(EQUALS) + else + accept(EQUALS) val rhsOffset = in.offset subExpr() match case rhs0 @ Ident(name) if placeholderParams.nonEmpty && name == placeholderParams.head.name diff --git a/tests/neg/i18020b.check b/tests/neg/i18020b.check new file mode 100644 index 000000000000..64bfe981d7c3 --- /dev/null +++ b/tests/neg/i18020b.check @@ -0,0 +1,26 @@ +-- [E040] Syntax Error: tests/neg/i18020b.scala:2:17 ------------------------------------------------------------------- +2 |class i18020(a_: Int): // error + | ^^^ + | ':' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18020b.scala:3:12 ------------------------------------------------------------------- +3 | def f(b_: Int) = 42 // error + | ^^^ + | ':' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18020b.scala:4:10 ------------------------------------------------------------------- +4 | def g_: Int = 27 // error + | ^^^ + | '=' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18020b.scala:6:12 ------------------------------------------------------------------- +6 | val x_: Int = 1 // error + | ^^^ + | '=' expected, but identifier found; identifier ends in colon, did you mean `x_`: in backticks? +-- [E040] Syntax Error: tests/neg/i18020b.scala:7:12 ------------------------------------------------------------------- +7 | val y_: Int = 2 // error + | ^^^ + | '=' expected, but identifier found; identifier ends in colon, did you mean `y_`: in backticks? +-- [E006] Not Found Error: tests/neg/i18020b.scala:8:4 ----------------------------------------------------------------- +8 | x_ + y_ // error + | ^^ + | Not found: x_ - did you mean x_:? + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i18020b.scala b/tests/neg/i18020b.scala new file mode 100644 index 000000000000..76aff27380f9 --- /dev/null +++ b/tests/neg/i18020b.scala @@ -0,0 +1,8 @@ +// problems with colon fusion, a harder challenge than cold fusion +class i18020(a_: Int): // error + def f(b_: Int) = 42 // error + def g_: Int = 27 // error + def k = + val x_: Int = 1 // error + val y_: Int = 2 // error + x_ + y_ // error From ebd56e43f2d183204023c7f4a7b061e78cbde785 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 11 Sep 2025 17:53:23 -0700 Subject: [PATCH 4/6] Help with defdef --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++++ tests/neg/i18020b.check | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 2e7d599a5d76..2c9cc5372fba 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -4113,6 +4113,10 @@ object Parsers { tpt = scalaUnit if (in.token == LBRACE) expr() else EmptyTree + else if in.token == IDENTIFIER && paramss.isEmpty && name.endsWith(":") then + val help = i"; identifier ends in colon, did you mean `${name.toSimpleName.dropRight(1)}`: in backticks?" + syntaxErrorOrIncomplete(ExpectedTokenButFound(EQUALS, in.token, suffix = help)) + EmptyTree else if (!isExprIntro) syntaxError(MissingReturnType(), in.lastOffset) accept(EQUALS) diff --git a/tests/neg/i18020b.check b/tests/neg/i18020b.check index 64bfe981d7c3..1e2d22cacf99 100644 --- a/tests/neg/i18020b.check +++ b/tests/neg/i18020b.check @@ -9,7 +9,7 @@ -- [E040] Syntax Error: tests/neg/i18020b.scala:4:10 ------------------------------------------------------------------- 4 | def g_: Int = 27 // error | ^^^ - | '=' expected, but identifier found + | '=' expected, but identifier found; identifier ends in colon, did you mean `g_`: in backticks? -- [E040] Syntax Error: tests/neg/i18020b.scala:6:12 ------------------------------------------------------------------- 6 | val x_: Int = 1 // error | ^^^ From d3948330cc034d763d5eaa4ffe8929813e108e4d Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 19 Sep 2025 14:34:16 -0700 Subject: [PATCH 5/6] Promote to overloaded accept --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 2c9cc5372fba..913a2b1e798a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -328,6 +328,13 @@ object Parsers { if in.token == token then in.nextToken() offset + def accept(token: Int, help: String): Int = + val offset = in.offset + if in.token != token then + syntaxErrorOrIncomplete(ExpectedTokenButFound(token, in.token, suffix = help)) + if in.token == token then in.nextToken() + offset + def accept(name: Name): Int = { val offset = in.offset if !isIdent(name) then @@ -4012,7 +4019,7 @@ object Parsers { lhs match case Ident(name) :: Nil if name.endsWith(":") => val help = i"; identifier ends in colon, did you mean `${name.toSimpleName.dropRight(1)}`: in backticks?" - syntaxErrorOrIncomplete(ExpectedTokenButFound(EQUALS, in.token, suffix = help)) + accept(EQUALS, help) case _ => accept(EQUALS) else accept(EQUALS) @@ -4115,7 +4122,7 @@ object Parsers { else EmptyTree else if in.token == IDENTIFIER && paramss.isEmpty && name.endsWith(":") then val help = i"; identifier ends in colon, did you mean `${name.toSimpleName.dropRight(1)}`: in backticks?" - syntaxErrorOrIncomplete(ExpectedTokenButFound(EQUALS, in.token, suffix = help)) + accept(EQUALS, help) EmptyTree else if (!isExprIntro) syntaxError(MissingReturnType(), in.lastOffset) From 5e4d1c652b310e089c4eb6f2f90689ee8ea990e5 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 19 Sep 2025 16:17:40 -0700 Subject: [PATCH 6/6] Indentation warning is a syntax warning --- .../dotty/tools/dotc/parsing/Parsers.scala | 6 ++---- .../dotty/tools/dotc/parsing/Scanners.scala | 4 ++-- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 19 +++++++++++++++---- tests/warn/i16072.check | 12 ++++++++---- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 913a2b1e798a..2442af1ce429 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -710,9 +710,7 @@ object Parsers { if in.isNewLine && !(nextIndentWidth < startIndentWidth) then warning( if startIndentWidth <= nextIndentWidth then - em"""Line is indented too far to the right, or a `{` is missing before: - | - |${t.tryToShow}""" + IndentationWarning(missing = LBRACE, before = t.tryToShow) else in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth), in.next.offset @@ -727,7 +725,7 @@ object Parsers { if in.isNewLine then val nextIndentWidth = in.indentWidth(in.next.offset) if in.currentRegion.indentWidth < nextIndentWidth && in.currentRegion.closedBy == OUTDENT then - warning(em"Line is indented too far to the right, or a `{` or `:` is missing", in.next.offset) + warning(IndentationWarning(missing = Seq(LBRACE, COLONop)*), in.next.offset) /* -------- REWRITES ----------------------------------------------------------- */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 52e03de60dea..71fdb7d829dc 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -20,7 +20,7 @@ import config.Feature import config.Feature.{migrateTo3, sourceVersion} import config.SourceVersion.{`3.0`, `3.0-migration`} import config.MigrationVersion -import reporting.{NoProfile, Profile, Message} +import reporting.* import java.util.Objects import dotty.tools.dotc.reporting.Message.rewriteNotice @@ -650,7 +650,7 @@ object Scanners { if r.enclosing.isClosedByUndentAt(nextWidth) then insert(OUTDENT, offset) else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then - report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos()) + report.warning(IndentationWarning(isLeft = true, missing = RBRACE), sourcePos()) else if lastWidth < nextWidth || lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then if canStartIndentTokens.contains(lastToken) then diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 02da5453c2f5..f2118e7acae7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -236,6 +236,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case DefaultShadowsGivenID // errorNumber: 220 case RecurseWithDefaultID // errorNumber: 221 case AmbiguousTemplateNameID // errorNumber: 222 + case IndentationWarningID // errorNumber: 223 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 34aca931c08f..55bb2801f899 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -9,7 +9,7 @@ import Denotations.SingleDenotation import SymDenotations.SymDenotation import NameKinds.{WildcardParamName, ContextFunctionParamName} import parsing.Scanners.Token -import parsing.Tokens +import parsing.Tokens, Tokens.showToken import printing.Highlighting.* import printing.Formatting import ErrorMessageID.* @@ -1233,12 +1233,12 @@ extends ReferenceMsg(ForwardReferenceExtendsOverDefinitionID) { class ExpectedTokenButFound(expected: Token, found: Token, prefix: String = "", suffix: String = "")(using Context) extends SyntaxMsg(ExpectedTokenButFoundID) { - private def foundText = Tokens.showToken(found) + private def foundText = showToken(found) def msg(using Context) = val expectedText = if (Tokens.isIdentifier(expected)) "an identifier" - else Tokens.showToken(expected) + else showToken(expected) i"""$prefix$expectedText expected, but $foundText found$suffix""" def explain(using Context) = @@ -1940,7 +1940,7 @@ class ExtendFinalClass(clazz:Symbol, finalClazz: Symbol)(using Context) class ExpectedTypeBoundOrEquals(found: Token)(using Context) extends SyntaxMsg(ExpectedTypeBoundOrEqualsID) { - def msg(using Context) = i"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${Tokens.showToken(found)} found" + def msg(using Context) = i"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${showToken(found)} found" def explain(using Context) = i"""Type parameters and abstract types may be constrained by a type bound. @@ -3745,3 +3745,14 @@ class AmbiguousTemplateName(tree: NamedDefTree[?])(using Context) extends Syntax override protected def msg(using Context) = i"name `${tree.name}` should be enclosed in backticks" override protected def explain(using Context): String = "Names with trailing operator characters may fuse with a subsequent colon if not set off by backquotes or spaces." + +class IndentationWarning(isLeft: Boolean = false, before: String = "", missing: Token*)(using Context) +extends SyntaxMsg(IndentationWarningID): + override protected def msg(using Context) = + s"Line is indented too far to the ${if isLeft then "left" else "right"}, or a ${ + missing.map(showToken).mkString(" or ") + } is missing${ + if !before.isEmpty then i" before:\n\n$before" else "" + }" + override protected def explain(using Context): String = + "Indentation that does not reflect syntactic nesting may be due to a typo such as missing punctuation." diff --git a/tests/warn/i16072.check b/tests/warn/i16072.check index decc43f99146..542a7c046cc1 100644 --- a/tests/warn/i16072.check +++ b/tests/warn/i16072.check @@ -1,7 +1,9 @@ --- Warning: tests/warn/i16072.scala:4:2 -------------------------------------------------------------------------------- +-- [E223] Syntax Warning: tests/warn/i16072.scala:4:2 ------------------------------------------------------------------ 4 | def x = 1 // warn too far right | ^ - | Line is indented too far to the right, or a `{` or `:` is missing + | Line is indented too far to the right, or a '{' or ':' is missing + | + | longer explanation available when compiling with `-explain` -- [E222] Syntax Warning: tests/warn/i16072.scala:3:7 ------------------------------------------------------------------ 3 |object Hello_: // warn colon in name without backticks because the body is empty | ^^^^^^^ @@ -12,10 +14,12 @@ 12 |object :: : // warn deprecated colon without backticks for operator name | ^ | `:` after symbolic operator is deprecated; use backticks around operator instead --- Warning: tests/warn/i16072.scala:21:2 ------------------------------------------------------------------------------- +-- [E223] Syntax Warning: tests/warn/i16072.scala:21:2 ----------------------------------------------------------------- 21 | def y = 1 // warn | ^ - | Line is indented too far to the right, or a `{` or `:` is missing + | Line is indented too far to the right, or a '{' or ':' is missing + | + | longer explanation available when compiling with `-explain` -- [E222] Syntax Warning: tests/warn/i16072.scala:20:6 ----------------------------------------------------------------- 20 |class Uhoh_: // warn | ^^^^^^