Skip to content

Try to extract name when backtick is missing #13369

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

Closed
wants to merge 1 commit into from
Closed
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
23 changes: 17 additions & 6 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ object Completion {
}.getOrElse("")

case (ref: untpd.RefTree) :: _ =>
if (ref.name == nme.ERROR) ""
if (ref.name == nme.ERROR) {
val content = ref.source.content()
// if the error resulted from unclosed back tick
if content(ref.span.start) == '`' then
content.slice(ref.span.start, ref.span.end).mkString
else
""
}
else ref.name.toString.take(pos.span.point - ref.span.point)

case _ =>
Expand All @@ -109,7 +116,9 @@ object Completion {
private def computeCompletions(pos: SourcePosition, path: List[Tree])(using Context): (Int, List[Completion]) = {
val mode = completionMode(path, pos)
val prefix = completionPrefix(path, pos)
val completer = new Completer(mode, prefix, pos)
val startsWithBacktick = prefix.headOption.exists(_ == '`')
val cleanPrefix = if (startsWithBacktick) prefix.drop(1) else prefix
val completer = new Completer(mode, cleanPrefix, pos)

val completions = path match {
// Ignore synthetic select from `This` because in code it was `Ident`
Expand All @@ -121,7 +130,7 @@ object Completion {
case _ => completer.scopeCompletions
}

val describedCompletions = describeCompletions(completions)
val describedCompletions = describeCompletions(completions, startsWithBacktick)
val offset = completionOffset(path)

interactiv.println(i"""completion with pos = $pos,
Expand All @@ -136,12 +145,14 @@ object Completion {
* Return the list of code completions with descriptions based on a mapping from names to the denotations they refer to.
* If several denotations share the same name, each denotation will be transformed into a separate completion item.
*/
def describeCompletions(completions: CompletionMap)(using Context): List[Completion] =
def describeCompletions(completions: CompletionMap, backtick: Boolean)(using Context): List[Completion] =
for
(name, denots) <- completions.toList
denot <- denots
yield
Completion(name.show, description(denot), List(denot.symbol))
yield {
val completionName = if (backtick) s"`${name.show}`" else name.show
Completion(completionName, description(denot), List(denot.symbol))
}

def description(denot: SingleDenotation)(using Context): String =
if denot.isType then denot.symbol.showFullName
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/repl/JLineTerminal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ final class JLineTerminal extends java.io.Closeable {
def currentToken: TokenData /* | Null */ = {
val source = SourceFile.virtual("<completions>", input)
val scanner = new Scanner(source)(using ctx.fresh.setReporter(Reporter.NoReporter))
var lastBacktickErrorStart: Option[Int] = None
while (scanner.token != EOF) {
val start = scanner.offset
val token = scanner.token
Expand All @@ -126,7 +127,13 @@ final class JLineTerminal extends java.io.Closeable {

val isCurrentToken = cursor >= start && cursor <= end
if (isCurrentToken)
return TokenData(token, start, end)
return TokenData(token, lastBacktickErrorStart.getOrElse(start), end)

// we need to enclose the last backtick, which unclosed produces ERROR token
if (token == ERROR && input(start) == '`')
lastBacktickErrorStart = Some(start)
else
lastBacktickErrorStart = None
}
null
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/test/dotty/tools/repl/TabcompleteTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,8 @@ class TabcompleteTests extends ReplTest {
tabComplete("import quoted.* ; def fooImpl(using Quotes): Expr[Int] = { import quotes.reflect.* ; TypeRepr.of[Int].s"))
}

@Test def wrongAnyMember: Unit = fromInitialState { implicit s =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean repeating the tests performed in CompletionTest.scala but rather directly reproducing the issue reported in #12514:
Starting with a fresh REPL try invoking completion on import scala.util.chaining.`s and then on import scala.util.chaining.`sc inside the same session

assertEquals(List("`scalaUtilChainingOps`", "`synchronized`"), tabComplete("import scala.util.chaining.`s"))
assertEquals(List("`scalaUtilChainingOps`"), tabComplete("import scala.util.chaining.`sc"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -887,4 +887,38 @@ class CompletionTest {
)
)
}

@Test def wrongAnyMember: Unit = {
code"""|import scala.util.chaining.`sc${m1}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to test some cases with more natural usages of backticks - if the name clashes with a keyword or contains spaces or would be normally disallowed for some other reason

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some more test cases, let me know if you have any other ideas!

|""".withSource
.completion(m1, Set(("`scalaUtilChainingOps`",Method,"[A](a: A): scala.util.ChainingOps[A]")))
}

@Test def importBackticked: Unit = {
code"""|object O{
| val `extends` = ""
|}
|import O.`extends`${m1}
|""".withSource
.completion(m1, Set(("extends",Field,"String")))
}

@Test def importBacktickedUnclosed: Unit = {
code"""|object O{
| val `extends` = ""
|}
|import O.`extends${m1}
|""".withSource
.completion(m1, Set(("`extends`",Field,"String")))
}


@Test def importBacktickedUnclosedSpace: Unit = {
code"""|object O{
| val `extends ` = ""
|}
|import O.`extends ${m1}
|""".withSource
.completion(m1, Set(("`extends `",Field,"String")))
}
}