Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement completions for named tuple fields #21202

Merged
merged 3 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 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,27 @@ object Completion:
.toSeq
.groupByName

/** Completions for named tuples */
private def namedTupleCompletions(qual: tpd.Tree)(using Context): CompletionMap =
def namedTupleCompleionsFromType(tpe: Type): CompletionMap =
KacperFKorban marked this conversation as resolved.
Show resolved Hide resolved
tpe.namedTupleElementTypes
KacperFKorban marked this conversation as resolved.
Show resolved Hide resolved
.map { (name, tpe) =>
val symbol = newSymbol(owner = NoSymbol, name, EmptyFlags, tpe)
val denot = SymDenotation(symbol, NoSymbol, name, EmptyFlags, tpe)
name -> denot
}
.toSeq
.groupByName

val qualTpe = qual.typeOpt
if qualTpe.isNamedTupleType then
namedTupleCompleionsFromType(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
namedTupleCompleionsFromType(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 @@ -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")
)