diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -118,6 +118,9 @@ /// enabled. ClangTidyOptionsBuilder GetClangTidyOptions; + /// If true, turn on the `-frecovery-ast` clang flag. + bool BuildRecoveryAST = false; + /// Clangd's workspace root. Relevant for "workspace" operations not bound /// to a particular file. /// FIXME: If not set, should use the current working directory. @@ -345,6 +348,9 @@ // can be caused by missing includes (e.g. member access in incomplete type). bool SuggestMissingIncludes = false; + // If true, preserve expressions in AST for broken code. + bool BuildRecoveryAST = false; + std::function TweakFilter; // GUARDED_BY(CachedCompletionFuzzyFindRequestMutex) diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -134,7 +134,8 @@ : nullptr), GetClangTidyOptions(Opts.GetClangTidyOptions), SuggestMissingIncludes(Opts.SuggestMissingIncludes), - TweakFilter(Opts.TweakFilter), WorkspaceRoot(Opts.WorkspaceRoot), + BuildRecoveryAST(Opts.BuildRecoveryAST), TweakFilter(Opts.TweakFilter), + WorkspaceRoot(Opts.WorkspaceRoot), // Pass a callback into `WorkScheduler` to extract symbols from a newly // parsed file and rebuild the file index synchronously each time an AST // is parsed. @@ -191,6 +192,7 @@ Inputs.ForceRebuild = ForceRebuild; Inputs.Opts = std::move(Opts); Inputs.Index = Index; + Inputs.Opts.BuildRecoveryAST = BuildRecoveryAST; bool NewFile = WorkScheduler.update(File, Inputs, WantDiags); // If we loaded Foo.h, we want to make sure Foo.cpp is indexed. if (NewFile && BackgroundIdx) diff --git a/clang-tools-extra/clangd/Compiler.h b/clang-tools-extra/clangd/Compiler.h --- a/clang-tools-extra/clangd/Compiler.h +++ b/clang-tools-extra/clangd/Compiler.h @@ -38,6 +38,7 @@ struct ParseOptions { tidy::ClangTidyOptions ClangTidyOpts; bool SuggestMissingIncludes = false; + bool BuildRecoveryAST = false; }; /// Information required to run clang, e.g. to parse AST or do code completion. diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -253,6 +253,10 @@ const PrecompiledPreamble *PreamblePCH = Preamble ? &Preamble->Preamble : nullptr; + // Recovery expression currently only works for C++. + if (CI->getLangOpts()->CPlusPlus) + CI->getLangOpts()->RecoveryAST = Opts.BuildRecoveryAST; + StoreDiags ASTDiags; std::string Content = std::string(Buffer->getBuffer()); std::string Filename = diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -132,6 +132,10 @@ // to read back. We rely on dynamic index for the comments instead. CI.getPreprocessorOpts().WriteCommentListToPCH = false; + // Recovery expression currently only works for C++. + if (CI.getLangOpts()->CPlusPlus) + CI.getLangOpts()->RecoveryAST = Inputs.Opts.BuildRecoveryAST; + CppFilePreambleCallbacks SerializedDeclsCollector(FileName, PreambleCallback); if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { log("Couldn't set working directory when building the preamble."); diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -281,6 +281,15 @@ Hidden, }; +opt RecoveryAST{ + "recovery-ast", + cat(Features), + desc("Preserve expressions in AST for broken code (C++ only). Note that " + "this feature is experimental and may lead to crashes"), + init(false), + Hidden, +}; + opt WorkerThreadsCount{ "j", cat(Misc), @@ -629,6 +638,7 @@ } Opts.StaticIndex = StaticIdx.get(); Opts.AsyncThreadsCount = WorkerThreadsCount; + Opts.BuildRecoveryAST = RecoveryAST; clangd::CodeCompleteOptions CCOpts; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -132,6 +132,16 @@ EXPECT_DECLS("CXXOperatorCallExpr", "void operator()(int n)"); } +TEST_F(TargetDeclTest, Recovery) { + Code = R"cpp( + // error-ok: testing behavior on broken code + int f(); + int f(int, int); + int x = [[f]](42); + )cpp"; + EXPECT_DECLS("UnresolvedLookupExpr", "int f()", "int f(int, int)"); +} + TEST_F(TargetDeclTest, UsingDecl) { Code = R"cpp( namespace foo { @@ -685,6 +695,15 @@ )cpp", "0: targets = {x}\n" "1: targets = {X::a}\n"}, + {R"cpp( + // error-ok: testing with broken code + int bar(); + int foo() { + return $0^bar() + $1^bar(42); + } + )cpp", + "0: targets = {bar}\n" + "1: targets = {bar}\n"}, // Namespaces and aliases. {R"cpp( namespace ns {} diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp --- a/clang-tools-extra/clangd/unittests/TestTU.cpp +++ b/clang-tools-extra/clangd/unittests/TestTU.cpp @@ -56,6 +56,7 @@ Inputs.Contents = Code; Inputs.FS = buildTestFS(Files); Inputs.Opts = ParseOptions(); + Inputs.Opts.BuildRecoveryAST = true; Inputs.Opts.ClangTidyOpts.Checks = ClangTidyChecks; Inputs.Opts.ClangTidyOpts.WarningsAsErrors = ClangTidyWarningsAsErrors; Inputs.Index = ExternalIndex;