teemperor updated this revision to Diff 153748.
teemperor marked 13 inline comments as done.
teemperor added a comment.

- Addresses the problems pointed out by Adrian and Pavel (Thanks!)
- Now using the completion API patch to get rid of the code that rebuilds the 
command line string from the args.


https://reviews.llvm.org/D48465

Files:
  include/lldb/Expression/ExpressionParser.h
  include/lldb/Expression/UserExpression.h
  packages/Python/lldbsuite/test/expression_command/completion/.categories
  packages/Python/lldbsuite/test/expression_command/completion/Makefile
  
packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py
  packages/Python/lldbsuite/test/expression_command/completion/main.cpp
  packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
  packages/Python/lldbsuite/test/lldbtest.py
  source/Commands/CommandObjectExpression.cpp
  source/Commands/CommandObjectExpression.h
  source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
  source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
  source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
  source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
  source/Plugins/ExpressionParser/Clang/ClangUserExpression.h

Index: source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
===================================================================
--- source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -143,6 +143,9 @@
              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;
   }
@@ -199,6 +202,10 @@
     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;
 };
 
Index: source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
===================================================================
--- source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -403,6 +403,16 @@
                                    "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;
 }
@@ -592,6 +602,119 @@
   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,
Index: source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
===================================================================
--- source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
+++ 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 @@
   //------------------------------------------------------------------
   ~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 @@
   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>
Index: source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
===================================================================
--- source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ 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"
@@ -544,7 +546,247 @@
 
 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) 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());
@@ -557,8 +799,18 @@
 
   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()) {
@@ -603,14 +855,30 @@
   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();
Index: source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
===================================================================
--- source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
+++ source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp
@@ -87,7 +87,8 @@
         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);
Index: source/Commands/CommandObjectExpression.h
===================================================================
--- source/Commands/CommandObjectExpression.h
+++ source/Commands/CommandObjectExpression.h
@@ -62,6 +62,8 @@
 
   Options *GetOptions() override;
 
+  int HandleCompletion(CompletionRequest &request) override;
+
 protected:
   //------------------------------------------------------------------
   // IOHandler::Delegate functions
Index: source/Commands/CommandObjectExpression.cpp
===================================================================
--- source/Commands/CommandObjectExpression.cpp
+++ source/Commands/CommandObjectExpression.cpp
@@ -307,6 +307,70 @@
 
 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();
+
+  // Remove the first token which is 'expr' or some alias/abbreviation of that.
+  const std::size_t old_code_size = code.size();
+  code = llvm::getToken(code).second.ltrim();
+
+  // We have an option, so we skip behind those options by looking for "--".
+  if (code.startswith("-")) {
+    // Skip all arguments.
+    llvm::StringRef arg_separator = "--";
+    std::size_t separator_pos = code.find(arg_separator);
+    if (separator_pos != llvm::StringRef::npos) {
+      code = code.substr(separator_pos + arg_separator.size());
+    }
+  }
+
+  // Make the cursor_pos again relative to the start of the code string.
+  cursor_pos -= (old_code_size - code.size());
+  // Drop trailing code, as we anyway don't use it for completion.
+  code = code.substr(0, cursor_pos);
+
+  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;
+
+  expr->Complete(exe_ctx, request.GetMatches(), cursor_pos);
+  return request.GetMatches().GetSize();
+}
+
 static lldb_private::Status
 CanBeUsedForElementCountPrinting(ValueObject &valobj) {
   CompilerType type(valobj.GetCompilerType());
Index: packages/Python/lldbsuite/test/lldbtest.py
===================================================================
--- packages/Python/lldbsuite/test/lldbtest.py
+++ packages/Python/lldbsuite/test/lldbtest.py
@@ -184,9 +184,12 @@
     return "Command '%s' returns successfully" % str
 
 
-def COMPLETION_MSG(str_before, str_after):
+def COMPLETION_MSG(str_before, str_after, str_got=None):
     '''A generic message generator for the completion mechanism.'''
-    return "'%s' successfully completes to '%s'" % (str_before, str_after)
+    if str_got != None:
+        return "'%s' successfully completes to '%s' instead of '%s'" % (str_before, str_after, str_got)
+    else:
+        return "'%s' successfully completes to '%s'" % (str_before, str_after)
 
 
 def EXP_MSG(str, actual, exe):
@@ -2133,6 +2136,46 @@
 
         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), exe=False, substrs=[p])
+            else:
+                self.expect(
+                    compare_string, msg=COMPLETION_MSG(
+                        str_input, p), exe=False, patterns=[p])
+
     def expect(
             self,
             str,
Index: packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
===================================================================
--- packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
+++ packages/Python/lldbsuite/test/functionalities/completion/TestCompletion.py
@@ -242,39 +242,3 @@
         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), exe=False, substrs=[p])
-            else:
-                self.expect(
-                    compare_string, msg=COMPLETION_MSG(
-                        str_input, p), exe=False, patterns=[p])
Index: packages/Python/lldbsuite/test/expression_command/completion/main.cpp
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/expression_command/completion/main.cpp
@@ -0,0 +1,30 @@
+namespace LongNamespaceName { class NestedClass { long m; }; }
+
+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();
+    return 0; // Break here
+}
Index: packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/expression_command/completion/TestExprCompletion.py
@@ -0,0 +1,216 @@
+"""
+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')
+
+        # 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')
+
+        # 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):
+        interp = self.dbg.GetCommandInterpreter()
+        match_strings = lldb.SBStringList()
+        num_matches = interp.HandleCompletion(str_input, len(str_input), 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))
Index: packages/Python/lldbsuite/test/expression_command/completion/Makefile
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/expression_command/completion/Makefile
@@ -0,0 +1,5 @@
+LEVEL = ../../make
+
+CXX_SOURCES := main.cpp
+
+include $(LEVEL)/Makefile.rules
Index: packages/Python/lldbsuite/test/expression_command/completion/.categories
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/expression_command/completion/.categories
@@ -0,0 +1 @@
+cmdline
Index: include/lldb/Expression/UserExpression.h
===================================================================
--- include/lldb/Expression/UserExpression.h
+++ include/lldb/Expression/UserExpression.h
@@ -98,6 +98,34 @@
                      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);
Index: include/lldb/Expression/ExpressionParser.h
===================================================================
--- include/lldb/Expression/ExpressionParser.h
+++ include/lldb/Expression/ExpressionParser.h
@@ -49,6 +49,41 @@
   //------------------------------------------------------------------
   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.
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to