diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f44703a562f1..852c087b871f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -671,11 +671,14 @@ object Parsers { ts.toList else leading :: Nil - def maybeNamed(op: () => Tree): () => Tree = () => + def maybeNamed(location: Location, op: () => Tree): () => Tree = () => if isIdent && in.lookahead.token == EQUALS && sourceVersion.enablesNamedTuples then atSpan(in.offset): val name = ident() in.nextToken() + if location.inPattern && in.token == INDENT then + in.currentRegion = in.currentRegion.outer + in.nextToken() NamedArg(name, op()) else op() @@ -2958,7 +2961,7 @@ object Parsers { if isErased then isFormalParams = true if isFormalParams then binding(Modifiers()) else - val t = maybeNamed(exprInParens)() + val t = maybeNamed(Location.InParens, exprInParens)() if t.isInstanceOf[ValDef] then isFormalParams = true t commaSeparatedRest(exprOrBinding(), exprOrBinding) @@ -3411,8 +3414,8 @@ object Parsers { * | NamedPattern {‘,’ NamedPattern} * NamedPattern ::= id '=' Pattern */ - def patterns(location: Location = Location.InPattern): List[Tree] = - commaSeparated(maybeNamed(() => pattern(location))) + def patterns(location: Location = Location.InPattern): List[Tree] = // default used from Markup + commaSeparated(maybeNamed(location, () => pattern(location))) // check that patterns are all named or all unnamed is done at desugaring def patternsOpt(location: Location = Location.InPattern): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ec246f7a3742..3be8b87b6171 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -651,8 +651,14 @@ object Scanners { if skipping then 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()) + else + val checkable = r match + case _: InBraces => true + case InParens(LPAREN, _) => true + case _ => false + if checkable && !closingRegionTokens.contains(token) then + report.warning(s"Line is indented too far to the left, or a ${showToken(r.closedBy)} is missing", + sourcePos()) else if lastWidth < nextWidth || lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then if canStartIndentTokens.contains(lastToken) then @@ -765,7 +771,14 @@ object Scanners { case _ => false currentRegion match case r: Indented if isEnclosedInParens(r.outer) => - insert(OUTDENT, offset) + // For region prefix COLONeol, only OUTDENT if COMMA at EOL + if r.prefix == COLONeol then + val lookahead = LookaheadScanner() + lookahead.nextToken() + if lookahead.isAfterLineEnd then + insert(OUTDENT, offset) + else + insert(OUTDENT, offset) case _ => peekAhead() if isAfterLineEnd diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 9f4cc2086dc1..c3c8f6c2ace8 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -1053,7 +1053,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { addParamssText( addParamssText(keywordStr("extension "), leadingParamss) ~~ (defKeyword ~~ valDefText(nameIdText(tree))).close, - trailingParamss) + trailingParamss) else addParamssText(defKeyword ~~ valDefText(nameIdText(tree)), tree.paramss) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala index 16535381165c..e6cf096716c6 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala @@ -86,7 +86,7 @@ class CompletionExtensionSuite extends BaseCompletionSuite: @Test def `filter-by-type-old` = check( - """|package example + """|package example | |object enrichments: | implicit class A(num: Int): @@ -99,7 +99,6 @@ class CompletionExtensionSuite extends BaseCompletionSuite: """|identity: String (implicit) |""".stripMargin, // identity2 won't be available filter = _.contains("(implicit)") - ) @Test def `filter-by-type-subtype` = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala index 6abea1209998..013bc844f216 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala @@ -59,7 +59,7 @@ class CompletionKeywordSuite extends BaseCompletionSuite: | **/ |} |""".stripMargin, - "", + "", includeCommitCharacter = true ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 103f365f8af6..96aa208abdf8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1358,7 +1358,7 @@ class CompletionSuite extends BaseCompletionSuite: | val t: TT@@ |} |""".stripMargin, - "TTT[A <: Int] = List[A]" + "TTT[A <: Int] = List[A]" ) @Test def `type-lambda` = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala index a82fa73865c8..3e9d8c1695e8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala @@ -19,20 +19,20 @@ class HoverDocSuite extends BaseHoverSuite: @Test def `doc` = check( - """object a { - | <> - |} - |""".stripMargin, - """|**Expression type**: - |```scala - |java.util.List[Int] - |``` - |**Symbol signature**: - |```scala - |final def emptyList[T](): java.util.List[T] - |``` - |Found documentation for java/util/Collections#emptyList(). - |""".stripMargin, + """|object a { + | <> + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |java.util.List[Int] + |``` + |**Symbol signature**: + |```scala + |final def emptyList[T](): java.util.List[T] + |``` + |Found documentation for java/util/Collections#emptyList(). + |""".stripMargin, ) @Test def `doc-parent` = @@ -47,7 +47,6 @@ class HoverDocSuite extends BaseHoverSuite: |``` |Found documentation for scala/collection/LinearSeqOps#headOption(). |""".stripMargin, - ) @Test def `java-method` = @@ -61,7 +60,7 @@ class HoverDocSuite extends BaseHoverSuite: |``` |Found documentation for java/lang/String#substring(). |""".stripMargin - ) + ) @Test def `object` = check( diff --git a/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala b/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala index f07868ad4f44..0a855c169275 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/TemplateFileTests.scala @@ -208,26 +208,26 @@ class TemplateFileTests: testTemplate( """# Hello {{ msg }}!""", ext = "md" - ) { t => + ): t => assertEquals( """
- |

Hello there!

- |
""".stripMargin, - t.resolveInner(RenderingContext(Map("msg" -> "there"))).code.trim()) - } + |

Hello there!

+ |""".stripMargin, + t.resolveInner(RenderingContext(Map("msg" -> "there"))).code.trim() + ) @Test def mixedTemplates() : Unit = testTemplate( """# Hello {{ msg }}!""", ext = "md" - ) { t => + ): t => assertEquals( """
- |

Hello there2!

- |
""".stripMargin, - t.resolveInner(RenderingContext(Map("msg" -> "there2"))).code.trim()) - } + |

Hello there2!

+ |""".stripMargin, + t.resolveInner(RenderingContext(Map("msg" -> "there2"))).code.trim() + ) @Test def htmlOnly(): Unit = diff --git a/tests/neg/i22527.scala b/tests/neg/i22527.scala new file mode 100644 index 000000000000..b9d476793634 --- /dev/null +++ b/tests/neg/i22527.scala @@ -0,0 +1,30 @@ + +//rule of thumb is COLONeol was at EOL, so COMMA must be at EOL +def test: Unit = + assert( + identity: + true, "ok" // error end of statement expected but ',' found + ) + +def callme[A](x: => A, msg: String) = try x.toString catch case t: RuntimeException => msg + +// not all indented regions require COMMA at EOL for OUTDENT +def orElse(x: Int): Unit = + callme( + if x > 0 then + class X extends AnyRef, Serializable // error Not found: Serializable - did you mean Specializable? + true // error ',' or ')' expected, but 'true' found + else + false, "fail") + +def g: Unit = + identity( + x = + class X extends AnyRef, Serializable // error + 27 // error + ) + +def onlyIf(x: Int): Unit = + callme( + if (x > 0) + true, "fail") // error syntax is broken after old-style conditional diff --git a/tests/neg/syntax-error-recovery.check b/tests/neg/syntax-error-recovery.check index 83b764c3062c..9f6cbaa7bf60 100644 --- a/tests/neg/syntax-error-recovery.check +++ b/tests/neg/syntax-error-recovery.check @@ -96,7 +96,5 @@ | longer explanation available when compiling with `-explain` -- Warning: tests/neg/syntax-error-recovery.scala:61:2 ----------------------------------------------------------------- 61 | println(bam) - | ^^^^^^^ - | Alphanumeric method println is not declared infix; it should not be used as infix operator. - | Instead, use method syntax .println(...) or backticked identifier `println`. - | The latter can be rewritten automatically under -rewrite -source 3.4-migration. + | ^ + | Line is indented too far to the left, or a ')' is missing diff --git a/tests/pos/i22527.scala b/tests/pos/i22527.scala new file mode 100644 index 000000000000..a8a1d436dfe2 --- /dev/null +++ b/tests/pos/i22527.scala @@ -0,0 +1,77 @@ + +//> using options -Werror + +import annotation.* + +def f: Unit = + identity( + identity: + class X extends AnyRef, Serializable + 42 + ) + +def test: Unit = + assert( + identity: + true, + "ok" + ) + +def toss: Unit = + assert( + throw + null, + "ok" + ) +def raise: Unit = + assert( + throw + null, "ok" // ok now + ) + +def callme[A](x: => A, msg: String) = try x.toString catch case t: RuntimeException => msg + +def orElse(x: Int): Unit = + callme( + if x > 0 then + true + else + false, "fail") + +@nowarn("msg=Unit") +def onlyIf(x: Int): Unit = + callme( + if (x > 0) then // then syntax required + true, "fail") // warn value discard + +def h(xs: List[Int]) = + xs.foldLeft(0) + ( + (acc, x) => + acc + + x, + ) + +def sum(x: Int, y: Int, z: Int) = x+y+z + +def k(xs: List[Int], y: Int, z: Int) = + xs.foldLeft(0) + ( + (acc, x) => + sum( + x + + y + + z, + y, + z, + ) + ) + +object `arrow eol`: + def f(g: Int => Int, i: Int): Int = g(i) + def g(map: Int => Int): Int => Int = map + def test = + f( + g: x => + x + 1, 42 + ) diff --git a/tests/pos/i24474.scala b/tests/pos/i24474.scala new file mode 100644 index 000000000000..48f3a1d1a92d --- /dev/null +++ b/tests/pos/i24474.scala @@ -0,0 +1,28 @@ +case class Foo(arg1: Int, arg2: Int) +case class Bar(arg1: Int, arg2: Option[Int]) + +object OK: + Foo(1, 2) match + case Foo(arg1 = + 5 + ) => + +object Test: + Foo(1, 2) match + case Foo(arg1 = + 5 // was: error: pattern expected + ) => + +object Nest: + Bar(1, Option(2)) match + case Bar(arg2 = + Some(value = + 42 + ) + ) => + +object Detupled: + (fst = 1, snd = 2) match + case (fst = + 5 + ) => diff --git a/tests/warn/i22527.check b/tests/warn/i22527.check new file mode 100644 index 000000000000..b50612e12b57 --- /dev/null +++ b/tests/warn/i22527.check @@ -0,0 +1,22 @@ +-- Warning: tests/warn/i22527.scala:60:10 ------------------------------------------------------------------------------ +60 | y, // warn + | ^ + | Line is indented too far to the left, or a ')' is missing +-- Warning: tests/warn/i22527.scala:61:10 ------------------------------------------------------------------------------ +61 | z, // warn + | ^ + | Line is indented too far to the left, or a ')' is missing +-- Warning: tests/warn/i22527.scala:72:2 ------------------------------------------------------------------------------- +72 | 42 // warn + | ^ + | Line is indented too far to the left, or a ')' is missing +-- Warning: tests/warn/i22527.scala:79:0 ------------------------------------------------------------------------------- +79 |k: // warn + |^ + |Line is indented too far to the left, or a ')' is missing +-- [E190] Potential Issue Warning: tests/warn/i22527.scala:40:6 -------------------------------------------------------- +40 | true, "fail") // warn value discard + | ^^^^ + | Discarded non-Unit value of type Boolean. Add `: Unit` to discard silently. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i22527.scala b/tests/warn/i22527.scala new file mode 100644 index 000000000000..36486552bfbb --- /dev/null +++ b/tests/warn/i22527.scala @@ -0,0 +1,81 @@ + +def f: Unit = + identity( + identity: + class X extends AnyRef, Serializable + 42 + ) + +def test: Unit = + assert( + identity: + true, + "ok" + ) + +def toss: Unit = + assert( + throw + null, + "ok" + ) +def raise: Unit = + assert( + throw + null, "ok" // ok now + ) + +def callme[A](x: => A, msg: String) = try x.toString catch case t: RuntimeException => msg + +def orElse(x: Int): Unit = + callme( + if x > 0 then + true + else + false, "fail") + +def onlyIf(x: Int): Unit = + callme( + if x > 0 then + true, "fail") // warn value discard + +def h(xs: List[Int]) = + xs.foldLeft(0) + ( + (acc, x) => + acc + + x, + ) + +def sum(x: Int, y: Int, z: Int) = x+y+z + +def k(xs: List[Int], y: Int, z: Int) = + xs.foldLeft(0) + ( + (acc, x) => + sum( + x + + y + + z, + y, // warn + z, // warn + ) + ) + +object `arrow eol`: + def f(g: Int => Int, i: Int): Int = g(i) + def g(map: Int => Int): Int => Int = map + def k(i: => Int) = i + def test = + f( + g(_ + 1), + 42 // warn + ) + def test2 = + f( + g: + x => + x + 1, +k: // warn + 42 + )