diff --git a/build.sbt b/build.sbt index 9e4bc030..f72e3938 100644 --- a/build.sbt +++ b/build.sbt @@ -126,6 +126,10 @@ lazy val tastyQuery = mimaBinaryIssueFilters ++= { import com.typesafe.tools.mima.core.* Seq( + // New pattern tree node for named extractor patterns `case Foo(field = pat)` + ProblemFilters.exclude[MissingClassProblem]("tastyquery.Trees$NamedPattern"), + ProblemFilters.exclude[MissingClassProblem]("tastyquery.Trees$NamedPattern$"), + ProblemFilters.exclude[MissingFieldProblem]("tastyquery.Trees.NamedPattern"), ) }, diff --git a/tasty-query/shared/src/main/scala/tastyquery/Printers.scala b/tasty-query/shared/src/main/scala/tastyquery/Printers.scala index 82c412ed..2d8f7c36 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Printers.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Printers.scala @@ -607,6 +607,11 @@ private[tastyquery] object Printers: case ExprPattern(expr) => print(expr) + case NamedPattern(name, body) => + print(name) + print(" = ") + print(body) + case QuotePattern(bindings, body, quotes, patternType) => print("'<") print(quotes) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala b/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala index 76842b8c..447bf9a6 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala @@ -145,6 +145,8 @@ object Traversers: traverse(patterns) case ExprPattern(expr) => traverse(expr) + case NamedPattern(name, body) => + traverse(body) case WildcardPattern(tpe) => () case QuotePattern(bindings, body, quotes, patternType) => diff --git a/tasty-query/shared/src/main/scala/tastyquery/Trees.scala b/tasty-query/shared/src/main/scala/tastyquery/Trees.scala index 1d5fca37..b1ee4925 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Trees.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Trees.scala @@ -638,6 +638,12 @@ object Trees { override final def withPos(pos: SourcePosition): ExprPattern = ExprPattern(expr)(pos) end ExprPattern + /** A named argument pattern `name = body` in an [[Unapply]], as in `case Foo(field = pat)`. */ + final case class NamedPattern(name: UnsignedTermName, body: PatternTree)(pos: SourcePosition) + extends PatternTree(pos): + override final def withPos(pos: SourcePosition): NamedPattern = NamedPattern(name, body)(pos) + end NamedPattern + /** A tree representing a quote pattern `'{ type binding1; ...; body }` or `'[ type binding1; ...; body ]`. * * The `bindings` contain the list of quote pattern type variable definitions (`TypeTreeBind`s) diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala index 3fcca481..19eae613 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala @@ -897,6 +897,13 @@ private[tasties] class TreeUnpickler private ( reader.readByte() val shared = forkAt(reader.readAddr()).readPattern if spn.isUnknown then shared else shared.withPos(spn) + case NAMEDARG => + // Named extractor patterns (e.g., `case Foo(field = value)`) wrap sub-patterns in NAMEDARG. + val spn = span + reader.readByte() + val name = readUnsignedName() + val body = readPattern + NamedPattern(name, body)(spn) case _ => val expr = readTerm ExprPattern(expr)(expr.pos) diff --git a/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala index fd5105c7..79b59b90 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala @@ -596,6 +596,17 @@ class ReadTreeSuite extends RestrictedUnpicklingSuite { assert(containsSubtree(guardWithAlternatives)(clue(tree))) } + testUnpickle("named-extractor-pattern", "simple_trees.NamedExtractorPattern") { tree => + // case Wrapper(value = Some(v)) => v + val namedPatternCheck: StructureCheck = { + case NamedPattern( + SimpleName("value"), + TypeTest(Unapply(_, _, List(Bind(SimpleName("v"), WildcardPattern(_), _))), _) + ) => + } + assert(containsSubtree(namedPatternCheck)(clue(tree))) + } + testUnpickle("assign", "simple_trees.Assign") { tree => val assignBlockMatch: StructureCheck = { case Block( diff --git a/test-sources/src/main/scala/simple_trees/NamedExtractorPattern.scala b/test-sources/src/main/scala/simple_trees/NamedExtractorPattern.scala new file mode 100644 index 00000000..078d3f92 --- /dev/null +++ b/test-sources/src/main/scala/simple_trees/NamedExtractorPattern.scala @@ -0,0 +1,8 @@ +package simple_trees + +case class Wrapper(value: Option[String]) + +class NamedExtractorPattern { + def extract(items: List[Wrapper]): List[String] = + items.collect { case Wrapper(value = Some(v)) => v } +}