Repository: scalameta/scalameta (scala)

Issue:
Parse error on polymorphic lambda when it appears inside a block
# Minimization
```scala
//> using dep org.scalameta::scalameta:4.13.4

import scala.meta.*

@main def Test =
  val program1 = """
def foo =
  [T] => (a: T) => a"""

  val program2 = """
def foo =
  println()
  [T] => (a: T) => a"""

  val tree1 = dialects.Scala3(program1).parse[Source].get // ok
  val tree2 = dialects.Scala3(program2).parse[Source].get // error
```

# Output
```scala
Exception in thread "main" <input>:4: error: `;` expected but `=>` found
  [T] => (a: T) => a
      ^
        at scala.meta.internal.parsers.Reporter.syntaxError(Reporter.scala:16)
        at scala.meta.internal.parsers.Reporter.syntaxError$(Reporter.scala:9)
        at scala.meta.internal.parsers.Reporter$$anon$1.syntaxError(Reporter.scala:22)
        at scala.meta.internal.parsers.Reporter.syntaxError(Reporter.scala:17)
        at scala.meta.internal.parsers.Reporter.syntaxError$(Reporter.scala:9)
        at scala.meta.internal.parsers.Reporter$$anon$1.syntaxError(Reporter.scala:22)
        at scala.meta.internal.parsers.ScalametaParser.syntaxErrorExpected(ScalametaParser.scala:425)
        at scala.meta.internal.parsers.ScalametaParser.syntaxErrorExpected(ScalametaParser.scala:423)
        at scala.meta.internal.parsers.ScalametaParser.acceptStatSep(ScalametaParser.scala:502)
        at scala.meta.internal.parsers.ScalametaParser.blockStatSeq$$anonfun$1$$anonfun$2(ScalametaParser.scala:4340)
        at scala.Function0.apply$mcZ$sp(Function0.scala:42)
        at scala.meta.internal.parsers.ScalametaParser$.doWhile(ScalametaParser.scala:4409)
        at scala.meta.internal.parsers.ScalametaParser.blockStatSeq$$anonfun$1(ScalametaParser.scala:4340)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
        at scala.meta.internal.parsers.ScalametaParser.scala$meta$internal$parsers$ScalametaParser$$listBy(ScalametaParser.scala:555)
        at scala.meta.internal.parsers.ScalametaParser.blockStatSeq(ScalametaParser.scala:4313)
        at scala.meta.internal.parsers.ScalametaParser.blockOnIndent$$anonfun$1$$anonfun$1(ScalametaParser.scala:2400)
        at scala.meta.internal.parsers.ScalametaParser.scala$meta$internal$parsers$ScalametaParser$$indentedOnOpen(ScalametaParser.scala:274)
        at scala.meta.internal.parsers.ScalametaParser.blockOnIndent$$anonfun$1(ScalametaParser.scala:2403)
        at scala.meta.internal.parsers.ScalametaParser.atPosOpt(ScalametaParser.scala:328)
        at scala.meta.internal.parsers.ScalametaParser.autoPosOpt(ScalametaParser.scala:382)
        at scala.meta.internal.parsers.ScalametaParser.blockOnIndent(ScalametaParser.scala:2404)
        at scala.meta.internal.parsers.ScalametaParser.blockExprOnIndent$$anonfun$1(ScalametaParser.scala:2407)
        at scala.meta.internal.parsers.ScalametaParser.blockExprPartial(ScalametaParser.scala:2393)
        at scala.meta.internal.parsers.ScalametaParser.blockExprOnIndent(ScalametaParser.scala:2407)
        at scala.meta.internal.parsers.ScalametaParser.simpleExpr0(ScalametaParser.scala:2117)
        at scala.meta.internal.parsers.ScalametaParser.simpleExpr(ScalametaParser.scala:2100)
        at scala.meta.internal.parsers.ScalametaParser.prefixExpr(ScalametaParser.scala:2097)
        at scala.meta.internal.parsers.ScalametaParser.postfixExpr(ScalametaParser.scala:1962)
        at scala.meta.internal.parsers.ScalametaParser.$anonfun$38(ScalametaParser.scala:1597)
        at scala.meta.internal.parsers.ScalametaParser.atPosOpt(ScalametaParser.scala:328)
        at scala.meta.internal.parsers.ScalametaParser.autoPosOpt(ScalametaParser.scala:382)
        at scala.meta.internal.parsers.ScalametaParser.expr(ScalametaParser.scala:1600)
        at scala.meta.internal.parsers.ScalametaParser.expr(ScalametaParser.scala:1417)
        at scala.meta.internal.parsers.ScalametaParser.defn$1(ScalametaParser.scala:3720)
        at scala.meta.internal.parsers.ScalametaParser.funDefRest$$anonfun$1(ScalametaParser.scala:3724)
        at scala.meta.internal.parsers.ScalametaParser.autoEndPos(ScalametaParser.scala:384)
        at scala.meta.internal.parsers.ScalametaParser.autoEndPos(ScalametaParser.scala:389)
        at scala.meta.internal.parsers.ScalametaParser.funDefRest(ScalametaParser.scala:3729)
        at scala.meta.internal.parsers.ScalametaParser.funDefOrDclOrExtensionOrSecondaryCtor(ScalametaParser.scala:3675)
        at scala.meta.internal.parsers.ScalametaParser.defOrDclOrSecondaryCtor(ScalametaParser.scala:3371)
        at scala.meta.internal.parsers.ScalametaParser.nonLocalDefOrDcl(ScalametaParser.scala:3350)
        at scala.meta.internal.parsers.ScalametaParser$$anon$2.applyOrElse(ScalametaParser.scala:4249)
        at scala.meta.internal.parsers.ScalametaParser$$anon$2.applyOrElse(ScalametaParser.scala:4241)
        at scala.PartialFunction.$anonfun$runWith$1(PartialFunction.scala:231)
        at scala.PartialFunction.$anonfun$runWith$1$adapted(PartialFunction.scala:230)
        at scala.meta.internal.parsers.ScalametaParser.statSeqBuf(ScalametaParser.scala:4234)
        at scala.meta.internal.parsers.ScalametaParser.statSeq$$anonfun$1(ScalametaParser.scala:4223)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
        at scala.meta.internal.parsers.ScalametaParser.scala$meta$internal$parsers$ScalametaParser$$listBy(ScalametaParser.scala:555)
        at scala.meta.internal.parsers.ScalametaParser.statSeq(ScalametaParser.scala:4223)
        at scala.meta.internal.parsers.ScalametaParser.bracelessPackageStats$1(ScalametaParser.scala:4393)
        at scala.meta.internal.parsers.ScalametaParser.source$$anonfun$1(ScalametaParser.scala:4396)
        at scala.meta.internal.parsers.ScalametaParser.atPos(ScalametaParser.scala:324)
        at scala.meta.internal.parsers.ScalametaParser.autoPos(ScalametaParser.scala:379)
        at scala.meta.internal.parsers.ScalametaParser.source(ScalametaParser.scala:4397)
        at scala.meta.internal.parsers.ScalametaParser.entrypointSource(ScalametaParser.scala:4399)
        at scala.meta.internal.parsers.ScalametaParser.parseSourceImpl(ScalametaParser.scala:114)
        at scala.meta.internal.parsers.ScalametaParser.parseSource$$anonfun$1(ScalametaParser.scala:111)
        at scala.meta.internal.parsers.ScalametaParser.parseRuleAfterBOF(ScalametaParser.scala:60)
        at scala.meta.internal.parsers.ScalametaParser.parseRule(ScalametaParser.scala:55)
        at scala.meta.internal.parsers.ScalametaParser.parseSource(ScalametaParser.scala:111)
        at scala.meta.parsers.Parse$.parseSource$lzyINIT1$$anonfun$1(Parse.scala:30)
        at scala.meta.parsers.Parse$$anon$1.apply(Parse.scala:37)
        at scala.meta.parsers.Api$XtensionParseDialectInput.parse(Api.scala:22)
        at a$package$.Test(a.scala:16)
        at Test.main(a.scala:5)
```

