Skip to content

Commit e2b8098

Browse files
authored
Merge pull request #15283 from prolativ/refinement-completions
Support code completion for refined types
2 parents e9de73c + 376e4cf commit e2b8098

File tree

4 files changed

+179
-30
lines changed

4 files changed

+179
-30
lines changed

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

+19-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import dotty.tools.dotc.core.Names.{Name, TermName}
1212
import dotty.tools.dotc.core.NameKinds.SimpleNameKind
1313
import dotty.tools.dotc.core.NameOps._
1414
import dotty.tools.dotc.core.Scopes._
15-
import dotty.tools.dotc.core.Symbols.{Symbol, defn}
15+
import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol, defn, newSymbol}
1616
import dotty.tools.dotc.core.StdNames.nme
1717
import dotty.tools.dotc.core.SymDenotations.SymDenotation
1818
import dotty.tools.dotc.core.TypeError
19-
import dotty.tools.dotc.core.Types.{AppliedType, ExprType, MethodOrPoly, NameFilter, NoType, TermRef, Type}
19+
import dotty.tools.dotc.core.Types.{AppliedType, ExprType, MethodOrPoly, NameFilter, NoType, RefinedType, TermRef, Type, TypeProxy}
2020
import dotty.tools.dotc.parsing.Tokens
2121
import dotty.tools.dotc.util.Chars
2222
import dotty.tools.dotc.util.SourcePosition
@@ -474,6 +474,18 @@ object Completion {
474474
|| (mode.is(Mode.Type) && (sym.isType || sym.isStableMember)))
475475
)
476476

477+
private def extractRefinements(site: Type)(using Context): Seq[SingleDenotation] =
478+
site match
479+
case RefinedType(parent, name, info) =>
480+
val flags = info match
481+
case _: (ExprType | MethodOrPoly) => Method
482+
case _ => EmptyFlags
483+
val symbol = newSymbol(owner = NoSymbol, name, flags, info)
484+
val denot = SymDenotation(symbol, NoSymbol, name, flags, info)
485+
denot +: extractRefinements(parent)
486+
case tp: TypeProxy => extractRefinements(tp.underlying)
487+
case _ => List.empty
488+
477489
/** @param site The type to inspect.
478490
* @return The members of `site` that are accessible and pass the include filter.
479491
*/
@@ -488,10 +500,13 @@ object Completion {
488500
catch
489501
case ex: TypeError =>
490502

491-
site.memberDenots(completionsFilter, appendMemberSyms).collect {
503+
val members = site.memberDenots(completionsFilter, appendMemberSyms).collect {
492504
case mbr if include(mbr, mbr.name)
493505
&& mbr.symbol.isAccessibleFrom(site) => mbr
494506
}
507+
val refinements = extractRefinements(site).filter(mbr => include(mbr, mbr.name))
508+
509+
members ++ refinements
495510
}
496511

497512
/**
@@ -504,8 +519,7 @@ object Completion {
504519
private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] = {
505520
val typer = ctx.typer
506521
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
507-
val convertedTrees = conversions.flatMap(typer.tryApplyingImplicitConversion(_, qual))
508-
val targets = convertedTrees.map(_.tpe.finalResultType)
522+
val targets = conversions.map(_.tree.tpe)
509523

510524
interactiv.println(i"implicit conversion targets considered: ${targets.toList}%, %")
511525
targets

compiler/src/dotty/tools/dotc/typer/Applications.scala

-21
Original file line numberDiff line numberDiff line change
@@ -2289,27 +2289,6 @@ trait Applications extends Compatibility {
22892289
catch
22902290
case NonFatal(_) => None
22912291

2292-
/** Tries applying conversion method reference to a provided receiver
2293-
*
2294-
* returns converted tree in case of success.
2295-
* None is returned if conversion method application fails.
2296-
*/
2297-
def tryApplyingImplicitConversion(conversionMethodRef: TermRef, receiver: Tree)(using Context): Option[Tree] =
2298-
val conversionMethodTree = ref(conversionMethodRef, needLoad = false)
2299-
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter))
2300-
2301-
try
2302-
val appliedTree = inContext(newCtx) {
2303-
typed(untpd.Apply(conversionMethodTree, untpd.TypedSplice(receiver) :: Nil))
2304-
}
2305-
2306-
if appliedTree.tpe.exists && !appliedTree.tpe.isError then
2307-
Some(appliedTree)
2308-
else
2309-
None
2310-
catch
2311-
case NonFatal(x) => None
2312-
23132292
def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean =
23142293
methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType &&
23152294
tryApplyingExtensionMethod(methodRef, nullLiteral.asInstance(receiverType)).nonEmpty

