Skip to content

Support code completion for refined types #15283

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

Merged
merged 1 commit into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 19 additions & 5 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import dotty.tools.dotc.core.Names.{Name, TermName}
import dotty.tools.dotc.core.NameKinds.SimpleNameKind
import dotty.tools.dotc.core.NameOps._
import dotty.tools.dotc.core.Scopes._
import dotty.tools.dotc.core.Symbols.{Symbol, defn}
import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol, defn, newSymbol}
import dotty.tools.dotc.core.StdNames.nme
import dotty.tools.dotc.core.SymDenotations.SymDenotation
import dotty.tools.dotc.core.TypeError
import dotty.tools.dotc.core.Types.{AppliedType, ExprType, MethodOrPoly, NameFilter, NoType, TermRef, Type}
import dotty.tools.dotc.core.Types.{AppliedType, ExprType, MethodOrPoly, NameFilter, NoType, RefinedType, TermRef, Type, TypeProxy}
import dotty.tools.dotc.parsing.Tokens
import dotty.tools.dotc.util.Chars
import dotty.tools.dotc.util.SourcePosition
Expand Down Expand Up @@ -474,6 +474,18 @@ object Completion {
|| (mode.is(Mode.Type) && (sym.isType || sym.isStableMember)))
)

private def extractRefinements(site: Type)(using Context): Seq[SingleDenotation] =
site match
case RefinedType(parent, name, info) =>
val flags = info match
case _: (ExprType | MethodOrPoly) => Method
case _ => EmptyFlags
val symbol = newSymbol(owner = NoSymbol, name, flags, info)
val denot = SymDenotation(symbol, NoSymbol, name, flags, info)
denot +: extractRefinements(parent)
case tp: TypeProxy => extractRefinements(tp.underlying)
case _ => List.empty

/** @param site The type to inspect.
* @return The members of `site` that are accessible and pass the include filter.
*/
Expand All @@ -488,10 +500,13 @@ object Completion {
catch
case ex: TypeError =>

site.memberDenots(completionsFilter, appendMemberSyms).collect {
val members = site.memberDenots(completionsFilter, appendMemberSyms).collect {
case mbr if include(mbr, mbr.name)
&& mbr.symbol.isAccessibleFrom(site) => mbr
}
val refinements = extractRefinements(site).filter(mbr => include(mbr, mbr.name))

members ++ refinements
}

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

interactiv.println(i"implicit conversion targets considered: ${targets.toList}%, %")
targets
Expand Down
21 changes: 0 additions & 21 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2289,27 +2289,6 @@ trait Applications extends Compatibility {
catch
case NonFatal(_) => None

/** Tries applying conversion method reference to a provided receiver
*
* returns converted tree in case of success.
* None is returned if conversion method application fails.
*/
def tryApplyingImplicitConversion(conversionMethodRef: TermRef, receiver: Tree)(using Context): Option[Tree] =
val conversionMethodTree = ref(conversionMethodRef, needLoad = false)
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter))

try
val appliedTree = inContext(newCtx) {
typed(untpd.Apply(conversionMethodTree, untpd.TypedSplice(receiver) :: Nil))
}

if appliedTree.tpe.exists && !appliedTree.tpe.isError then
Some(appliedTree)
else
None
catch
case NonFatal(x) => None

