Skip to content

Commit 5c761fd

Browse files
authored
Add pattern completion for unapply contexts (#20274)
Fixes #19972. Add pattern completion for `Unapply` tree contexts. A typical example would be ```scala optionList match case List(S@@) ``` which should be prompted `Some(value)`, due to `List.unapplySeq` expecting `Option[T]` patterns as arguments.
1 parent 4636ce2 commit 5c761fd

File tree

3 files changed

+95
-2
lines changed

3 files changed

+95
-2
lines changed

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala

+32
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ class Completions(
7070
false
7171
case (_: (Import | Export)) :: _ => false
7272
case _ :: (_: (Import | Export)) :: _ => false
73+
// UnApply has patterns included in MatchCaseCompletions
74+
case _ :: (_: UnApply) :: _ => false
7375
case _ => true
7476

7577
private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath)
@@ -405,6 +407,36 @@ class Completions(
405407
true,
406408
)
407409

410+
// unapply pattern
411+
case Ident(name) :: (unapp : UnApply) :: _ =>
412+
(
413+
CaseKeywordCompletion.contribute(
414+
EmptyTree, // no selector
415+
completionPos,
416+
indexedContext,
417+
config,
418+
search,
419+
parent = unapp,
420+
autoImports,
421+
patternOnly = Some(name.decoded)
422+
),
423+
false,
424+
)
425+
case Select(_, name) :: (unapp : UnApply) :: _ =>
426+
(
427+
CaseKeywordCompletion.contribute(
428+
EmptyTree, // no selector
429+
completionPos,
430+
indexedContext,
431+
config,
432+
search,
433+
parent = unapp,
434+
autoImports,
435+
patternOnly = Some(name.decoded)
436+
),
437+
false,
438+
)
439+
408440
// class FooImpl extends Foo:
409441
// def x|
410442
case OverrideExtractor(td, completing, start, exhaustive, fallbackName) =>

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala

+18-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import dotty.tools.dotc.core.Types.NoType
2727
import dotty.tools.dotc.core.Types.OrType
2828
import dotty.tools.dotc.core.Types.Type
2929
import dotty.tools.dotc.core.Types.TypeRef
30+
import dotty.tools.dotc.core.Types.AppliedType
31+
import dotty.tools.dotc.typer.Applications.UnapplyArgs
3032
import dotty.tools.dotc.util.SourcePosition
3133
import dotty.tools.pc.AutoImports.AutoImportsGenerator
3234
import dotty.tools.pc.AutoImports.SymbolImport
@@ -75,10 +77,24 @@ object CaseKeywordCompletion:
7577
patternOnly,
7678
hasBind
7779
)
80+
7881
val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedContext)
7982
val selTpe = selector match
8083
case EmptyTree =>
8184
parent match
85+
/* Parent is an unapply pattern */
86+
case UnApply(fn, implicits, patterns) if !fn.tpe.isErroneous =>
87+
patternOnly match
88+
case None => None
89+
case Some(value) =>
90+
val argPts = UnapplyArgs(fn.tpe.widen.finalResultType, fn, patterns, parent.srcPos).argTypes
91+
patterns.zipWithIndex
92+
.find:
93+
case (Ident(v), tpe) => v.decoded == value
94+
case (Select(_, v), tpe) => v.decoded == value
95+
case t => false
96+
.map((_, id) => argPts(id).widen.deepDealias)
97+
/* Parent is a function expecting a case match expression */
8298
case TreeApply(fun, _) if !fun.tpe.isErroneous =>
8399
fun.tpe.paramInfoss match
84100
case (head :: Nil) :: _
@@ -105,7 +121,8 @@ object CaseKeywordCompletion:
105121
if patternOnly.isEmpty then
106122
val selectorTpe = selTpe.show
107123
val tpeLabel =
108-
if !selectorTpe.contains("x$1") then selectorTpe
124+
if !selectorTpe.contains("x$1") /* selector of a function type? */ then
125+
selectorTpe
109126
else selector.symbol.info.show
110127
val label = s"case ${tpeLabel} =>"
111128
List(

Diff for: presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala

+45-1
Original file line numberDiff line numberDiff line change
@@ -634,14 +634,58 @@ class CompletionSuite extends BaseCompletionSuite:
634634
|""".stripMargin
635635
)
636636

637+
@Test def patRecursive =
638+
check(
639+
s"""|object Main {
640+
| Option(List(Option(1))) match {
641+
| case Some(List(None, Som@@))
642+
|}
643+
|""".stripMargin,
644+
"""|Some(value) scala
645+
|Some scala
646+
|""".stripMargin
647+
)
648+
check(
649+
s"""|object Main {
650+
| (null: Option[Option[Option[Option[Int]]]]) match
651+
| case Some(Some(Some(Som@@))))
652+
|}
653+
|""".stripMargin,
654+
"""|Some(value) scala
655+
|Some scala
656+
|""".stripMargin
657+
)
658+
check(
659+
s"""|object Main {
660+
| Option(Option(1)) match {
661+
| case Some(Som@@)
662+
|}
663+
|""".stripMargin,
664+
"""|Some(value) scala
665+
|Some scala
666+
|""".stripMargin
667+
)
668+
check(
669+
s"""|object Test:
670+
| case class NestedClass(x: Int)
671+
|object TestRun:
672+
| Option(Test.NestedClass(5)) match
673+
| case Some(Test.Neste@@)
674+
|""".stripMargin,
675+
"""|NestedClass(x) test.Test
676+
|NestedClass test.Test
677+
|""".stripMargin
678+
)
679+
637680
@Test def pat1 =
638681
check(
639682
s"""|object Main {
640683
| Option(1) match {
641684
| case List(Som@@)
642685
|}
643686
|""".stripMargin,
644-
"""|Some[A](value: A): Some[A]
687+
"""|Some(value) scala
688+
|Some scala
645689
|Some scala
646690
|""".stripMargin
647691
)

0 commit comments

Comments
 (0)