Skip to content

Commit 4df84e4

Browse files
committed
improve extension consturct typechecking for completions
1 parent f3092a3 commit 4df84e4

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import dotty.tools.dotc.util.SourcePosition
2424

2525
import scala.collection.mutable
2626
import scala.util.control.NonFatal
27+
import dotty.tools.dotc.core.ContextOps.localContext
28+
import dotty.tools.dotc.core.Names
29+
import dotty.tools.dotc.core.Types
30+
import dotty.tools.dotc.core.Symbols
2731

2832
/**
2933
* One of the results of a completion query.
@@ -130,8 +134,8 @@ object Completion:
130134
case _ => 0
131135
}
132136

133-
/** Some information about the trees is lost after Typer such as Extension method definitions
134-
* are expanded into methods. In order to support completions in those cases
137+
/** Some information about the trees is lost after Typer such as Extension method construct
138+
* is expanded into methods. In order to support completions in those cases
135139
* we have to rely on untyped trees and only when types are necessary use typed trees.
136140
*/
137141
def resolveTypedOrUntypedPath(tpdPath: List[Tree], pos: SourcePosition)(using Context): List[untpd.Tree] =
@@ -144,6 +148,28 @@ object Completion:
144148
case (_: untpd.TypTree) :: _ => tpdPath
145149
case _ => untpdPath
146150

151+
/** Handle case when cursor position is inside extension method construct.
152+
* The extension method construct is then desugared into methods, and consturct parameters
153+
* are no longer a part of a typed tree, but instead are prepended to method parameters.
154+
*
155+
* @param untpdPath The typed or untyped path to the tree that is being completed
156+
* @param tpdPath The typed path that will be returned if no extension method construct is found
157+
* @param pos The cursor position
158+
*
159+
* @return Typed path to the parameter of the extension construct if found or tpdPath
160+
*/
161+
private def typeCheckExtensionConstructPath(
162+
untpdPath: List[untpd.Tree], tpdPath: List[Tree], pos: SourcePosition
163+
)(using Context): List[Tree] =
164+
untpdPath.collectFirst:
165+
case untpd.ExtMethods(paramss, _) =>
166+
val enclosingParam = paramss.flatten.find(_.span.contains(pos.span))
167+
enclosingParam.map: param =>
168+
ctx.typer.index(paramss.flatten)
169+
val typedEnclosingParam = ctx.typer.typed(param)
170+
Interactive.pathTo(typedEnclosingParam, pos.span)
171+
.flatten.getOrElse(tpdPath)
172+
147173
private def computeCompletions(pos: SourcePosition, tpdPath: List[Tree])(using Context): (Int, List[Completion]) =
148174
val path0 = resolveTypedOrUntypedPath(tpdPath, pos)
149175
val mode = completionMode(path0, pos)
@@ -154,10 +180,7 @@ object Completion:
154180

155181
val completer = new Completer(mode, prefix, pos)
156182

157-
val adjustedPath: List[Tree] = path0 match
158-
case (sel: untpd.Select) :: _ :: untpd.ExtMethods(_, _) :: _ => List(ctx.typer.typedExpr(sel))
159-
case _ => tpdPath
160-
183+
val adjustedPath = typeCheckExtensionConstructPath(path0, tpdPath, pos)
161184
val completions = adjustedPath match
162185
// Ignore synthetic select from `This` because in code it was `Ident`
163186
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis

language-server/test/dotty/tools/languageserver/CompletionTest.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,39 @@ class CompletionTest {
15431543
("TestSelect", Module, "Test.TestSelect"), ("TestSelect", Class, "Test.TestSelect")
15441544
))
15451545

1546+
@Test def extensionDefinitionCompletionsSelectNested: Unit =
1547+
code"""|object Test:
1548+
| object Test2:
1549+
| class TestSelect()
1550+
|object T:
1551+
| extension (x: Test.Test2.TestSel$m1)
1552+
|"""
1553+
.completion(m1, Set(
1554+
("TestSelect", Module, "Test.Test2.TestSelect"), ("TestSelect", Class, "Test.Test2.TestSelect")
1555+
))
1556+
1557+
@Test def extensionDefinitionCompletionsSelectInside: Unit =
1558+
code"""|object Test:
1559+
| object Test2:
1560+
| class TestSelect()
1561+
|object T:
1562+
| extension (x: Test.Te$m1.TestSelect)
1563+
|"""
1564+
.completion(m1, Set(("Test2", Module, "Test.Test2")))
1565+
1566+
@Test def extensionDefinitionCompletionsTypeParam: Unit =
1567+
code"""|object T:
1568+
| extension [TypeParam](x: TypePar$m1)
1569+
|"""
1570+
.completion(m1, Set(("TypeParam", Field, "T.TypeParam")))
1571+
1572+
1573+
@Test def typeParamCompletions: Unit =
1574+
code"""|object T:
1575+
| def xxx[TTT](x: TT$m1)
1576+
|"""
1577+
.completion(m1, Set(("TTT", Field, "T.TTT")))
1578+
15461579
@Test def selectDynamic: Unit =
15471580
code"""|import scala.language.dynamics
15481581
|class Foo extends Dynamic {

0 commit comments

Comments
 (0)