Repository: scalameta/scalameta
Pull request #4067 (2024-12-07 15:31:44)

ScalametaParser: add `GivenSigContext`
It allows anonymous names in parameters. Helps with #4047.


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 dfa0aa79a..e2a91336b 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
@@ -42,6 +42,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
   private object TypeBracketsContext extends NestedContext
   private object PatternTypeContext extends NestedContext
   private object ExtensionSigContext extends NestedContext
+  private object GivenSigContext extends NestedContext
 
   /* ------------- PARSER ENTRY POINTS -------------------------------------------- */
 
@@ -3122,10 +3123,11 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
         anonNameOK: Boolean = false,
         ownerIsTypeOverride: => Boolean = false
     ) = {
+      val allowAnonName = anonNameOK || GivenSigContext.isInside()
       val params = commaSeparatedWithIndex(termParam(
         ownerIsCase = ownerIsCase,
         ownerIsType = ownerIsType || ownerIsTypeOverride,
-        allowAnonName = anonNameOK,
+        allowAnonName = allowAnonName,
         mod = mod
       ))
       reduceParams(params, mod)
@@ -3214,7 +3216,8 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
     mods.headOption.collect { case q: Mod.Quasi if endParamQuasi => q.become[Term.Param] }
       .getOrElse(currToken match {
         case t: Ellipsis => ellipsis[Term.Param](t, 1)
-        case _ if !peek[Colon] && allowAnonName => getParam(anonName(), Some(getParamType))
+        case _ if !peek[Colon] && (allowAnonName || GivenSigContext.isInside()) =>
+          getParam(anonName(), Some(getParamType))
         case t: Unquote =>
           val name = unquote[Name](t)
           if (endParamQuasi) name.become[Term.Param]
diff --git a/scalameta/tokens/shared/src/main/scala/scala/meta/tokens/Tokens.scala b/scalameta/tokens/shared/src/main/scala/scala/meta/tokens/Tokens.scala
index bb887b410..d9b30bd65 100644
--- a/scalameta/tokens/shared/src/main/scala/scala/meta/tokens/Tokens.scala
+++ b/scalameta/tokens/shared/src/main/scala/scala/meta/tokens/Tokens.scala
@@ -38,6 +38,8 @@ class Tokens private (private val tokens: Array[Token], private val start: Int,
     else throw new NoSuchElementException(s"token $idx out of $length")
   def end: Int = start + length // for MIMA compat
 
+  def getOpt(idx: Int): Option[Token] = if (idx >= 0 && idx < length) Some(get(idx)) else None
+
   /** get element in full Tokens relative to start of this slice */
   def getWideOpt(idx: Int): Option[Token] = {
     val wideIdx = start + idx
@@ -86,7 +88,41 @@ class Tokens private (private val tokens: Array[Token], private val start: Int,
   @inline
   def skipIf(p: Token => Boolean, rangeBeg: Int): Int = skipIf(p, rangeBeg, length)
   @inline
+  def skipWideIf(p: Token => Boolean, rangeBeg: Int): Int = skipWideIf(p, rangeBeg, Int.MaxValue)
+  @inline
   def rskipIf(p: Token => Boolean, rangeBeg: Int): Int = rskipIf(p, rangeBeg, -1)
+  @inline
+  def rskipWideIf(p: Token => Boolean, rangeBeg: Int): Int = rskipWideIf(p, rangeBeg, Int.MinValue)
+
+  @inline
+  def findNot(p: Token => Boolean, rangeBeg: Int): Option[Token] = findNot(p, rangeBeg, Int.MaxValue)
+  @inline
+  def rfindNot(p: Token => Boolean, rangeBeg: Int): Option[Token] =
+    rfindNot(p, rangeBeg, Int.MinValue)
+
+  @inline
+  def findWideNot(p: Token => Boolean, rangeBeg: Int): Option[Token] =
+    findWideNot(p, rangeBeg, Int.MaxValue)
+  @inline
+  def rfindWideNot(p: Token => Boolean, rangeBeg: Int): Option[Token] =
+    rfindWideNot(p, rangeBeg, Int.MinValue)
+
+  /**
+   * Find token not satisfying a given predicate.
+   * @param rangeBeg
+   *   first index to check
+   * @param rangeEnd
+   *   first index not to check
+   */
+  def findNot(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Option[Token] =
+    getOpt(skipIf(p, rangeBeg, rangeEnd))
+  def rfindNot(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Option[Token] =
+    getOpt(rskipIf(p, rangeBeg, rangeEnd))
+
+  def findWideNot(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Option[Token] =
+    getWideOpt(skipWideIf(p, rangeBeg, rangeEnd))
+  def rfindWideNot(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Option[Token] =
+    getWideOpt(rskipWideIf(p, rangeBeg, rangeEnd))
 
   /**
    * Skip tokens satisfying a given predicate.
@@ -96,15 +132,15 @@ class Tokens private (private val tokens: Array[Token], private val start: Int,
    *   first index not to check
    */
   def skipIf(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int =
-    if (rangeBeg < 0) rangeBeg else skipIfFull(p, start + rangeBeg, start + rangeEnd.min(length))
+    if (rangeBeg < 0) rangeBeg else skipFullIf(p, start + rangeBeg, start + rangeEnd.min(length))
 
   def skipWideIf(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int = {
     val beg = start + rangeBeg
     // rangeEnd + start could overflow, so let's first truncate it
-    if (beg < 0) rangeBeg else skipIfFull(p, beg, start + rangeEnd.min(tokens.length - start))
+    if (beg < 0) rangeBeg else skipFullIf(p, beg, start + rangeEnd.min(tokens.length - start))
   }
 
-  private def skipIfFull(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int = {
+  private def skipFullIf(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int = {
     var i = rangeBeg
     while (i < rangeEnd && p(tokens(i))) i += 1
     i - start
@@ -119,15 +155,15 @@ class Tokens private (private val tokens: Array[Token], private val start: Int,
    */
   def rskipIf(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int =
     if (rangeBeg >= length || rangeBeg <= rangeEnd) rangeBeg
-    else rskipIfFull(p, start + rangeBeg, start + rangeEnd.max(-1))
+    else rskipFullIf(p, start + rangeBeg, start + rangeEnd.max(-1))
 
   def rskipWideIf(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int = {
     val beg = start + rangeBeg
     // rangeEnd + start could overflow; compare it to (-start -1), and ~start is exactly that
-    if (beg >= tokens.length) rangeBeg else rskipIfFull(p, beg, start + rangeEnd.max(~start))
+    if (beg >= tokens.length) rangeBeg else rskipFullIf(p, beg, start + rangeEnd.max(~start))
   }
 
-  private def rskipIfFull(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int = {
+  private def rskipFullIf(p: Token => Boolean, rangeBeg: Int, rangeEnd: Int): Int = {
     var i = rangeBeg
     while (i > rangeEnd && p(tokens(i))) i -= 1
     i - start


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/tokens/TokensSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/tokens/TokensSuite.scala
index 9767afe17..bbe6bf549 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/tokens/TokensSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/tokens/TokensSuite.scala
@@ -172,6 +172,7 @@ class TokensApiSuite extends FunSuite {
     val beforeLbeg = tokens.length - beforeLlen
     assertEquals(beforeL.rskipWideIf(notIdent, beforeLbeg, -1), beforeLlen)
     assertEquals(beforeL.getWideOpt(beforeLlen).orNull, afterL.head)
+    assertEquals(beforeL.rfindWideNot(notIdent, beforeLbeg, -1), Some(afterL.head))
 
     val afterLlen = afterL.length
     val afterLbeg = afterLlen - tokens.length
@@ -185,6 +186,7 @@ class TokensApiSuite extends FunSuite {
     val afterRbeg = afterRlen - tokens.length
     assertEquals(afterR.skipWideIf(notIdent, afterRbeg, tokens.length), -1)
     assertEquals(afterR.getWideOpt(-1).orNull, afterL.head)
+    assertEquals(afterR.findWideNot(notIdent, afterRbeg), Some(afterL.head))
 
     var lastidx = -1
     tokens.foreachWithIndex { (t, i) =>