Skip to content

Commit

Permalink
Backport "improvement: Support completions for implicit classes" to L…
Browse files Browse the repository at this point in the history
…TS (#20808)

Backports #19314 to the LTS branch.

PR submitted by the release tooling.
[skip ci]
  • Loading branch information
WojciechMazur authored Jun 26, 2024
2 parents 3a6bad0 + c5d3a93 commit 74b2da5
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import scala.meta.pc.*
import scala.util.control.NonFatal

import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.core.Names.*
import dotty.tools.dotc.core.Symbols.*

Expand All @@ -19,8 +20,14 @@ class CompilerSearchVisitor(

val logger: Logger = Logger.getLogger(classOf[CompilerSearchVisitor].getName().nn).nn

private def isAccessibleImplicitClass(sym: Symbol) =
val owner = sym.maybeOwner
owner != NoSymbol && owner.isClass &&
owner.is(Flags.Implicit) &&
owner.isStatic && owner.isPublic

private def isAccessible(sym: Symbol): Boolean = try
sym != NoSymbol && sym.isPublic && sym.isStatic
sym != NoSymbol && sym.isPublic && sym.isStatic || isAccessibleImplicitClass(sym)
catch
case err: AssertionError =>
logger.log(Level.WARNING, err.getMessage())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,16 @@ object SemanticdbSymbols:
// however in scalac this method is defined only in `module Files`
if typeSym.is(JavaDefined) then
typeSym :: owner.info.decl(termName(value)).symbol :: Nil
/**
* Looks like decl doesn't work for:
* package a:
* implicit class <<A>> (i: Int):
* def inc = i + 1
*/
else if typeSym == NoSymbol then
owner.info.member(typeName(value)).symbol :: Nil
else typeSym :: Nil
end if
case Descriptor.Term(value) =>
val outSymbol = owner.info.decl(termName(value)).symbol
if outSymbol.exists
Expand Down Expand Up @@ -92,6 +101,8 @@ object SemanticdbSymbols:
.map(_.symbol)
.filter(sym => symbolName(sym) == s)
.toList
end match
end tryMember

parentSymbol.flatMap(tryMember)
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class CompletionProvider(

def mkItemWithImports(
v: CompletionValue.Workspace | CompletionValue.Extension |
CompletionValue.Interpolator
CompletionValue.Interpolator | CompletionValue.ImplicitClass
) =
val sym = v.symbol
path match
Expand Down Expand Up @@ -264,7 +264,7 @@ class CompletionProvider(
end mkItemWithImports

completion match
case v: (CompletionValue.Workspace | CompletionValue.Extension) =>
case v: (CompletionValue.Workspace | CompletionValue.Extension | CompletionValue.ImplicitClass) =>
mkItemWithImports(v)
case v: CompletionValue.Interpolator if v.isWorkspace || v.isExtension =>
mkItemWithImports(v)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,20 @@ object CompletionValue:
override def isFromWorkspace: Boolean = true
override def completionItemDataKind: Integer = CompletionSource.WorkspaceKind.ordinal

/**
* CompletionValue for old implicit classes methods via SymbolSearch
*/
case class ImplicitClass(
label: String,
symbol: Symbol,
override val snippetSuffix: CompletionSuffix,
override val importSymbol: Symbol,
) extends Symbolic:
override def completionItemKind(using Context): CompletionItemKind =
CompletionItemKind.Method
override def description(printer: ShortenedTypePrinter)(using Context): String =
s"${printer.completionSymbol(symbol)} (implicit)"

/**
* CompletionValue for extension methods via SymbolSearch
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,14 +519,38 @@ class Completions(
Some(search.search(query, buildTargetIdentifier, visitor).nn)
case CompletionKind.Members =>
val visitor = new CompilerSearchVisitor(sym =>
if sym.is(ExtensionMethod) &&
def isExtensionMethod = sym.is(ExtensionMethod) &&
qualType.widenDealias <:< sym.extensionParam.info.widenDealias
then
def isImplicitClass(owner: Symbol) =
val constructorParam =
owner.info
.membersBasedOnFlags(
Flags.ParamAccessor,
Flags.EmptyFlags,
)
.headOption
.map(_.info)
owner.isClass && owner.is(Flags.Implicit) &&
constructorParam.exists(p =>
qualType.widenDealias <:< p.widenDealias
)
end isImplicitClass

def isImplicitClassMethod = sym.is(Flags.Method) && !sym.isConstructor &&
isImplicitClass(sym.maybeOwner)

if isExtensionMethod then
completionsWithSuffix(
sym,
sym.decodedName,
CompletionValue.Extension(_, _, _)
).map(visit).forall(_ == true)
else if isImplicitClassMethod then
completionsWithSuffix(
sym,
sym.decodedName,
CompletionValue.ImplicitClass(_, _, _, sym.maybeOwner),
).map(visit).forall(_ == true)
else false,
)
Some(search.searchMethods(query, buildTargetIdentifier, visitor).nn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|""".stripMargin
)

@Test def `simple-old-syntax` =
check(
"""|package example
|
|object Test:
| implicit class TestOps(a: Int):
| def testOps(b: Int): String = ???
|
|def main = 100.test@@
|""".stripMargin,
"""|testOps(b: Int): String (implicit)
|""".stripMargin
)

@Test def `simple2` =
check(
"""|package example
Expand All @@ -35,6 +49,21 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
filter = _.contains("(extension)")
)

@Test def `simple2-old-syntax` =
check(
"""|package example
|
|object enrichments:
| implicit class TestOps(a: Int):
| def testOps(b: Int): String = ???
|
|def main = 100.t@@
|""".stripMargin,
"""|testOps(b: Int): String (implicit)
|""".stripMargin,
filter = _.contains("(implicit)")
)

@Test def `filter-by-type` =
check(
"""|package example
Expand All @@ -52,6 +81,22 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
filter = _.contains("(extension)")
)

@Test def `filter-by-type-old` =
check(
"""|package example
|
|object enrichments:
| implicit class A(num: Int):
| def identity2: Int = num + 1
| implicit class B(str: String):
| def identity: String = str
|
|def main = "foo".iden@@
|""".stripMargin,
"""|identity: String (implicit)
|""".stripMargin // identity2 won't be available
)

@Test def `filter-by-type-subtype` =
check(
"""|package example
Expand All @@ -70,6 +115,24 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
filter = _.contains("(extension)")
)

@Test def `filter-by-type-subtype-old` =
check(
"""|package example
|
|class A
|class B extends A
|
|object enrichments:
| implicit class Test(a: A):
| def doSomething: A = a
|
|def main = (new B).do@@
|""".stripMargin,
"""|doSomething: A (implicit)
|""".stripMargin,
filter = _.contains("(implicit)")
)

@Test def `simple-edit` =
checkEdit(
"""|package example
Expand All @@ -92,6 +155,28 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|""".stripMargin
)

@Test def `simple-edit-old` =
checkEdit(
"""|package example
|
|object enrichments:
| implicit class A (num: Int):
| def incr: Int = num + 1
|
|def main = 100.inc@@
|""".stripMargin,
"""|package example
|
|import example.enrichments.A
|
|object enrichments:
| implicit class A (num: Int):
| def incr: Int = num + 1
|
|def main = 100.incr
|""".stripMargin
)

@Test def `simple-edit-suffix` =
checkEdit(
"""|package example
Expand All @@ -114,6 +199,28 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|""".stripMargin
)

@Test def `simple-edit-suffix-old` =
checkEdit(
"""|package example
|
|object enrichments:
| implicit class A (val num: Int):
| def plus(other: Int): Int = num + other
|
|def main = 100.pl@@
|""".stripMargin,
"""|package example
|
|import example.enrichments.A
|
|object enrichments:
| implicit class A (val num: Int):
| def plus(other: Int): Int = num + other
|
|def main = 100.plus($0)
|""".stripMargin
)

@Test def `simple-empty` =
check(
"""|package example
Expand All @@ -129,6 +236,21 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
filter = _.contains("(extension)")
)

@Test def `simple-empty-old` =
check(
"""|package example
|
|object enrichments:
| implicit class TestOps(a: Int):
| def testOps(b: Int): String = ???
|
|def main = 100.@@
|""".stripMargin,
"""|testOps(b: Int): String (implicit)
|""".stripMargin,
filter = _.contains("(implicit)")
)

@Test def `directly-in-pkg1` =
check(
"""|
Expand All @@ -143,6 +265,20 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|""".stripMargin
)

@Test def `directly-in-pkg1-old` =
check(
"""|
|package examples:
| implicit class A(num: Int):
| def incr: Int = num + 1
|
|package examples2:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (implicit)
|""".stripMargin
)

@Test def `directly-in-pkg2` =
check(
"""|package example:
Expand All @@ -157,6 +293,20 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|""".stripMargin
)

@Test def `directly-in-pkg2-old` =
check(
"""|package examples:
| object X:
| def fooBar(num: Int) = num + 1
| implicit class A (num: Int) { def incr: Int = num + 1 }
|
|package examples2:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (implicit)
|""".stripMargin
)

@Test def `nested-pkg` =
check(
"""|package a: // some comment
Expand All @@ -175,7 +325,25 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|""".stripMargin
)

@Test def `name-conflict` =
@Test def `nested-pkg-old` =
check(
"""|package aa: // some comment
| package cc:
| implicit class A (num: Int):
| def increment2 = num + 2
| implicit class A (num: Int):
| def increment = num + 1
|
|
|package bb:
| def main: Unit = 123.incre@@
|""".stripMargin,
"""|increment: Int (implicit)
|increment2: Int (implicit)
|""".stripMargin
)

@Test def `name-conflict` =
checkEdit(
"""
|package example
Expand Down

0 comments on commit 74b2da5

Please sign in to comment.