Skip to content

Commit 28f284d

Browse files
committed
[lldb] Rewrite Swift REPL completion to use CompletionRequest
This removes all the workaround and problematic code that was needed for the old interface. With the CompletionRequest we no longer need to calculate the common prefix ourselves and we don't need to calculate the magic return value. On the other hand we can no longer provide 'appendix' completions (E.g. completing "Suff" with "ix", instead we now need to provide the completion "Suffix"), so in one case we now need to prefix the completion with the existing prefix from the CompletionRequest (as Swift only returns the string to append and not the whole token). Also rewrites the TestSwiftREPLCompletion.py to test all the problematic cases and some more.
1 parent b8ea404 commit 28f284d

File tree

3 files changed

+64
-42
lines changed

3 files changed

+64
-42
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,42 @@
1-
from __future__ import print_function
2-
import pexpect
3-
import os
1+
42
import lldb
53
from lldbsuite.test.decorators import *
64
from lldbsuite.test.lldbtest import *
7-
from lldbsuite.test import lldbutil
5+
from lldbsuite.test.lldbpexpect import PExpectTest
86

7+
class SwiftCompletionTest(PExpectTest):
98

10-
class TestSwiftREPLCompletion(TestBase):
119
mydir = TestBase.compute_mydir(__file__)
1210

11+
# PExpect uses many timeouts internally and doesn't play well
12+
# under ASAN on a loaded machine..
13+
@skipIfAsan
1314
@skipUnlessDarwin
14-
def test_repl_completion(self):
15-
prompt = "Welcome to"
16-
child = pexpect.spawn('%s --repl' % (lldbtest_config.lldbExec))
17-
# Assign to make sure the sessions are killed during teardown
18-
self.child = child
19-
# Send a <TAB> and make sure we don't crash.
20-
child.sendline("import Foundatio\t")
21-
child.sendline("print(NSString(\"patatino\"))")
22-
child.expect("patatino")
15+
def test_basic_completion(self):
16+
17+
self.launch(extra_args=["--repl"], executable=None, dimensions=(100,500))
18+
19+
# Wait on the first prompt
20+
self.child.expect_exact("1>")
21+
# Press tab a few times which should do nothing.
22+
# Note that we don't get any indentation whitespace as
23+
# pexpect is not recognized as a interactive terminal by pexpect it seems.
24+
self.child.send("\t\t\t")
25+
26+
# Try completing something that only has one result "Hasabl" -> "Hashable".
27+
self.child.send("Hashabl\t")
28+
self.child.expect_exact("Hashable")
29+
self.child.sendline("")
30+
31+
# Try completing something that has multiple completions.
32+
self.child.send("Hash\t")
33+
self.child.expect_exact("Available completions:")
34+
self.child.expect_exact("Hashable")
35+
self.child.expect_exact("Hasher")
36+
self.child.sendline("")
37+
38+
def setUpCommands(self):
39+
return [] # REPL doesn't take any setup commands.
40+
41+
def expect_prompt(self):
42+
pass # No constant prompt on the REPL.

lldb/source/Plugins/ExpressionParser/Swift/SwiftREPL.cpp

+28-26
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,8 @@ bool SwiftREPL::PrintOneVariable(Debugger &debugger, StreamFileSP &output_sp,
533533
return handled;
534534
}
535535