compiler/src/dotty/tools/dotc/typer/Implicits.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -1125,9 +1125,9 @@ trait Implicits:
11251125
ctx.reporter.removeBufferedMessages
11261126
adapted.tpe match {
11271127
case _: SearchFailureType => SearchFailure(adapted)
1128-
case error: PreviousErrorType if !adapted.symbol.isAccessibleFrom(cand.ref.prefix) =>
1128+
case error: PreviousErrorType if !adapted.symbol.isAccessibleFrom(cand.ref.prefix) =>
11291129
SearchFailure(adapted.withType(new NestedFailure(error.msg, pt)))
1130-
case _ =>
1130+
case _ =>
11311131
// Special case for `$conforms` and `<:<.refl`. Showing them to the users brings
11321132
// no value, so we instead report a `NoMatchingImplicitsFailure`
11331133
if (adapted.symbol == defn.Predef_conforms || adapted.symbol == defn.SubType_refl)
@@ -1523,11 +1523,11 @@ trait Implicits:
15231523
def implicitScope(tp: Type): OfTypeImplicits = ctx.run.nn.implicitScope(tp)
15241524

15251525
/** All available implicits, without ranking */
1526-
def allImplicits: Set[TermRef] = {
1526+
def allImplicits: Set[SearchSuccess] = {
15271527
val contextuals = ctx.implicits.eligible(wildProto).map(tryImplicit(_, contextual = true))
15281528
val inscope = implicitScope(wildProto).eligible.map(tryImplicit(_, contextual = false))
15291529
(contextuals.toSet ++ inscope).collect {
1530-
case success: SearchSuccess => success.ref
1530+
case success: SearchSuccess => success
15311531
}
15321532
}
15331533

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

+156
Original file line numberDiff line numberDiff line change
@@ -1285,4 +1285,160 @@ class CompletionTest {
12851285
.completion(m2, ("symbol", Field, "String"))
12861286
}
12871287

1288+
@Test def refinedNonselectable: Unit = {
1289+
code"""trait Foo
1290+
|trait Bar extends Foo
1291+
|
1292+
|trait Quux:
1293+
| def aaa: Foo
1294+
| def bbb: Foo
1295+
| def ccc(s: String): String
1296+
| private def ddd(): Boolean = ???
1297+
|
1298+
|val quux = new Quux:
1299+
| def aaa: Foo = ???
1300+
| def bbb: Bar = ??? // overriden signature
1301+
| def ccc(s: String): String = ???
1302+
| def ccc(i: Int): Int = ??? // overloaded
1303+
| private def ddd(): Boolean = ???
1304+
| def eee(): Boolean = ???
1305+
| private def fff(): Boolean = ???
1306+
| val ggg: Int = ???
1307+
|
1308+
|val a = quux.aa${m1}
1309+
|val b = quux.bb${m2}
1310+
|val c = quux.cc${m3}
1311+
|val d = quux.dd${m4}
1312+
|val e = quux.ee${m5}
1313+
|val f = quux.ff${m6}
1314+
|val g = quux.gg${m7}
1315+
|object imported:
1316+
| import quux.*
1317+
| val b = bb${m8}"""
1318+
.completion(m1, ("aaa", Method, "=> Foo"))
1319+
.completion(m2, ("bbb", Method, "=> Bar"))
1320+
.completion(m3, ("ccc", Method, "(s: String): String"))
1321+
.noCompletions(m4)
1322+
.noCompletions(m5)
1323+
.noCompletions(m6)
1324+
.noCompletions(m7)
1325+
.completion(m8, ("bbb", Method, "=> Bar"))
1326+
}
1327+
1328+
@Test def refinedSelectable: Unit = {
1329+
code"""trait Foo
1330+
|trait Bar extends Foo
1331+
|
1332+
|trait Quux extends Selectable:
1333+
| def aaa: Foo
1334+
| def bbb: Foo
1335+
| def ccc(s: String): String
1336+
| private def ddd(): Boolean = ???
1337+
|
1338+
|val quux = new Quux:
1339+
| def aaa: Foo = ???
1340+
| def bbb: Bar = ??? // overriden signature
1341+
| def ccc(s: String): String = ???
1342+
| def ccc(i: Int): Int = ??? // overloaded
1343+
| private def ddd(): Boolean = ???
1344+
| def eee(): Boolean = ???
1345+
| private def fff(): Boolean = ???
1346+
| val ggg: Int = ???
1347+
|
1348+
|val a = quux.aa${m1}
1349+
|val b = quux.bb${m2}
1350+
|val c = quux.cc${m3}
1351+
|val d = quux.dd${m4}
1352+
|val e = quux.ee${m5}
1353+
|val f = quux.ff${m6}
1354+
|val g = quux.gg${m7}
1355+
|object imported:
1356+
| import quux.*
1357+
| val b = bb${m8}"""
1358+
.completion(m1, ("aaa", Method, "=> Foo"))
1359+
.completion(m2, ("bbb", Method, "=> Bar"))
1360+
.completion(m3, ("ccc", Method, "(s: String): String"), ("ccc", Method, "(i: Int): Int"))
1361+
.noCompletions(m4)
1362+
.completion(m5, ("eee", Method, "(): Boolean"))
1363+
.noCompletions(m6)
1364+
.completion(m7, ("ggg", Field, "Int"))
1365+
.completion(m8, ("bbb", Method, "=> Bar"))
1366+
}
1367+
1368+
@Test def refinedSelectableFromImplicitConversion: Unit = {
1369+
code"""case class Wrapper[A](inner: A) extends Selectable
1370+
|object Wrapper:
1371+
| implicit def refineWrapper[A](wrapper: Wrapper[A])(using refiner: Refiner[A]): refiner.Refined = ???
1372+
|
1373+
|trait Refiner[A]:
1374+
| type Refined
1375+
|
1376+
|case class Foo(name: String)
1377+
|object Foo:
1378+
| given Refiner[Foo] with
1379+
| type Refined = Wrapper[Foo] { def name: String }
1380+
|
1381+
|def fooWrapper: Wrapper[Foo] = ???
1382+
|def name: Wrapper[String] = fooWrapper.na${m1}"""
1383+
.completion(m1, Set(("name", Method, "=> String")))
1384+
}
1385+
1386+
@Test def transparentMacro: Unit = {
1387+
val p1 = Project.withSources(
1388+
code"""package p1
1389+
|import scala.quoted.*
1390+
|
1391+
|trait Foo:
1392+
| def xxxa = 0
1393+
|
1394+
|class Bar extends Foo:
1395+
| def xxxb = 1
1396+
|
1397+
|transparent inline def bar: Foo = $${ barImpl }
1398+
|def barImpl(using Quotes) = '{ new Bar }
1399+
|"""
1400+
)
1401+
val p2 = Project.dependingOn(p1).withSources(
1402+
code"""package p2
1403+
|val x = p1.bar.xx${m1}
1404+
"""
1405+
)
1406+
withProjects(p1, p2).completion(m1, Set(("xxxa", Method, "=> Int"), ("xxxb", Method, "=> Int")))
1407+
}
1408+
1409+
1410+
@Test def implicitlyRefinedWithTransparentMacro: Unit = {
1411+
val p1 = Project.withSources(
1412+
code"""package p1
1413+
|import scala.quoted.*
1414+
|import scala.language.implicitConversions
1415+
|
1416+
|case class Wrapper[A](inner: A) extends Selectable:
1417+
| def selectDynamic(name: String) = ???
1418+
|object Wrapper:
1419+
| implicit def refineWrapper[A](wrapper: Wrapper[A])(using refiner: Refiner[A]): refiner.Refined = ???
1420+
|
1421+
|trait Refiner[A]:
1422+
| type Refined
1423+
|
1424+
|case class Foo(name: String)
1425+
|object Foo:
1426+
| transparent inline given fooRefiner: Refiner[Foo] = $${ fooRefinerImpl }
1427+
|
1428+
|def fooRefinerImpl(using Quotes): Expr[Refiner[Foo]] = '{
1429+
| new Refiner[Foo] {
1430+
| type Refined = Wrapper[Foo] { def name: String }
1431+
| }
1432+
|}
1433+
|"""
1434+
)
1435+
val p2 = Project.dependingOn(p1).withSources(
1436+
code"""package p2
1437+
|import p1.*
1438+
|def fooWrapper: Wrapper[Foo] = ???
1439+
|def name = fooWrapper.na${m1}
1440+
"""
1441+
)
1442+
withProjects(p1, p2).completion(m1, Set(("name", Method, "=> String")))
1443+
}
12881444
}

0 commit comments

Comments
 (0)