Skip to content

Commit

Permalink
fix: InverseSemanticdbSymbols for symbolic names
Browse files Browse the repository at this point in the history
Symbols in compiler have encoded names (eg. `$at$at` instead of `@@`) so we need to encode semanticdb symbols before matching on name.
  • Loading branch information
jkciesluk committed Aug 16, 2023
1 parent 0074f74 commit 0421dd0
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 8 deletions.
25 changes: 17 additions & 8 deletions mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,11 @@ class MetalsGlobal(
else if (s.isEmptyPackage) rootMirror.EmptyPackage :: Nil
else if (s.isPackage) {
try {
rootMirror.staticPackage(s.stripSuffix("/").replace("/", ".")) :: Nil
val pkg = s
.split("/")
.map(n => TermName(n.stripBackticks).encoded)
.mkString(".")
rootMirror.staticPackage(pkg) :: Nil
} catch {
case NonFatal(_) =>
Nil
Expand All @@ -525,20 +529,25 @@ class MetalsGlobal(
case Descriptor.None =>
Nil
case Descriptor.Type(value) =>
val member = owner.info.decl(TypeName(value)) :: Nil
if (sym.isJava) owner.info.decl(TermName(value)) :: member
val member = owner.info.decl(TypeName(value).encode) :: Nil
if (sym.isJava)
owner.info.decl(TermName(value).encode) :: member
else member
case Descriptor.Term(value) =>
owner.info.decl(TermName(value)) :: Nil
owner.info.decl(TermName(value).encode) :: Nil
case Descriptor.Package(value) =>
owner.info.decl(TermName(value)) :: Nil
owner.info.decl(TermName(value).encode) :: Nil
case Descriptor.Parameter(value) =>
owner.paramss.flatten.filter(_.name.containsName(value))
owner.paramss.flatten.filter(
_.name.decodedName.containsName(value)
)
case Descriptor.TypeParameter(value) =>
owner.typeParams.filter(_.name.containsName(value))
owner.typeParams.filter(
_.name.decodedName.containsName(value)
)
case Descriptor.Method(value, _) =>
owner.info
.decl(TermName(value))
.decl(TermName(value).encode)
.alternatives
.iterator
.filter(sym => semanticdbSymbol(sym) == s)
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/src/main/scala/tests/BaseCompletionLspSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import scala.concurrent.Future
import scala.meta.internal.metals.TextEdits

import munit.Location
import org.eclipse.lsp4j.CompletionItem
import org.eclipse.lsp4j.CompletionList

abstract class BaseCompletionLspSuite(name: String) extends BaseLspSuite(name) {
Expand All @@ -23,6 +24,7 @@ abstract class BaseCompletionLspSuite(name: String) extends BaseLspSuite(name) {
.textContentsOnDisk(filename)
.replace("// @@", query.replace("@@", ""))
for {
_ <- server.didFocus(filename)
_ <- server.didChange(filename)(_ => text)
completion <- server.completionList(filename, query)
} yield {
Expand Down Expand Up @@ -78,6 +80,57 @@ abstract class BaseCompletionLspSuite(name: String) extends BaseLspSuite(name) {
}
}

def withCompletionItemResolve(
query: String,
project: Char = 'a',
filter: String => Boolean = _ => true,
index: Int = 0,
)(
fn: CompletionItem => Unit
): Future[Unit] = {
import scala.collection.JavaConverters._
val filename = s"$project/src/main/scala/$project/${project.toUpper}.scala"
val text = server
.textContentsOnDisk(filename)
.replace("// @@", query.replace("@@", ""))
for {
_ <- server.didFocus(filename)
_ <- server.didChange(filename)(_ => text)
completion <- server.completionList(filename, query)
items =
completion
.getItems()
.asScala
.filter(item => filter(item.getLabel))
.toList
_ = assert(items.length > index, "Completion item index out of bounds")
resolved <- server.completionItemResolve(items(index))
} yield fn(resolved)
}

def assertCompletionItemResolve(
query: String,
expectedLabel: String,
expectedDoc: Option[String] = None,
project: Char = 'a',
filter: String => Boolean = _ => true,
index: Int = 0,
)(implicit loc: Location): Future[Unit] = {
withCompletionItemResolve(query, project, filter, index) { resolved =>
assertNoDiff(resolved.getLabel(), expectedLabel)
expectedDoc.foreach { doc =>
val obtainedDoc =
if (resolved.getDocumentation() == null) ""
else if (resolved.getDocumentation().isRight()) {
resolved.getDocumentation().getRight().getValue()
} else {
resolved.getDocumentation().getLeft()
}
assertNoDiff(obtainedDoc, doc)
}
}
}

def basicTest(scalaVersion: String): Future[Unit] = {
cleanWorkspace()
for {
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/src/main/scala/tests/TestingServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,12 @@ final case class TestingServer(
}
}

def completionItemResolve(
item: l.CompletionItem
): Future[l.CompletionItem] = {
fullServer.completionItemResolve(item).asScala
}

def codeAction(
filename: String,
query: String,
Expand Down
49 changes: 49 additions & 0 deletions tests/unit/src/test/scala/tests/CompletionLspSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,53 @@ class CompletionLspSuite extends BaseCompletionLspSuite("completion") {
)
} yield ()
}

test("symbolic-name") {
cleanWorkspace()
for {
_ <- initialize(
"""/metals.json
|{
| "a": {}
|}
|/a/src/main/scala/a/A.scala
|package a
|
|abstract class Base {
| /**
| * Some documentation
| */
| type !![A, B] = A with B
|}
|object Main extends Base {
| // @@
|}
|""".stripMargin
)
_ <- assertCompletionItemResolve(
"val x = List(1).:::@@",
expectedLabel = ":::[B >: Int](prefix: List[B]): List[B]",
expectedDoc = Some(
"""|Adds the elements of a given list in front of this list.
|
|Example:
|
|```
|List(1, 2) ::: List(3, 4) = List(3, 4).:::(List(1, 2)) = List(1, 2, 3, 4)
|```
|**Parameters**
|- `prefix`: The list elements to prepend.
|
|**Returns:** a list resulting from the concatenation of the given
| list `prefix` and this list.
|""".stripMargin
),
)
_ <- assertCompletionItemResolve(
"val x: !!@@",
expectedLabel = "!!",
expectedDoc = Some("Some documentation"),
)
} yield ()
}
}

0 comments on commit 0421dd0

Please sign in to comment.