Skip to content

Commit

Permalink
feat: Implement completions for named tuple fields (#21202)
Browse files Browse the repository at this point in the history
closes #20478

---------

Co-authored-by: Jędrzej Rochala <[email protected]>
  • Loading branch information
KacperFKorban and rochala authored Jul 17, 2024
1 parent bbb45ca commit 3e78238
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 4 deletions.
43 changes: 39 additions & 4 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import dotty.tools.dotc.core.Names
import dotty.tools.dotc.core.Types
import dotty.tools.dotc.core.Symbols
import dotty.tools.dotc.core.Constants
import dotty.tools.dotc.core.TypeOps
import dotty.tools.dotc.core.StdNames

/**
* One of the results of a completion query.
Expand Down Expand Up @@ -200,7 +202,8 @@ object Completion:

private def computeCompletions(
pos: SourcePosition,
mode: Mode, rawPrefix: String,
mode: Mode,
rawPrefix: String,
adjustedPath: List[tpd.Tree],
untpdPath: List[untpd.Tree],
matches: Option[Name => Boolean]
Expand Down Expand Up @@ -442,9 +445,17 @@ object Completion:
def selectionCompletions(qual: tpd.Tree)(using Context): CompletionMap =
val adjustedQual = widenQualifier(qual)

implicitConversionMemberCompletions(adjustedQual) ++
extensionCompletions(adjustedQual) ++
directMemberCompletions(adjustedQual)
val implicitConversionMembers = implicitConversionMemberCompletions(adjustedQual)
val extensionMembers = extensionCompletions(adjustedQual)
val directMembers = directMemberCompletions(adjustedQual)
val namedTupleMembers = namedTupleCompletions(adjustedQual)

List(
implicitConversionMembers,
extensionMembers,
directMembers,
namedTupleMembers
).reduce(_ ++ _)

/** Completions for members of `qual`'s type.
* These include inherited definitions but not members added by extensions or implicit conversions
Expand Down Expand Up @@ -516,6 +527,30 @@ object Completion:
.toSeq
.groupByName

/** Completions for named tuples */
private def namedTupleCompletions(qual: tpd.Tree)(using Context): CompletionMap =
def namedTupleCompletionsFromType(tpe: Type): CompletionMap =
val freshCtx = ctx.fresh.setExploreTyperState()
inContext(freshCtx):
tpe.namedTupleElementTypes
.map { (name, tpe) =>
val symbol = newSymbol(owner = NoSymbol, name, EmptyFlags, tpe)
val denot = SymDenotation(symbol, NoSymbol, name, EmptyFlags, tpe)
name -> denot
}
.toSeq
.filter((name, denot) => include(denot, name))
.groupByName

val qualTpe = qual.typeOpt
if qualTpe.isNamedTupleType then
namedTupleCompletionsFromType(qualTpe)
else if qualTpe.derivesFrom(defn.SelectableClass) then
val pre = if !TypeOps.isLegalPrefix(qualTpe) then Types.SkolemType(qualTpe) else qualTpe
val fieldsType = pre.select(StdNames.tpnme.Fields).dealias.simplified
namedTupleCompletionsFromType(fieldsType)
else Map.empty

/** Completions from extension methods */
private def extensionCompletions(qual: tpd.Tree)(using Context): CompletionMap =
def asDefLikeType(tpe: Type): Type = tpe match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1723,4 +1723,14 @@ class CompletionTest {
.completion(m5, Set())
.completion(m6, Set())

@Test def namedTupleCompletion: Unit =
code"""|import scala.language.experimental.namedTuples
|
|val person: (name: String, city: String) =
| (name = "Jamie", city = "Lausanne")
|
|val n = person.na$m1
|"""
.completion(m1, Set(("name", Field, "String")))

}
Original file line number Diff line number Diff line change
Expand Up @@ -1983,3 +1983,50 @@ class CompletionSuite extends BaseCompletionSuite:
|val foo: SomeClass
|""".stripMargin,
)

@Test def `namedTuple completions` =
check(
"""|import scala.language.experimental.namedTuples
|import scala.NamedTuple.*
|
|val person = (name = "Jamie", city = "Lausanne")
|
|val n = person.na@@""".stripMargin,
"name: String",
filter = _.contains("name")
)

@Test def `Selectable with namedTuple Fields member` =
check(
"""|import scala.language.experimental.namedTuples
|import scala.NamedTuple.*
|
|class NamedTupleSelectable extends Selectable {
| type Fields <: AnyNamedTuple
| def selectDynamic(name: String): Any = ???
|}
|
|val person2 = new NamedTupleSelectable {
| type Fields = (name: String, city: String)
|}
|
|val n = person2.na@@""".stripMargin,
"""|name: String
|selectDynamic(name: String): Any
""".stripMargin,
filter = _.contains("name")
)

@Test def `Selectable without namedTuple Fields mamber` =
check(
"""|class NonNamedTupleSelectable extends Selectable {
| def selectDynamic(name: String): Any = ???
|}
|
|val person2 = new NonNamedTupleSelectable {}
|
|val n = person2.na@@""".stripMargin,
"""|selectDynamic(name: String): Any
""".stripMargin,
filter = _.contains("name")
)

0 comments on commit 3e78238

Please sign in to comment.