# Expected Output

No error since this is parsed correctly by the compiler.

Pull request description:
Fixes #4226.

Patch:
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
index 73e7ceb6b9..8bd6b08cd0 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
@@ -1382,7 +1382,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
   def newLineOptWhenFollowedBy(pred: Token => Boolean): Boolean = nextIfPair[EOL](pred)
   def newLineOptWhenFollowedBy[T: ClassTag]: Boolean = nextIfPair[EOL, T]
 
-  def isIndentingOrEOL(nonOptBracesOK: Boolean): Boolean =
+  def isIndentingOrEOL(nonOptBracesOK: => Boolean): Boolean =
     if (dialect.allowSignificantIndentation) in.indenting else nonOptBracesOK && at[EOL]
 
   def isAfterOpt[A: ClassTag, B: ClassTag]: Boolean = if (at[B]) tryAhead[A] else at[A]
@@ -2167,8 +2167,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
     currToken match {
       case _: AtEOL if (peekToken match {
             case _: Dot => true
-            case _: LeftBrace if canApply => isIndentingOrEOL(true)
-            case _: LeftParen if canApply => isIndentingOrEOL(false)
+            case t: OpenDelim if canApply => isIndentingOrEOL(t.is[LeftBrace])
             case _ => false
           }) => next(); simpleExprRest(t, canApply, startPos)
       case _: Dot =>
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
index ef70cec34f..802ba4dea2 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
@@ -198,9 +198,9 @@ final class ScannerTokens(val tokens: Tokens)(implicit dialect: Dialect) {
 
   def mightStartStat(token: Token, closeDelimOK: Boolean): Boolean = token match {
     case _: KwCatch | _: KwElse | _: KwExtends | _: KwFinally | _: KwForsome | _: KwMatch |
-        _: KwWith | _: KwYield | _: LeftBracket | _: Comma | _: Colon | _: Dot | _: Equals |
-        _: Semicolon | _: Hash | _: RightArrow | _: LeftArrow | _: Subtype | _: Supertype |
-        _: Viewbound | _: AtEOLorF => false
+        _: KwWith | _: KwYield | _: Comma | _: Colon | _: Dot | _: Equals | _: Semicolon | _: Hash |
+        _: RightArrow | _: LeftArrow | _: Subtype | _: Supertype | _: Viewbound | _: AtEOLorF =>
+      false
     case _: CloseDelim => closeDelimOK
     case _ => true
   }


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InterleavedDeclSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InterleavedDeclSuite.scala
index d51e2e9d81..c610fde295 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InterleavedDeclSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/InterleavedDeclSuite.scala
@@ -71,4 +71,54 @@ class InterleavedDeclSuite extends BaseDottySuite {
       pname("B")
     ))
   )
