Skip to content

Commit e4aa398

Browse files
committed
[named tuples][completion] fixed named tuple completion #SCL-23823 fixed
1 parent e7eb51e commit e4aa398

File tree

4 files changed

+92
-29
lines changed

4 files changed

+92
-29
lines changed

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/completion/ScalaNamedTupleCompletionContributor.scala

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.jetbrains.plugins.scala.lang.completion
22

3-
import com.intellij.codeInsight.completion.{CompletionContributor, CompletionParameters, CompletionType, InsertHandler, InsertionContext}
3+
import com.intellij.codeInsight.completion.{CompletionContributor, CompletionParameters, CompletionType, CompletionUtilCore, InsertHandler, InsertionContext}
44
import com.intellij.codeInsight.lookup.{LookupElement, LookupElementBuilder}
55
import com.intellij.codeInsight.template.TemplateBuilderImpl
66
import com.intellij.openapi.util.TextRange
@@ -11,14 +11,16 @@ import com.intellij.util.ProcessingContext
1111
import org.jetbrains.plugins.scala.extensions.{PsiElementExt, ToNullSafe}
1212
import org.jetbrains.plugins.scala.lang.completion.ScalaNamedTupleCompletionContributor.ScalaNamedTupleCompletionProvider
1313
import org.jetbrains.plugins.scala.lang.psi.api.expr.{ScExpression, ScNamedTuple, ScNamedTupleExprComponent, ScParenthesisedExpr, ScReferenceExpression}
14+
import org.jetbrains.plugins.scala.lang.psi.types.ScType
1415
import org.jetbrains.plugins.scala.lang.psi.types.api.NamedTupleType
1516

1617
import scala.annotation.tailrec
18+
import scala.collection.mutable
1719