def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean =
methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType &&
tryApplyingExtensionMethod(methodRef, nullLiteral.asInstance(receiverType)).nonEmpty
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1125,9 +1125,9 @@ trait Implicits:
ctx.reporter.removeBufferedMessages
adapted.tpe match {
case _: SearchFailureType => SearchFailure(adapted)
case error: PreviousErrorType if !adapted.symbol.isAccessibleFrom(cand.ref.prefix) =>
case error: PreviousErrorType if !adapted.symbol.isAccessibleFrom(cand.ref.prefix) =>
SearchFailure(adapted.withType(new NestedFailure(error.msg, pt)))
case _ =>
case _ =>
// Special case for `$conforms` and `<:<.refl`. Showing them to the users brings
// no value, so we instead report a `NoMatchingImplicitsFailure`
if (adapted.symbol == defn.Predef_conforms || adapted.symbol == defn.SubType_refl)
Expand Down Expand Up @@ -1523,11 +1523,11 @@ trait Implicits:
def implicitScope(tp: Type): OfTypeImplicits = ctx.run.nn.implicitScope(tp)

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

Expand Down
156 changes: 156 additions & 0 deletions language-server/test/dotty/tools/languageserver/CompletionTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1285,4 +1285,160 @@ class CompletionTest {
.completion(m2, ("symbol", Field, "String"))
}

@Test def refinedNonselectable: Unit = {
code"""trait Foo
|trait Bar extends Foo
|
|trait Quux:
| def aaa: Foo
| def bbb: Foo
| def ccc(s: String): String
| private def ddd(): Boolean = ???
|
|val quux = new Quux:
| def aaa: Foo = ???
| def bbb: Bar = ??? // overriden signature
| def ccc(s: String): String = ???
| def ccc(i: Int): Int = ??? // overloaded
| private def ddd(): Boolean = ???
| def eee(): Boolean = ???
| private def fff(): Boolean = ???
| val ggg: Int = ???
|
|val a = quux.aa${m1}
|val b = quux.bb${m2}
|val c = quux.cc${m3}
|val d = quux.dd${m4}
|val e = quux.ee${m5}
|val f = quux.ff${m6}
|val g = quux.gg${m7}
|object imported:
| import quux.*
| val b = bb${m8}"""
.completion(m1, ("aaa", Method, "=> Foo"))
.completion(m2, ("bbb", Method, "=> Bar"))
.completion(m3, ("ccc", Method, "(s: String): String"))
.noCompletions(m4)
.noCompletions(m5)
.noCompletions(m6)
.noCompletions(m7)
.completion(m8, ("bbb", Method, "=> Bar"))
}

@Test def refinedSelectable: Unit = {
code"""trait Foo
|trait Bar extends Foo
|
|trait Quux extends Selectable:
| def aaa: Foo
| def bbb: Foo
| def ccc(s: String): String
| private def ddd(): Boolean = ???
|
|val quux = new Quux:
| def aaa: Foo = ???
| def bbb: Bar = ??? // overriden signature
| def ccc(s: String): String = ???
| def ccc(i: Int): Int = ??? // overloaded
| private def ddd(): Boolean = ???
| def eee(): Boolean = ???
| private def fff(): Boolean = ???
| val ggg: Int = ???
|
|val a = quux.aa${m1}
|val b = quux.bb${m2}
|val c = quux.cc${m3}
|val d = quux.dd${m4}
|val e = quux.ee${m5}
|val f = quux.ff${m6}
|val g = quux.gg${m7}
|object imported:
| import quux.*
| val b = bb${m8}"""
.completion(m1, ("aaa", Method, "=> Foo"))
.completion(m2, ("bbb", Method, "=> Bar"))
.completion(m3, ("ccc", Method, "(s: String): String"), ("ccc", Method, "(i: Int): Int"))
.noCompletions(m4)
.completion(m5, ("eee", Method, "(): Boolean"))
.noCompletions(m6)
.completion(m7, ("ggg", Field, "Int"))
.completion(m8, ("bbb", Method, "=> Bar"))
}

@Test def refinedSelectableFromImplicitConversion: Unit = {
code"""case class Wrapper[A](inner: A) extends Selectable
|object Wrapper:
| implicit def refineWrapper[A](wrapper: Wrapper[A])(using refiner: Refiner[A]): refiner.Refined = ???
|
|trait Refiner[A]:
| type Refined
|
|case class Foo(name: String)
|object Foo:
| given Refiner[Foo] with
| type Refined = Wrapper[Foo] { def name: String }
|
|def fooWrapper: Wrapper[Foo] = ???
|def name: Wrapper[String] = fooWrapper.na${m1}"""
.completion(m1, Set(("name", Method, "=> String")))
}

@Test def transparentMacro: Unit = {
val p1 = Project.withSources(
code"""package p1
|import scala.quoted.*
|
|trait Foo:
| def xxxa = 0
|
|class Bar extends Foo:
| def xxxb = 1
|
|transparent inline def bar: Foo = $${ barImpl }
|def barImpl(using Quotes) = '{ new Bar }
|"""
)
val p2 = Project.dependingOn(p1).withSources(
code"""package p2
|val x = p1.bar.xx${m1}
"""
)
withProjects(p1, p2).completion(m1, Set(("xxxa", Method, "=> Int"), ("xxxb", Method, "=> Int")))
}


@Test def implicitlyRefinedWithTransparentMacro: Unit = {
val p1 = Project.withSources(
code"""package p1
|import scala.quoted.*
|import scala.language.implicitConversions
|
|case class Wrapper[A](inner: A) extends Selectable:
| def selectDynamic(name: String) = ???
|object Wrapper:
| implicit def refineWrapper[A](wrapper: Wrapper[A])(using refiner: Refiner[A]): refiner.Refined = ???
|
|trait Refiner[A]:
| type Refined
|
|case class Foo(name: String)
|object Foo:
| transparent inline given fooRefiner: Refiner[Foo] = $${ fooRefinerImpl }
|
|def fooRefinerImpl(using Quotes): Expr[Refiner[Foo]] = '{
| new Refiner[Foo] {
| type Refined = Wrapper[Foo] { def name: String }
| }
|}
|"""
)
val p2 = Project.dependingOn(p1).withSources(
code"""package p2
|import p1.*
|def fooWrapper: Wrapper[Foo] = ???
|def name = fooWrapper.na${m1}
"""
)
withProjects(p1, p2).completion(m1, Set(("name", Method, "=> String")))
}
}