summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lldb/include/lldb/Expression/ExpressionParser.h35
-rw-r--r--lldb/include/lldb/Expression/UserExpression.h28
-rw-r--r--lldb/packages/Python/lldbsuite/test/expression_command/completion/.categories1
-rw-r--r--lldb/packages/Python/lldbsuite/test/expression_command/completion/Makefile5
-rw-r--r--lldb/packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py227
-rw-r--r--lldb/packages/Python/lldbsuite/test/expression_command/completion/main.cpp35
-rw-r--r--lldb/packages/Python/lldbsuite/test/expression_command/completion/other.cpp4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py36
-rw-r--r--lldb/packages/Python/lldbsuite/test/lldbtest.py40
-rw-r--r--lldb/source/Commands/CommandObjectExpression.cpp68
-rw-r--r--lldb/source/Commands/CommandObjectExpression.h2
-rw-r--r--lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp3
-rw-r--r--lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp277
-rw-r--r--lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h34
-rw-r--r--lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp123
-rw-r--r--lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h7
16 files changed, 884 insertions, 41 deletions
diff --git a/lldb/include/lldb/Expression/ExpressionParser.h b/lldb/include/lldb/Expression/ExpressionParser.h
index 66957926650..e77a44d1027 100644
--- a/lldb/include/lldb/Expression/ExpressionParser.h
+++ b/lldb/include/lldb/Expression/ExpressionParser.h
@@ -50,6 +50,41 @@ public:
virtual ~ExpressionParser(){};
//------------------------------------------------------------------
+ /// Attempts to find possible command line completions for the given
+ /// expression.
+ ///
+ /// @param[out] matches
+ /// The list of completions that should be appended with string
+ /// that would complete the current token at the cursor position.
+ /// Note that the string in the list replaces the current token
+ /// in the command line.
+ ///
+ /// @param[in] line
+ /// The line with the completion cursor inside the expression as a string.
+ /// The first line in the expression has the number 0.
+ ///
+ /// @param[in] pos
+ /// The character position in the line with the completion cursor.
+ /// If the value is 0, then the cursor is on top of the first character
+ /// in the line (i.e. the user has requested completion from the start of
+ /// the expression).
+ ///
+ /// @param[in] typed_pos
+ /// The cursor position in the line as typed by the user. If the user
+ /// expression has not been transformed in some form (e.g. wrapping it
+ /// in a function body for C languages), then this is equal to the
+ /// 'pos' parameter. The semantics of this value are otherwise equal to
+ /// 'pos' (e.g. a value of 0 means the cursor is at start of the
+ /// expression).
+ ///
+ /// @return
+ /// True if we added any completion results to the output;
+ /// false otherwise.
+ //------------------------------------------------------------------
+ virtual bool Complete(StringList &matches, unsigned line, unsigned pos,
+ unsigned typed_pos) = 0;
+
+ //------------------------------------------------------------------
/// Parse a single expression and convert it to IR using Clang. Don't wrap
/// the expression in anything at all.
///
diff --git a/lldb/include/lldb/Expression/UserExpression.h b/lldb/include/lldb/Expression/UserExpression.h
index 96ca80c882e..56453de9429 100644
--- a/lldb/include/lldb/Expression/UserExpression.h
+++ b/lldb/include/lldb/Expression/UserExpression.h
@@ -98,6 +98,34 @@ public:
lldb_private::ExecutionPolicy execution_policy,
bool keep_result_in_memory, bool generate_debug_info) = 0;
+ //------------------------------------------------------------------
+ /// Attempts to find possible command line completions for the given
+ /// (possible incomplete) user expression.
+ ///
+ /// @param[in] exe_ctx
+ /// The execution context to use when looking up entities that
+ /// are needed for parsing and completing (locations of functions, types
+ /// of variables, persistent variables, etc.)
+ ///
+ /// @param[out] matches
+ /// The list of completions that should be appended with string
+ /// that would complete the current token at the cursor position.
+ /// Note that the string in the list replaces the current token
+ /// in the command line.
+ ///
+ /// @param[in] complete_pos
+ /// The position of the cursor inside the user expression string.
+ /// The completion process starts on the token that the cursor is in.
+ ///
+ /// @return
+ /// True if we added any completion results to the output;
+ /// false otherwise.
+ //------------------------------------------------------------------
+ virtual bool Complete(ExecutionContext &exe_ctx, StringList &matches,
+ unsigned complete_pos) {
+ return false;
+ }
+
virtual bool CanInterpret() = 0;
bool MatchesContext(ExecutionContext &exe_ctx);
diff --git a/lldb/packages/Python/lldbsuite/test/expression_command/completion/.categories b/lldb/packages/Python/lldbsuite/test/expression_command/completion/.categories
new file mode 100644
index 00000000000..3a3f4df6416
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/expression_command/completion/.categories
@@ -0,0 +1 @@
+cmdline
diff --git a/lldb/packages/Python/lldbsuite/test/expression_command/completion/Makefile b/lldb/packages/Python/lldbsuite/test/expression_command/completion/Makefile
new file mode 100644
index 00000000000..6fc26a9193f
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/expression_command/completion/Makefile
@@ -0,0 +1,5 @@
+LEVEL = ../../make
+
+CXX_SOURCES := main.cpp other.cpp
+
+include $(LEVEL)/Makefile.rules
diff --git a/lldb/packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py b/lldb/packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py
new file mode 100644
index 00000000000..2ebc239fa08
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py
@@ -0,0 +1,227 @@
+"""
+Test the lldb command line completion mechanism for the 'expr' command.
+"""
+
+from __future__ import print_function
+
+import random
+import os
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbplatform
+from lldbsuite.test import lldbutil
+
+class CommandLineExprCompletionTestCase(TestBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ NO_DEBUG_INFO_TESTCASE = True
+
+ @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24489")
+ def test_expr_completion(self):
+ self.build()
+ self.main_source = "main.cpp"
+ self.main_source_spec = lldb.SBFileSpec(self.main_source)
+ self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+
+ # Try the completion before we have a context to complete on.
+ self.assume_no_completions('expr some_expr')
+ self.assume_no_completions('expr ')
+ self.assume_no_completions('expr f')
+
+
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
+ '// Break here', self.main_source_spec)
+
+ # Completing member functions
+ self.complete_exactly('expr some_expr.FooNoArgs',
+ 'expr some_expr.FooNoArgsBar()')
+ self.complete_exactly('expr some_expr.FooWithArgs',
+ 'expr some_expr.FooWithArgsBar(')
+ self.complete_exactly('expr some_expr.FooWithMultipleArgs',
+ 'expr some_expr.FooWithMultipleArgsBar(')
+ self.complete_exactly('expr some_expr.FooUnderscore',
+ 'expr some_expr.FooUnderscoreBar_()')
+ self.complete_exactly('expr some_expr.FooNumbers',
+ 'expr some_expr.FooNumbersBar1()')
+ self.complete_exactly('expr some_expr.StaticMemberMethod',
+ 'expr some_expr.StaticMemberMethodBar()')
+
+ # Completing static functions
+ self.complete_exactly('expr Expr::StaticMemberMethod',
+ 'expr Expr::StaticMemberMethodBar()')
+
+ # Completing member variables
+ self.complete_exactly('expr some_expr.MemberVariab',
+ 'expr some_expr.MemberVariableBar')
+
+ # Multiple completions
+ self.completions_contain('expr some_expr.',
+ ['some_expr.FooNumbersBar1()',
+ 'some_expr.FooUnderscoreBar_()',
+ 'some_expr.FooWithArgsBar(',
+ 'some_expr.MemberVariableBar'])
+
+ self.completions_contain('expr some_expr.Foo',
+ ['some_expr.FooNumbersBar1()',
+ 'some_expr.FooUnderscoreBar_()',
+ 'some_expr.FooWithArgsBar('])
+
+ self.completions_contain('expr ',
+ ['static_cast',
+ 'reinterpret_cast',
+ 'dynamic_cast'])
+
+ self.completions_contain('expr 1 + ',
+ ['static_cast',
+ 'reinterpret_cast',
+ 'dynamic_cast'])
+
+ # Completion expr without spaces
+ # This is a bit awkward looking for the user, but that's how
+ # the completion API works at the moment.
+ self.completions_contain('expr 1+',
+ ['1+some_expr', "1+static_cast"])
+
+ # Test with spaces
+ self.complete_exactly('expr some_expr .FooNoArgs',
+ 'expr some_expr .FooNoArgsBar()')
+ self.complete_exactly('expr some_expr .FooNoArgs',
+ 'expr some_expr .FooNoArgsBar()')
+ self.complete_exactly('expr some_expr .FooNoArgs',
+ 'expr some_expr .FooNoArgsBar()')
+ self.complete_exactly('expr some_expr. FooNoArgs',
+ 'expr some_expr. FooNoArgsBar()')
+ self.complete_exactly('expr some_expr . FooNoArgs',
+ 'expr some_expr . FooNoArgsBar()')
+ self.complete_exactly('expr Expr :: StaticMemberMethod',
+ 'expr Expr :: StaticMemberMethodBar()')
+ self.complete_exactly('expr Expr ::StaticMemberMethod',
+ 'expr Expr ::StaticMemberMethodBar()')
+ self.complete_exactly('expr Expr:: StaticMemberMethod',
+ 'expr Expr:: StaticMemberMethodBar()')
+
+ # Test that string literals don't break our parsing logic.
+ self.complete_exactly('expr const char *cstr = "some_e"; char c = *cst',
+ 'expr const char *cstr = "some_e"; char c = *cstr')
+ self.complete_exactly('expr const char *cstr = "some_e" ; char c = *cst',
+ 'expr const char *cstr = "some_e" ; char c = *cstr')
+ # Requesting completions inside an incomplete string doesn't provide any
+ # completions.
+ self.complete_exactly('expr const char *cstr = "some_e',
+ 'expr const char *cstr = "some_e')
+
+ # Completing inside double dash should do nothing
+ self.assume_no_completions('expr -i0 -- some_expr.', 10)
+ self.assume_no_completions('expr -i0 -- some_expr.', 11)
+
+ # Test with expr arguments
+ self.complete_exactly('expr -i0 -- some_expr .FooNoArgs',
+ 'expr -i0 -- some_expr .FooNoArgsBar()')
+ self.complete_exactly('expr -i0 -- some_expr .FooNoArgs',
+ 'expr -i0 -- some_expr .FooNoArgsBar()')
+
+ # Addrof and deref
+ self.complete_exactly('expr (*(&some_expr)).FooNoArgs',
+ 'expr (*(&some_expr)).FooNoArgsBar()')
+ self.complete_exactly('expr (*(&some_expr)) .FooNoArgs',
+ 'expr (*(&some_expr)) .FooNoArgsBar()')
+ self.complete_exactly('expr (* (&some_expr)) .FooNoArgs',
+ 'expr (* (&some_expr)) .FooNoArgsBar()')
+ self.complete_exactly('expr (* (& some_expr)) .FooNoArgs',
+ 'expr (* (& some_expr)) .FooNoArgsBar()')
+
+ # Addrof and deref (part 2)
+ self.complete_exactly('expr (&some_expr)->FooNoArgs',
+ 'expr (&some_expr)->FooNoArgsBar()')
+ self.complete_exactly('expr (&some_expr) ->FooNoArgs',
+ 'expr (&some_expr) ->FooNoArgsBar()')
+ self.complete_exactly('expr (&some_expr) -> FooNoArgs',
+ 'expr (&some_expr) -> FooNoArgsBar()')
+ self.complete_exactly('expr (&some_expr)-> FooNoArgs',
+ 'expr (&some_expr)-> FooNoArgsBar()')
+
+ # Builtin arg
+ self.complete_exactly('expr static_ca',
+ 'expr static_cast')
+
+ # From other files
+ self.complete_exactly('expr fwd_decl_ptr->Hidden',
+ 'expr fwd_decl_ptr->HiddenMember')
+
+
+ # Types
+ self.complete_exactly('expr LongClassNa',
+ 'expr LongClassName')
+ self.complete_exactly('expr LongNamespaceName::NestedCla',
+ 'expr LongNamespaceName::NestedClass')
+
+ # Namespaces
+ self.complete_exactly('expr LongNamespaceNa',
+ 'expr LongNamespaceName::')
+
+ # Multiple arguments
+ self.complete_exactly('expr &some_expr + &some_e',
+ 'expr &some_expr + &some_expr')
+ self.complete_exactly('expr SomeLongVarNameWithCapitals + SomeLongVarName',
+ 'expr SomeLongVarNameWithCapitals + SomeLongVarNameWithCapitals')
+ self.complete_exactly('expr SomeIntVar + SomeIntV',
+ 'expr SomeIntVar + SomeIntVar')
+
+ # Multiple statements
+ self.complete_exactly('expr long LocalVariable = 0; LocalVaria',
+ 'expr long LocalVariable = 0; LocalVariable')
+
+ # Custom Decls
+ self.complete_exactly('expr auto l = [](int LeftHandSide, int bx){ return LeftHandS',
+ 'expr auto l = [](int LeftHandSide, int bx){ return LeftHandSide')
+ self.complete_exactly('expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.Mem',
+ 'expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.MemberName')
+
+ # Completing function call arguments
+ self.complete_exactly('expr some_expr.FooWithArgsBar(some_exp',
+ 'expr some_expr.FooWithArgsBar(some_expr')
+ self.complete_exactly('expr some_expr.FooWithArgsBar(SomeIntV',
+ 'expr some_expr.FooWithArgsBar(SomeIntVar')
+ self.complete_exactly('expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVa',
+ 'expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVar')
+
+ # Function return values
+ self.complete_exactly('expr some_expr.Self().FooNoArgs',
+ 'expr some_expr.Self().FooNoArgsBar()')
+ self.complete_exactly('expr some_expr.Self() .FooNoArgs',
+ 'expr some_expr.Self() .FooNoArgsBar()')
+ self.complete_exactly('expr some_expr.Self(). FooNoArgs',
+ 'expr some_expr.Self(). FooNoArgsBar()')
+
+ def assume_no_completions(self, str_input, cursor_pos = None):
+ interp = self.dbg.GetCommandInterpreter()
+ match_strings = lldb.SBStringList()
+ if cursor_pos is None:
+ cursor_pos = len(str_input)
+ num_matches = interp.HandleCompletion(str_input, cursor_pos, 0, -1, match_strings)
+
+ available_completions = []
+ for m in match_strings:
+ available_completions.append(m)
+
+ self.assertEquals(num_matches, 0, "Got matches, but didn't expect any: " + str(available_completions))
+
+ def completions_contain(self, str_input, items):
+ interp = self.dbg.GetCommandInterpreter()
+ match_strings = lldb.SBStringList()
+ num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings)
+ common_match = match_strings.GetStringAtIndex(0)
+
+ for item in items:
+ found = False
+ for m in match_strings:
+ if m == item:
+ found = True
+ if not found:
+ # Transform match_strings to a python list with strings
+ available_completions = []
+ for m in match_strings:
+ available_completions.append(m)
+ self.assertTrue(found, "Couldn't find completion " + item + " in completions " + str(available_completions))
diff --git a/lldb/packages/Python/lldbsuite/test/expression_command/completion/main.cpp b/lldb/packages/Python/lldbsuite/test/expression_command/completion/main.cpp
new file mode 100644
index 00000000000..908bebbebff
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/expression_command/completion/main.cpp
@@ -0,0 +1,35 @@
+namespace LongNamespaceName { class NestedClass { long m; }; }
+
+// Defined in other.cpp, we only have a forward declaration here.
+struct ForwardDecl;
+extern ForwardDecl fwd_decl;
+
+class LongClassName { long i ; };
+
+class Expr {
+public:
+ int FooNoArgsBar() { return 1; }
+ int FooWithArgsBar(int i) { return i; }
+ int FooWithMultipleArgsBar(int i, int j) { return i + j; }
+ int FooUnderscoreBar_() { return 4; }
+ int FooNumbersBar1() { return 8; }
+ int MemberVariableBar = 0;
+ Expr &Self() { return *this; }
+ static int StaticMemberMethodBar() { return 82; }
+};
+
+int main()
+{
+ LongClassName a;
+ LongNamespaceName::NestedClass NestedFoo;
+ long SomeLongVarNameWithCapitals = 44;
+ int SomeIntVar = 33;
+ Expr some_expr;
+ some_expr.FooNoArgsBar();
+ some_expr.FooWithArgsBar(1);
+ some_expr.FooUnderscoreBar_();
+ some_expr.FooNumbersBar1();
+ Expr::StaticMemberMethodBar();
+ ForwardDecl *fwd_decl_ptr = &fwd_decl;
+ return 0; // Break here
+}
diff --git a/lldb/packages/Python/lldbsuite/test/expression_command/completion/other.cpp b/lldb/packages/Python/lldbsuite/test/expression_command/completion/other.cpp
new file mode 100644
index 00000000000..1f8a488639b
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/expression_command/completion/other.cpp
@@ -0,0 +1,4 @@
+struct ForwardDecl {
+ long HiddenMemberName;
+};
+ForwardDecl fwd_decl;
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py b/lldb/packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
index 5971e5a673d..7302660d6cf 100644
--- a/lldb/packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
@@ -281,39 +281,3 @@ class CommandLineCompletionTestCase(TestBase):
self.complete_from_to('breakpoint set -n Fo',
'breakpoint set -n Foo::Bar(int,\\ int)',
turn_off_re_match=True)
-
- def complete_from_to(self, str_input, patterns, turn_off_re_match=False):
- """Test that the completion mechanism completes str_input to patterns,
- where patterns could be a pattern-string or a list of pattern-strings"""
- # Patterns should not be None in order to proceed.
- self.assertFalse(patterns is None)
- # And should be either a string or list of strings. Check for list type
- # below, if not, make a list out of the singleton string. If patterns
- # is not a string or not a list of strings, there'll be runtime errors
- # later on.
- if not isinstance(patterns, list):
- patterns = [patterns]
-
- interp = self.dbg.GetCommandInterpreter()
- match_strings = lldb.SBStringList()
- num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings)
- common_match = match_strings.GetStringAtIndex(0)
- if num_matches == 0:
- compare_string = str_input
- else:
- if common_match != None and len(common_match) > 0:
- compare_string = str_input + common_match
- else:
- compare_string = ""
- for idx in range(1, num_matches+1):
- compare_string += match_strings.GetStringAtIndex(idx) + "\n"
-
- for p in patterns:
- if turn_off_re_match:
- self.expect(
- compare_string, msg=COMPLETION_MSG(
- str_input, p, match_strings), exe=False, substrs=[p])
- else:
- self.expect(
- compare_string, msg=COMPLETION_MSG(
- str_input, p, match_strings), exe=False, patterns=[p])
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index e7316af9e89..cee4d34101a 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -2145,6 +2145,46 @@ class TestBase(Base):
return match_object
+
+ def complete_exactly(self, str_input, patterns):
+ self.complete_from_to(str_input, patterns, True)
+
+ def complete_from_to(self, str_input, patterns, turn_off_re_match=False):
+ """Test that the completion mechanism completes str_input to patterns,
+ where patterns could be a pattern-string or a list of pattern-strings"""
+ # Patterns should not be None in order to proceed.
+ self.assertFalse(patterns is None)
+ # And should be either a string or list of strings. Check for list type
+ # below, if not, make a list out of the singleton string. If patterns
+ # is not a string or not a list of strings, there'll be runtime errors
+ # later on.
+ if not isinstance(patterns, list):
+ patterns = [patterns]
+
+ interp = self.dbg.GetCommandInterpreter()
+ match_strings = lldb.SBStringList()
+ num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings)
+ common_match = match_strings.GetStringAtIndex(0)
+ if num_matches == 0:
+ compare_string = str_input
+ else:
+ if common_match != None and len(common_match) > 0:
+ compare_string = str_input + common_match
+ else:
+ compare_string = ""
+ for idx in range(1, num_matches+1):
+ compare_string += match_strings.GetStringAtIndex(idx) + "\n"
+
+ for p in patterns:
+ if turn_off_re_match:
+ self.expect(
+ compare_string, msg=COMPLETION_MSG(
+ str_input, p, match_strings), exe=False, substrs=[p])
+ else:
+ self.expect(
+ compare_string, msg=COMPLETION_MSG(
+ str_input, p, match_strings), exe=False, patterns=[p])
+
def expect(
self,
str,
diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp
index 08959ff8473..fdeaac24d0a 100644
--- a/lldb/source/Commands/CommandObjectExpression.cpp
+++ b/lldb/source/Commands/CommandObjectExpression.cpp
@@ -307,6 +307,74 @@ CommandObjectExpression::~CommandObjectExpression() = default;
Options *CommandObjectExpression::GetOptions() { return &m_option_group; }
+int CommandObjectExpression::HandleCompletion(CompletionRequest &request) {
+ EvaluateExpressionOptions options;
+ options.SetCoerceToId(m_varobj_options.use_objc);
+ options.SetLanguage(m_command_options.language);
+ options.SetExecutionPolicy(lldb_private::eExecutionPolicyNever);
+ options.SetAutoApplyFixIts(false);
+ options.SetGenerateDebugInfo(false);
+
+ // We need a valid execution context with a frame pointer for this
+ // completion, so if we don't have one we should try to make a valid
+ // execution context.
+ if (m_interpreter.GetExecutionContext().GetFramePtr() == nullptr)
+ m_interpreter.UpdateExecutionContext(nullptr);
+
+ // This didn't work, so let's get out before we start doing things that
+ // expect a valid frame pointer.
+ if (m_interpreter.GetExecutionContext().GetFramePtr() == nullptr)
+ return 0;
+
+ ExecutionContext exe_ctx(m_interpreter.GetExecutionContext());
+
+ Target *target = exe_ctx.GetTargetPtr();
+
+ if (!target)
+ target = GetDummyTarget();
+
+ if (!target)
+ return 0;
+
+ unsigned cursor_pos = request.GetRawCursorPos();
+ llvm::StringRef code = request.GetRawLine();
+
+ const std::size_t original_code_size = code.size();
+
+ // Remove the first token which is 'expr' or some alias/abbreviation of that.
+ code = llvm::getToken(code).second.ltrim();
+ OptionsWithRaw args(code);
+ code = args.GetRawPart();
+
+ // The position where the expression starts in the command line.
+ assert(original_code_size >= code.size());
+ std::size_t raw_start = original_code_size - code.size();
+
+ // Check if the cursor is actually in the expression string, and if not, we
+ // exit.
+ // FIXME: We should complete the options here.
+ if (cursor_pos < raw_start)
+ return 0;
+
+ // Make the cursor_pos again relative to the start of the code string.
+ assert(cursor_pos >= raw_start);
+ cursor_pos -= raw_start;
+
+ auto language = exe_ctx.GetFrameRef().GetLanguage();
+
+ Status error;
+ lldb::UserExpressionSP expr(target->GetUserExpressionForLanguage(
+ code, llvm::StringRef(), language, UserExpression::eResultTypeAny,
+ options, error));
+ if (error.Fail())
+ return 0;
+
+ StringList matches;
+ expr->Complete(exe_ctx, matches, cursor_pos);
+ request.AddCompletions(matches);
+ return request.GetNumberOfMatches();
+}
+
static lldb_private::Status
CanBeUsedForElementCountPrinting(ValueObject &valobj) {
CompilerType type(valobj.GetCompilerType());
diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h
index 710f8714097..1e2a0d2ad00 100644
--- a/lldb/source/Commands/CommandObjectExpression.h
+++ b/lldb/source/Commands/CommandObjectExpression.h
@@ -62,6 +62,8 @@ public:
Options *GetOptions() override;
+ int HandleCompletion(CompletionRequest &request) override;
+
protected:
//------------------------------------------------------------------
// IOHandler::Delegate functions
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
index fa49a51f32a..c2bc18a04e9 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
@@ -87,7 +87,8 @@ void ASTResultSynthesizer::TransformTopLevelDecl(Decl *D) {
SynthesizeObjCMethodResult(method_decl);
}
} else if (FunctionDecl *function_decl = dyn_cast<FunctionDecl>(D)) {
- if (m_ast_context &&
+ // When completing user input the body of the function may be a nullptr.
+ if (m_ast_context && function_decl->hasBody() &&
!function_decl->getNameInfo().getAsString().compare("$__lldb_expr")) {
RecordPersistentTypes(function_decl);
SynthesizeFunctionResult(function_decl);
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 405e29b2430..cfd5d9be111 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -34,6 +34,8 @@
#include "clang/Parse/ParseAST.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Rewrite/Frontend/FrontendActions.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
+#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaConsumer.h"
#include "llvm/ADT/StringRef.h"
@@ -546,7 +548,248 @@ ClangExpressionParser::ClangExpressionParser(ExecutionContextScope *exe_scope,
ClangExpressionParser::~ClangExpressionParser() {}
+namespace {
+
+//----------------------------------------------------------------------
+/// @class CodeComplete
+///
+/// A code completion consumer for the clang Sema that is responsible for
+/// creating the completion suggestions when a user requests completion
+/// of an incomplete `expr` invocation.
+//----------------------------------------------------------------------
+class CodeComplete : public CodeCompleteConsumer {
+ CodeCompletionTUInfo CCTUInfo;
+
+ std::string expr;
+ unsigned position = 0;
+ StringList &matches;
+
+ /// Returns true if the given character can be used in an identifier.
+ /// This also returns true for numbers because for completion we usually
+ /// just iterate backwards over iterators.
+ ///
+ /// Note: lldb uses '$' in its internal identifiers, so we also allow this.
+ static bool IsIdChar(char c) {
+ return c == '_' || std::isalnum(c) || c == '$';
+ }
+
+ /// Returns true if the given character is used to separate arguments
+ /// in the command line of lldb.
+ static bool IsTokenSeparator(char c) { return c == ' ' || c == '\t'; }
+
+ /// Drops all tokens in front of the expression that are unrelated for
+ /// the completion of the cmd line. 'unrelated' means here that the token
+ /// is not interested for the lldb completion API result.
+ StringRef dropUnrelatedFrontTokens(StringRef cmd) {
+ if (cmd.empty())
+ return cmd;
+
+ // If we are at the start of a word, then all tokens are unrelated to
+ // the current completion logic.
+ if (IsTokenSeparator(cmd.back()))
+ return StringRef();
+
+ // Remove all previous tokens from the string as they are unrelated
+ // to completing the current token.
+ StringRef to_remove = cmd;
+ while (!to_remove.empty() && !IsTokenSeparator(to_remove.back())) {
+ to_remove = to_remove.drop_back();
+ }
+ cmd = cmd.drop_front(to_remove.size());
+
+ return cmd;
+ }
+
+ /// Removes the last identifier token from the given cmd line.
+ StringRef removeLastToken(StringRef cmd) {
+ while (!cmd.empty() && IsIdChar(cmd.back())) {
+ cmd = cmd.drop_back();
+ }
+ return cmd;
+ }
+
+ /// Attemps to merge the given completion from the given position into the
+ /// existing command. Returns the completion string that can be returned to
+ /// the lldb completion API.
+ std::string mergeCompletion(StringRef existing, unsigned pos,
+ StringRef completion) {
+ StringRef existing_command = existing.substr(0, pos);
+ // We rewrite the last token with the completion, so let's drop that
+ // token from the command.
+ existing_command = removeLastToken(existing_command);
+ // We also should remove all previous tokens from the command as they
+ // would otherwise be added to the completion that already has the
+ // completion.
+ existing_command = dropUnrelatedFrontTokens(existing_command);
+ return existing_command.str() + completion.str();
+ }
+
+public:
+ /// Constructs a CodeComplete consumer that can be attached to a Sema.
+ /// @param[out] matches
+ /// The list of matches that the lldb completion API expects as a result.
+ /// This may already contain matches, so it's only allowed to append
+ /// to this variable.
+ /// @param[out] expr
+ /// The whole expression string that we are currently parsing. This
+ /// string needs to be equal to the input the user typed, and NOT the
+ /// final code that Clang is parsing.
+ /// @param[out] position
+ /// The character position of the user cursor in the `expr` parameter.
+ ///
+ CodeComplete(StringList &matches, std::string expr, unsigned position)
+ : CodeCompleteConsumer(CodeCompleteOptions(), false),
+ CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()), expr(expr),
+ position(position), matches(matches) {}
+
+ /// Deregisters and destroys this code-completion consumer.
+ virtual ~CodeComplete() {}
+
+ /// \name Code-completion filtering
+ /// Check if the result should be filtered out.
+ bool isResultFilteredOut(StringRef Filter,
+ CodeCompletionResult Result) override {
+ // This code is mostly copied from CodeCompleteConsumer.
+ switch (Result.Kind) {
+ case CodeCompletionResult::RK_Declaration:
+ return !(
+ Result.Declaration->getIdentifier() &&
+ Result.Declaration->getIdentifier()->getName().startswith(Filter));
+ case CodeCompletionResult::RK_Keyword:
+ return !StringRef(Result.Keyword).startswith(Filter);
+ case CodeCompletionResult::RK_Macro:
+ return !Result.Macro->getName().startswith(Filter);
+ case CodeCompletionResult::RK_Pattern:
+ return !StringRef(Result.Pattern->getAsString()).startswith(Filter);
+ }
+ // If we trigger this assert or the above switch yields a warning, then
+ // CodeCompletionResult has been enhanced with more kinds of completion
+ // results. Expand the switch above in this case.
+ assert(false && "Unknown completion result type?");
+ // If we reach this, then we should just ignore whatever kind of unknown
+ // result we got back. We probably can't turn it into any kind of useful
+ // completion suggestion with the existing code.
+ return true;
+ }
+
+ /// \name Code-completion callbacks
+ /// Process the finalized code-completion results.
+ void ProcessCodeCompleteResults(Sema &SemaRef, CodeCompletionContext Context,
+ CodeCompletionResult *Results,
+ unsigned NumResults) override {
+
+ // The Sema put the incomplete token we try to complete in here during
+ // lexing, so we need to retrieve it here to know what we are completing.
+ StringRef Filter = SemaRef.getPreprocessor().getCodeCompletionFilter();
+
+ // Iterate over all the results. Filter out results we don't want and
+ // process the rest.
+ for (unsigned I = 0; I != NumResults; ++I) {
+ // Filter the results with the information from the Sema.
+ if (!Filter.empty() && isResultFilteredOut(Filter, Results[I]))
+ continue;
+
+ CodeCompletionResult &R = Results[I];
+ std::string ToInsert;
+ // Handle the different completion kinds that come from the Sema.
+ switch (R.Kind) {
+ case CodeCompletionResult::RK_Declaration: {
+ const NamedDecl *D = R.Declaration;
+ ToInsert = R.Declaration->getNameAsString();
+ // If we have a function decl that has no arguments we want to
+ // complete the empty parantheses for the user. If the function has
+ // arguments, we at least complete the opening bracket.
+ if (const FunctionDecl *F = dyn_cast<FunctionDecl>(D)) {
+ if (F->getNumParams() == 0)
+ ToInsert += "()";
+ else
+ ToInsert += "(";
+ }
+ // If we try to complete a namespace, then we directly can append
+ // the '::'.
+ if (const NamespaceDecl *N = dyn_cast<NamespaceDecl>(D)) {
+ if (!N->isAnonymousNamespace())
+ ToInsert += "::";
+ }
+ break;
+ }
+ case CodeCompletionResult::RK_Keyword:
+ ToInsert = R.Keyword;
+ break;
+ case CodeCompletionResult::RK_Macro:
+ // It's not clear if we want to complete any macros in the
+ ToInsert = R.Macro->getName().str();
+ break;
+ case CodeCompletionResult::RK_Pattern:
+ ToInsert = R.Pattern->getTypedText();
+ break;
+ }
+ // At this point all information is in the ToInsert string.
+
+ // We also filter some internal lldb identifiers here. The user
+ // shouldn't see these.
+ if (StringRef(ToInsert).startswith("$__lldb_"))
+ continue;
+ if (!ToInsert.empty()) {
+ // Merge the suggested Token into the existing command line to comply
+ // with the kind of result the lldb API expects.
+ std::string CompletionSuggestion =
+ mergeCompletion(expr, position, ToInsert);
+ matches.AppendString(CompletionSuggestion);
+ }
+ }
+ }
+
+ /// \param S the semantic-analyzer object for which code-completion is being
+ /// done.
+ ///
+ /// \param CurrentArg the index of the current argument.
+ ///
+ /// \param Candidates an array of overload candidates.
+ ///
+ /// \param NumCandidates the number of overload candidates
+ void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
+ OverloadCandidate *Candidates,
+ unsigned NumCandidates,
+ SourceLocation OpenParLoc) override {
+ // At the moment we don't filter out any overloaded candidates.
+ }
+
+ CodeCompletionAllocator &getAllocator() override {
+ return CCTUInfo.getAllocator();
+ }
+
+ CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
+};
+} // namespace
+
+bool ClangExpressionParser::Complete(StringList &matches, unsigned line,
+ unsigned pos, unsigned typed_pos) {
+ DiagnosticManager mgr;
+ // We need the raw user expression here because that's what the CodeComplete
+ // class uses to provide completion suggestions.
+ // However, the `Text` method only gives us the transformed expression here.
+ // To actually get the raw user input here, we have to cast our expression to
+ // the LLVMUserExpression which exposes the right API. This should never fail
+ // as we always have a ClangUserExpression whenever we call this.
+ LLVMUserExpression &llvm_expr = *static_cast<LLVMUserExpression *>(&m_expr);
+ CodeComplete CC(matches, llvm_expr.GetUserText(), typed_pos);
+ // We don't need a code generator for parsing.
+ m_code_generator.reset();
+ // Start parsing the expression with our custom code completion consumer.
+ ParseInternal(mgr, &CC, line, pos);
+ return true;
+}
+
unsigned ClangExpressionParser::Parse(DiagnosticManager &diagnostic_manager) {
+ return ParseInternal(diagnostic_manager);
+}
+
+unsigned
+ClangExpressionParser::ParseInternal(DiagnosticManager &diagnostic_manager,
+ CodeCompleteConsumer *completion_consumer,
+ unsigned completion_line,
+ unsigned completion_column) {
ClangDiagnosticManagerAdapter *adapter =
static_cast<ClangDiagnosticManagerAdapter *>(
m_compiler->getDiagnostics().getClient());
@@ -559,8 +802,18 @@ unsigned ClangExpressionParser::Parse(DiagnosticManager &diagnostic_manager) {
clang::SourceManager &source_mgr = m_compiler->getSourceManager();
bool created_main_file = false;
- if (m_compiler->getCodeGenOpts().getDebugInfo() ==
- codegenoptions::FullDebugInfo) {
+
+ // Clang wants to do completion on a real file known by Clang's file manager,
+ // so we have to create one to make this work.
+ // TODO: We probably could also simulate to Clang's file manager that there
+ // is a real file that contains our code.
+ bool should_create_file = completion_consumer != nullptr;
+
+ // We also want a real file on disk if we generate full debug info.
+ should_create_file |= m_compiler->getCodeGenOpts().getDebugInfo() ==
+ codegenoptions::FullDebugInfo;
+
+ if (should_create_file) {
int temp_fd = -1;
llvm::SmallString<PATH_MAX> result_path;
if (FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir()) {
@@ -605,14 +858,30 @@ unsigned ClangExpressionParser::Parse(DiagnosticManager &diagnostic_manager) {
if (ClangExpressionDeclMap *decl_map = type_system_helper->DeclMap())
decl_map->InstallCodeGenerator(m_code_generator.get());
+ // If we want to parse for code completion, we need to attach our code
+ // completion consumer to the Sema and specify a completion position.
+ // While parsing the Sema will call this consumer with the provided
+ // completion suggestions.
+ if (completion_consumer) {
+ auto main_file = source_mgr.getFileEntryForID(source_mgr.getMainFileID());
+ auto &PP = m_compiler->getPreprocessor();
+ // Lines and columns start at 1 in Clang, but code completion positions are
+ // indexed from 0, so we need to add 1 to the line and column here.
+ ++completion_line;
+ ++completion_column;
+ PP.SetCodeCompletionPoint(main_file, completion_line, completion_column);
+ }
+
if (ast_transformer) {
ast_transformer->Initialize(m_compiler->getASTContext());
ParseAST(m_compiler->getPreprocessor(), ast_transformer,
- m_compiler->getASTContext());
+ m_compiler->getASTContext(), false, TU_Complete,
+ completion_consumer);
} else {
m_code_generator->Initialize(m_compiler->getASTContext());
ParseAST(m_compiler->getPreprocessor(), m_code_generator.get(),
- m_compiler->getASTContext());
+ m_compiler->getASTContext(), false, TU_Complete,
+ completion_consumer);
}
diag_buf->EndSourceFile();
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
index 6b17c6d5fb3..7fdc9c121ef 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
@@ -20,6 +20,10 @@
#include <string>
#include <vector>
+namespace clang {
+class CodeCompleteConsumer;
+}
+
namespace lldb_private {
class IRExecutionUnit;
@@ -58,6 +62,9 @@ public:
//------------------------------------------------------------------
~ClangExpressionParser() override;
+ bool Complete(StringList &matches, unsigned line, unsigned pos,
+ unsigned typed_pos) override;
+
//------------------------------------------------------------------
/// Parse a single expression and convert it to IR using Clang. Don't wrap
/// the expression in anything at all.
@@ -143,6 +150,33 @@ public:
std::string GetClangTargetABI(const ArchSpec &target_arch);
private:
+ //------------------------------------------------------------------
+ /// Parses the expression.
+ ///
+ /// @param[in] diagnostic_manager
+ /// The diagnostic manager that should receive the diagnostics
+ /// from the parsing process.
+ ///
+ /// @param[in] completion
+ /// The completion consumer that should be used during parsing
+ /// (or a nullptr if no consumer should be attached).
+ ///
+ /// @param[in] completion_line
+ /// The line in which the completion marker should be placed.
+ /// The first line is represented by the value 0.
+ ///
+ /// @param[in] completion_column
+ /// The column in which the completion marker should be placed.
+ /// The first column is represented by the value 0.
+ ///
+ /// @return
+ /// The number of parsing errors.
+ //-------------------------------------------------------------------
+ unsigned ParseInternal(DiagnosticManager &diagnostic_manager,
+ clang::CodeCompleteConsumer *completion = nullptr,
+ unsigned completion_line = 0,
+ unsigned completion_column = 0);
+
std::unique_ptr<llvm::LLVMContext>
m_llvm_context; ///< The LLVM context to generate IR into
std::unique_ptr<clang::FileManager>
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 2e61f704127..c43e9d7511f 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -402,6 +402,16 @@ llvm::Optional<lldb::LanguageType> ClangUserExpression::GetLanguageForExpr(
"couldn't construct expression body");
return llvm::Optional<lldb::LanguageType>();
}
+
+ // Find and store the start position of the original code inside the
+ // transformed code. We need this later for the code completion.
+ std::size_t original_start;
+ std::size_t original_end;
+ bool found_bounds = source_code->GetOriginalBodyBounds(
+ m_transformed_text, lang_type, original_start, original_end);
+ if (found_bounds) {
+ m_user_expression_start_pos = original_start;
+ }
}
return lang_type;
}
@@ -591,6 +601,119 @@ bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
return true;
}
+//------------------------------------------------------------------
+/// Converts an absolute position inside a given code string into
+/// a column/line pair.
+///
+/// @param[in] abs_pos
+/// A absolute position in the code string that we want to convert
+/// to a column/line pair.
+///
+/// @param[in] code
+/// A multi-line string usually representing source code.
+///
+/// @param[out] line
+/// The line in the code that contains the given absolute position.
+/// The first line in the string is indexed as 1.
+///
+/// @param[out] column
+/// The column in the line that contains the absolute position.
+/// The first character in a line is indexed as 0.
+//------------------------------------------------------------------
+static void AbsPosToLineColumnPos(unsigned abs_pos, llvm::StringRef code,
+ unsigned &line, unsigned &column) {
+ // Reset to code position to beginning of the file.
+ line = 0;
+ column = 0;
+
+ assert(abs_pos <= code.size() && "Absolute position outside code string?");
+
+ // We have to walk up to the position and count lines/columns.
+ for (std::size_t i = 0; i < abs_pos; ++i) {
+ // If we hit a line break, we go back to column 0 and enter a new line.
+ // We only handle \n because that's what we internally use to make new
+ // lines for our temporary code strings.
+ if (code[i] == '\n') {
+ ++line;
+ column = 0;
+ continue;
+ }
+ ++column;
+ }
+}
+
+bool ClangUserExpression::Complete(ExecutionContext &exe_ctx,
+ StringList &matches, unsigned complete_pos) {
+ Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
+
+ // We don't want any visible feedback when completing an expression. Mostly
+ // because the results we get from an incomplete invocation are probably not
+ // correct.
+ DiagnosticManager diagnostic_manager;
+
+ if (!PrepareForParsing(diagnostic_manager, exe_ctx))
+ return false;
+
+ lldb::LanguageType lang_type = lldb::LanguageType::eLanguageTypeUnknown;
+ if (auto new_lang = GetLanguageForExpr(diagnostic_manager, exe_ctx))
+ lang_type = new_lang.getValue();
+
+ if (log)
+ log->Printf("Parsing the following code:\n%s", m_transformed_text.c_str());
+
+ //////////////////////////
+ // Parse the expression
+ //
+
+ m_materializer_ap.reset(new Materializer());
+
+ ResetDeclMap(exe_ctx, m_result_delegate, /*keep result in memory*/ true);
+
+ OnExit on_exit([this]() { ResetDeclMap(); });
+
+ if (!DeclMap()->WillParse(exe_ctx, m_materializer_ap.get())) {
+ diagnostic_manager.PutString(
+ eDiagnosticSeverityError,
+ "current process state is unsuitable for expression parsing");
+
+ return false;
+ }
+
+ if (m_options.GetExecutionPolicy() == eExecutionPolicyTopLevel) {
+ DeclMap()->SetLookupsEnabled(true);
+ }
+
+ Process *process = exe_ctx.GetProcessPtr();
+ ExecutionContextScope *exe_scope = process;
+
+ if (!exe_scope)
+ exe_scope = exe_ctx.GetTargetPtr();
+
+ ClangExpressionParser parser(exe_scope, *this, false);
+
+ // We have to find the source code location where the user text is inside
+ // the transformed expression code. When creating the transformed text, we
+ // already stored the absolute position in the m_transformed_text string. The
+ // only thing left to do is to transform it into the line:column format that
+ // Clang expects.
+
+ // The line and column of the user expression inside the transformed source
+ // code.
+ unsigned user_expr_line, user_expr_column;
+ if (m_user_expression_start_pos.hasValue())
+ AbsPosToLineColumnPos(*m_user_expression_start_pos, m_transformed_text,
+ user_expr_line, user_expr_column);
+ else
+ return false;
+
+ // The actual column where we have to complete is the start column of the
+ // user expression + the offset inside the user code that we were given.
+ const unsigned completion_column = user_expr_column + complete_pos;
+ parser.Complete(matches, user_expr_line, completion_column, complete_pos);
+
+ return true;
+}
+
bool ClangUserExpression::AddArguments(ExecutionContext &exe_ctx,
std::vector<lldb::addr_t> &args,
lldb::addr_t struct_address,
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
index ac363bf9174..c26975e60e5 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -143,6 +143,9 @@ public:
lldb_private::ExecutionPolicy execution_policy,
bool keep_result_in_memory, bool generate_debug_info) override;
+ bool Complete(ExecutionContext &exe_ctx, StringList &matches,
+ unsigned complete_pos) override;
+
ExpressionTypeSystemHelper *GetTypeSystemHelper() override {
return &m_type_system_helper;
}
@@ -198,6 +201,10 @@ private:
lldb::TargetSP m_target_sp;
};
+ /// The absolute character position in the transformed source code where the
+ /// user code (as typed by the user) starts. If the variable is empty, then we
+ /// were not able to calculate this position.
+ llvm::Optional<unsigned> m_user_expression_start_pos;
ResultDelegate m_result_delegate;
};
OpenPOWER on IntegriCloud