1820
class ScalaNamedTupleCompletionContributor extends CompletionContributor {
1921
extend(
2022
CompletionType.BASIC,
21-
identifierWithParentsPattern(classOf[ScReferenceExpression], classOf[ScNamedTupleExprComponent], classOf[ScNamedTuple]),
23+
identifierWithParentsPattern(classOf[ScNamedTupleExprComponent], classOf[ScNamedTuple]),
2224
ScalaNamedTupleCompletionProvider,
2325
)
2426

@@ -33,42 +35,55 @@ object ScalaNamedTupleCompletionContributor {
3335
object ScalaNamedTupleCompletionProvider extends ScalaCompletionProvider {
3436
override protected def completionsFor(position: PsiElement)
3537
(implicit parameters: CompletionParameters, context: ProcessingContext): Iterable[LookupElement] = {
36-
val refParent = getContextOfType(position, classOf[ScReferenceExpression])
37-
.nullSafe
38-
.map(_.getParent)
39-
.orNull
40-
refParent match {
41-
case expr: ScParenthesisedExpr => createCompletionFor(expr, Map.empty)
42-
case comp: ScNamedTupleExprComponent =>
38+
val identifierParent = getContextOfType(position, classOf[ScParenthesisedExpr], classOf[ScNamedTupleExprComponent])
39+
identifierParent match {
40+
case expr: ScParenthesisedExpr => createCompletionFor(expr, Seq.empty)
41+
case comp: ScNamedTupleExprComponent if position == comp.nameId =>
4342
val namedTuple = comp.namedTuple
4443
val existingComponents =
4544
namedTuple
4645
.components
4746
.flatMap { comp =>
4847
comp.nameElement.zip(comp.expr)
49-
.map { case (name, expr) => name.getText -> expr.getText }
50-
}.toMap
48+
.map { case (name, expr) => name.getText.replace(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED, "") -> expr.getText }
49+
}
5150

5251
createCompletionFor(namedTuple, existingComponents)
5352
case _ =>
5453
Seq.empty
5554
}
5655
}
5756

58-
private def createCompletionFor(expr: ScExpression, existingComponents: Map[String, String]): Seq[LookupElement] = {
57+
private def createCompletionFor(expr: ScExpression, existingComponents: Seq[(String, String)]): Seq[LookupElement] = {
58+
val existingComponentsMap = existingComponents.toMap
59+
def hasNewComponent(components: Seq[(ScType, _)]): Boolean =
60+
components.size > existingComponents.size || components.exists {
61+
case (NamedTupleType.NameType(name), _) => !existingComponentsMap.contains(name)
62+
case _ => false
63+
}
64+
5965
expr.expectedType() match {
60-
case Some(NamedTupleType(components)) =>
66+
case Some(NamedTupleType(components)) if hasNewComponent(components) =>
6167
val elements =
6268
components.map {
6369
case (NamedTupleType.NameType(name), _) => name //s"$name = " + existingComponents.getOrElse(name, s"???")
6470
case _ => "???"
6571
}
6672

6773
val presentation = elements
68-
.filterNot(existingComponents.contains)
74+
.filterNot(existingComponentsMap.contains)
6975
.mkString("", ", ", " =")
76+
77+
// the identifier that we currently try to complete is of course not in components
78+
// so just put down unmatched components in order
79+
val unmatchedExpressions = existingComponents
80+
.iterator
81+
.collect { case (name, expr) if !elements.contains(name) => expr }
82+
.to(mutable.Queue)
83+
def nextUnmatchedExpression =
84+
if (unmatchedExpressions.isEmpty) "???" else unmatchedExpressions.dequeue()
7085
val text = elements
71-
.map(name => s"$name = ${existingComponents.getOrElse(name, "???")}")
86+
.map(name => s"$name = ${existingComponentsMap.getOrElse(name, nextUnmatchedExpression)}")
7287
.mkString(", ")
7388
Seq(
7489
LookupElementBuilder

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/parser/parsing/expressions/SimpleExpr.scala

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,20 +112,29 @@ object SimpleExpr extends ParsingRule {
112112
val namedTupleComponentMarker = builder.mark()
113113
var hasNamedTupleContent = false
114114
if (isNamedTuple) {
115-
if (builder.lookAhead(tIDENTIFIER, tASSIGN)) {
116-
hasNamedTupleContent = true
117-
builder.advanceLexer()
118-
builder.advanceLexer()
119-
} else if (builder.getTokenType == tASSIGN) {
120-
hasNamedTupleContent = true
121-
builder.error(ErrMsg("identifier.expected"))
122-
builder.advanceLexer()
123-
} else {
124-
builder.error(ErrMsg("identifier.expected"))
115+
builder.getTokenType match {
116+
case `tIDENTIFIER` =>
117+
hasNamedTupleContent = true
118+
builder.advanceLexer()
119+
120+
if (builder.getTokenType == tASSIGN) {
121+
builder.advanceLexer()
122+
} else {
123+
builder.error(ScalaBundle.message("assignment.expected"))
124+
}
125+
case token =>
126+
builder.error(ErrMsg("identifier.expected"))
127+
128+
if (token == `tASSIGN`) {
129+
builder.advanceLexer()
130+
}
125131
}
126132
}
127133

128134
val parsedExpr = Expr()
135+
if (!parsedExpr) {
136+
builder.expressionExpectedError()
137+
}
129138

130139
if (isNamedTuple && (hasNamedTupleContent || parsedExpr)) {
131140
namedTupleComponentMarker.done(ScalaElementType.NAMED_TUPLE_COMPONENT)

scala/scala-impl/test/org/jetbrains/plugins/scala/lang/completion3/ScalaNamedTupleCompletionContributorTest.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,40 @@ class ScalaNamedTupleCompletionContributorTest extends ScalaCompletionTestBase {
4040
|""".stripMargin,
4141
item = """a = ???, b = "test"""",
4242
)
43+
44+
def testCompleteExistingNamedTupleWithReordering2(): Unit = doCompletionTest(
45+
fileText =
46+
s"""val x: (a: Int, bbb: String) = (a = 1, b$CARET = "test")
47+
|""".stripMargin,
48+
resultText =
49+
s"""val x: (a: Int, bbb: String) = (a = 1, bbb = "test")
50+
|""".stripMargin,
51+
item = """a = 1, bbb = "test"""",
52+
)
53+
54+
def testCompleteExistingNamedTupleWithReordering3(): Unit = doCompletionTest(
55+
fileText =
56+
s"""val x: (a: Int, bbb: String, ccc: Boolean) = (b$CARET = "test", a = 1, c = true)
57+
|""".stripMargin,
58+
resultText =
59+
s"""val x: (a: Int, bbb: String, ccc: Boolean) = (a = 1, bbb = "test", ccc = true)
60+
|""".stripMargin,
61+
item = """a = 1, bbb = "test", ccc = true""",
62+
)
63+
64+
def testDontCompleteInExpr(): Unit =
65+
checkNoCompletion(s"val x: (a: Int, b: String) = (a = $CARET)") {
66+
item => item.getLookupString.contains(" = ")
67+
}
68+
69+
// SCL-23823
70+
def testDontCompleteInExpr2(): Unit =
71+
checkNoCompletion(s"val x: (a: Int, b: String) = (a = p.$CARET, b = ???)") {
72+
item => item.getLookupString.contains(" = ")
73+
}
74+
75+
def testDontCompleteIfAllComponentsAreThere(): Unit =
76+
checkNoCompletion(s"val x: (a: Int, b: String) = (a = ???, b$CARET = ???)") {
77+
item => item.getLookupString.contains(" = ")
78+
}
4379
}

scala/scala-impl/test/org/jetbrains/plugins/scala/lang/parser/scala3/TupleParserTest.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,8 @@ class TupleParserTest extends SimpleScala3ParserTestBase {
693693
| PsiElement(identifier)('name')
694694
| PsiWhiteSpace(' ')
695695
| PsiElement(=)('=')
696+
| PsiErrorElement:Expression expected
697+
| <empty list>
696698
| PsiElement(,)(',')
697699
| PsiWhiteSpace(' ')
698700
| ScNamedTupleExprComponentImpl(named tuple component)
@@ -703,11 +705,12 @@ class TupleParserTest extends SimpleScala3ParserTestBase {
703705
| IntegerLiteral
704706
| PsiElement(integer)('1')
705707
| PsiElement(,)(',')
708+
| PsiErrorElement:Identifier expected
709+
| <empty list>
706710
| PsiWhiteSpace(' ')
707-
| ScNamedTupleExprComponentImpl(named tuple component)
708-
| PsiErrorElement:Identifier expected
709-
| <empty list>
710-
| PsiElement(=)('=')
711+
| PsiElement(=)('=')
712+
| PsiErrorElement:Expression expected
713+
| <empty list>
711714
| PsiElement(,)(',')
712715
| PsiWhiteSpace(' ')
713716
| ScNamedTupleExprComponentImpl(named tuple component)

0 commit comments

Comments
 (0)