+
+  test("f[A](a: A, as: A*)[B](b: B, bs: B*)[C](implicit c: C): B [def with breaks]") {
+    val code =
+      """|def f
+         |  [A]
+         |  (a: A, as: A*)
+         |  [B]
+         |  (b: B, bs: B*)
+         |  [C]
+         |  (implicit c: C)
+         |  : B
+         |""".stripMargin
+    val layout = "def f[A](a: A, as: A*)[B](b: B, bs: B*)[C](implicit c: C): B"
+    runTestAssert[Stat](code, layout)(Decl.Def(
+      Nil,
+      tname("f"),
+      pcg(List(pparam("A")), List(tparam("a", "A"), tparam("as", Type.Repeated(pname("A"))))) ::
+        pcg(List(pparam("B")), List(tparam("b", "B"), tparam("bs", Type.Repeated(pname("B"))))) ::
+        pcg(List(pparam("C")), List(tparam(List(Mod.Implicit()), "c", "C"))) :: Nil,
+      pname("B")
+    ))
+  }
+
+  test("f[A](a: A, as: A*)[B](b: B, bs: B*)[C](implicit c: C): B [run with breaks before LP]") {
+    val code =
+      """|f[A]
+         |  (a1, a2)[B]
+         |  (b1, b2, b3)[C]
+         |""".stripMargin
+    val layout = "f[A](a1, a2)[B](b1, b2, b3)[C]"
+    val innerTree = tapplytype(tapply(tapplytype("f", "A"), "a1", "a2"), "B")
+    val tree = tapplytype(tapply(innerTree, "b1", "b2", "b3"), "C")
+    runTestAssert[Stat](code, layout)(tree)
+  }
+
+  test("f[A](a: A, as: A*)[B](b: B, bs: B*)[C](implicit c: C): B [run with breaks]") {
+    val code =
+      """|f
+         |  [A]
+         |  (a1, a2)
+         |  [B]
+         |  (b1, b2, b3)
+         |  [C]
+         |""".stripMargin
+    val layout = "f[A](a1, a2)[B](b1, b2, b3)[C]"
+    val innerTree = tapplytype(tapply(tapplytype("f", "A"), "a1", "a2"), "B")
+    val tree = tapplytype(tapply(innerTree, "b1", "b2", "b3"), "C")
+    runTestAssert[Stat](code, layout)(tree)
+  }
+
 }
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/NewFunctionsSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/NewFunctionsSuite.scala
index 18f48ad98c..35d03661bf 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/NewFunctionsSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/NewFunctionsSuite.scala
@@ -704,4 +704,42 @@ class NewFunctionsSuite extends BaseDottySuite {
     runTestAssert[Stat](code, layout)(tree)
   }
 
+  test("#4226 brackets") {
+    val code =
+      """|def foo =
+         |  println()
+         |  [T] => (a: T) => a
+         |""".stripMargin
+    val layout =
+      """|def foo = {
+         |  println()
+         |  [T] => (a: T) => a
+         |}
+         |""".stripMargin
+    val tree = Defn.Def(
+      Nil,
+      "foo",
+      Nil,
+      None,
+      blk(tapply("println"), tpolyfunc(pparam("T"))(tfunc(tparam("a", "T"))("a")))
+    )
+    runTestAssert[Stat](code, layout)(tree)
+  }
+
+  test("#4226 parens") {
+    val code =
+      """|def foo =
+         |  println()
+         |  (a: T) => a
+         |""".stripMargin
+    val layout =
+      """|def foo = {
+         |  println()
+         |  (a: T) => a
+         |}
+         |""".stripMargin
+    val tree = Defn.Def(Nil, "foo", Nil, None, blk(tapply("println"), tfunc(tparam("a", "T"))("a")))
+    runTestAssert[Stat](code, layout)(tree)
+  }
+
 }