From 376e4cfd70d339f0e235b5b8f6dabbe8e01ceae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Tue, 24 May 2022 15:13:02 +0200 Subject: [PATCH] Support code completion for refined types --- .../tools/dotc/interactive/Completion.scala | 24 ++- .../dotty/tools/dotc/typer/Applications.scala | 21 --- .../dotty/tools/dotc/typer/Implicits.scala | 8 +- .../tools/languageserver/CompletionTest.scala | 156 ++++++++++++++++++ 4 files changed, 179 insertions(+), 30 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 9fda94cb08d4..476da61b5426 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -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 @@ -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. */ @@ -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 } /** @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 036edc2683a9..9e4a1edd6e84 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 1255c1e373c4..2544fe1bb04e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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) @@ -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 } } diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 730025a40434..f8d4d0726482 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -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"))) + } }