536-
int SwiftREPL::CompleteCode(const std::string &current_code,
537-
lldb_private::StringList &matches) {
536+
void SwiftREPL::CompleteCode(const std::string &current_code,
537+
CompletionRequest &request) {
538538
//----------------------------------------------------------------------g
539539
// If we use the target's SwiftASTContext for completion, it reaaallly
540540
// slows down subsequent expressions. The compiler team doesn't have time
@@ -546,7 +546,7 @@ int SwiftREPL::CompleteCode(const std::string &current_code,
546546
auto type_system_or_err = m_target.GetScratchTypeSystemForLanguage(eLanguageTypeSwift);
547547
if (!type_system_or_err) {
548548
llvm::consumeError(type_system_or_err.takeError());
549-
return 0;
549+
return;
550550
}
551551

552552
auto *target_swift_ast =
@@ -579,54 +579,56 @@ int SwiftREPL::CompleteCode(const std::string &current_code,
579579
swift::SourceFile &repl_source_file =
580580
repl_module->getMainSourceFile(swift::SourceFileKind::REPL);
581581

582+
// Swift likes to give us strings to append to the current token but
583+
// the CompletionRequest requires a replacement for the full current
584+
// token. Fix this by getting the current token here and we attach
585+
// the suffix we get from Swift.
586+
std::string prefix = request.GetCursorArgumentPrefix();
582587
llvm::StringRef current_code_ref(current_code);
583588
completions.populate(repl_source_file, current_code_ref);
589+
590+
// The root is the unique completion we need to use, so let's add it
591+
// to the completion list. As the completion is unique we can stop here.
584592
llvm::StringRef root = completions.getRoot();
585593
if (!root.empty()) {
586-
matches.AppendString(root.data(), root.size());
587-
return 1;
594+
request.AddCompletion(prefix + root.str(), "", CompletionMode::Partial);
595+
return;
588596
}
597+
589598
// Otherwise, advance through the completion state machine.
590599
const swift::CompletionState completion_state = completions.getState();
591600
switch (completion_state) {
592601
case swift::CompletionState::CompletedRoot: {
593-
// We completed the root. Next step is to display the completion list.
594-
matches.AppendString(""); // Empty string to indicate no completion,
595-
// just display other strings that come after it
602+
// Display the completion list.
596603
llvm::ArrayRef<llvm::StringRef> llvm_matches =
597604
completions.getCompletionList();
598605
for (const auto &llvm_match : llvm_matches) {
606+
// The completions here aren't really useful for actually completing
607+
// the token but are more descriptive hints for the user
608+
// (e.g. "isMultiple(of: Int) -> Bool"). They aren't useful for
609+
// actually completing anything so let's use the current token as
610+
// a placeholder that is always valid.
599611
if (!llvm_match.empty())
600-
matches.AppendString(llvm_match.data(), llvm_match.size());
612+
request.AddCompletion(prefix, llvm_match);
601613
}
602-
// Don't include the empty string we appended above or we will display
603-
// one
604-
// too many we need to return the magical value of one less than our
605-
// actual matches.
606-
// TODO: modify all IOHandlerDelegate::IOHandlerComplete() to use a
607-
// CompletionMatches
608-
// class that wraps up the "StringList matches;" along with other smarts
609-
// so we don't
610-
// have to return magic values and incorrect sizes.
611-
return matches.GetSize() - 1;
612614
} break;
613615

614616
case swift::CompletionState::DisplayedCompletionList: {
615617
// Complete the next completion stem in the cycle.
616-
llvm::StringRef stem = completions.getPreviousStem().InsertableString;
617-
matches.AppendString(stem.data(), stem.size());
618+
request.AddCompletion(prefix + completions.getPreviousStem().InsertableString.str());
618619
} break;
619620

620621
case swift::CompletionState::Empty:
621-
case swift::CompletionState::Unique:
622-
// We already provided a definitive completion--nothing else to do.
623-
break;
622+
case swift::CompletionState::Unique: {
623+
llvm::StringRef root = completions.getRoot();
624+
625+
if (!root.empty())
626+
request.AddCompletion(prefix + root.str());
627+
} break;
624628

625629
case swift::CompletionState::Invalid:
626630
llvm_unreachable("got an invalid completion set?!");
627631
}
628632
}
629633
}
630-
631-
return matches.GetSize();
632634
}

lldb/source/Plugins/ExpressionParser/Swift/SwiftREPL.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ class SwiftREPL : public REPL {
6868
lldb::ValueObjectSP &valobj_sp,
6969
ExpressionVariable *var = nullptr) override;
7070

71-
int CompleteCode(const std::string &current_code,
72-
StringList &matches) override;
71+
void CompleteCode(const std::string &current_code,
72+
CompletionRequest &request) override;
7373

7474
public:
7575
static bool classof(const REPL *repl) {

0 commit comments

Comments
 (0)