1 | //===--- IncludeFixer.cpp ----------------------------------------*- C++-*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "IncludeFixer.h" |
10 | #include "AST.h" |
11 | #include "Diagnostics.h" |
12 | #include "SourceCode.h" |
13 | #include "index/Index.h" |
14 | #include "index/Symbol.h" |
15 | #include "support/Logger.h" |
16 | #include "support/Trace.h" |
17 | #include "clang/AST/Decl.h" |
18 | #include "clang/AST/DeclBase.h" |
19 | #include "clang/AST/DeclarationName.h" |
20 | #include "clang/AST/NestedNameSpecifier.h" |
21 | #include "clang/AST/Type.h" |
22 | #include "clang/Basic/Diagnostic.h" |
23 | #include "clang/Basic/DiagnosticParse.h" |
24 | #include "clang/Basic/DiagnosticSema.h" |
25 | #include "clang/Basic/LangOptions.h" |
26 | #include "clang/Basic/SourceLocation.h" |
27 | #include "clang/Basic/SourceManager.h" |
28 | #include "clang/Basic/TokenKinds.h" |
29 | #include "clang/Lex/Lexer.h" |
30 | #include "clang/Sema/DeclSpec.h" |
31 | #include "clang/Sema/Lookup.h" |
32 | #include "clang/Sema/Scope.h" |
33 | #include "clang/Sema/Sema.h" |
34 | #include "clang/Sema/TypoCorrection.h" |
35 | #include "llvm/ADT/DenseMap.h" |
36 | #include "llvm/ADT/STLExtras.h" |
37 | #include "llvm/ADT/StringExtras.h" |
38 | #include "llvm/ADT/StringRef.h" |
39 | #include "llvm/ADT/StringSet.h" |
40 | #include "llvm/Support/Error.h" |
41 | #include "llvm/Support/FormatVariadic.h" |
42 | #include <algorithm> |
43 | #include <optional> |
44 | #include <set> |
45 | #include <string> |
46 | #include <vector> |
47 | |
48 | namespace clang { |
49 | namespace clangd { |
50 | namespace { |
51 | |
52 | std::optional<llvm::StringRef> getArgStr(const clang::Diagnostic &Info, |
53 | unsigned Index) { |
54 | switch (Info.getArgKind(Idx: Index)) { |
55 | case DiagnosticsEngine::ak_c_string: |
56 | return llvm::StringRef(Info.getArgCStr(Idx: Index)); |
57 | case DiagnosticsEngine::ak_std_string: |
58 | return llvm::StringRef(Info.getArgStdStr(Idx: Index)); |
59 | default: |
60 | return std::nullopt; |
61 | } |
62 | } |
63 | |
64 | std::vector<Fix> only(std::optional<Fix> F) { |
65 | if (F) |
66 | return {std::move(*F)}; |
67 | return {}; |
68 | } |
69 | |
70 | } // namespace |
71 | |
72 | std::vector<Fix> IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel, |
73 | const clang::Diagnostic &Info) const { |
74 | switch (Info.getID()) { |
75 | /* |
76 | There are many "incomplete type" diagnostics! |
77 | They are almost all Sema diagnostics with "incomplete" in the name. |
78 | |
79 | sed -n '/CLASS_NOTE/! s/DIAG(\\([^,]*\\).*)/ case diag::\\1:/p' \ |
80 | tools/clang/include/clang/Basic/DiagnosticSemaKinds.inc | grep incomplete |
81 | */ |
82 | // clang-format off |
83 | //case diag::err_alignof_member_of_incomplete_type: |
84 | case diag::err_array_incomplete_or_sizeless_type: |
85 | case diag::err_array_size_incomplete_type: |
86 | case diag::err_asm_incomplete_type: |
87 | case diag::err_bad_cast_incomplete: |
88 | case diag::err_call_function_incomplete_return: |
89 | case diag::err_call_incomplete_argument: |
90 | case diag::err_call_incomplete_return: |
91 | case diag::err_capture_of_incomplete_or_sizeless_type: |
92 | case diag::err_catch_incomplete: |
93 | case diag::err_catch_incomplete_ptr: |
94 | case diag::err_catch_incomplete_ref: |
95 | case diag::err_cconv_incomplete_param_type: |
96 | case diag::err_coroutine_promise_type_incomplete: |
97 | case diag::err_covariant_return_incomplete: |
98 | //case diag::err_deduced_class_template_incomplete: |
99 | case diag::err_delete_incomplete_class_type: |
100 | case diag::err_dereference_incomplete_type: |
101 | case diag::err_exception_spec_incomplete_type: |
102 | case diag::err_field_incomplete_or_sizeless: |
103 | case diag::err_for_range_incomplete_type: |
104 | case diag::err_func_def_incomplete_result: |
105 | case diag::err_ice_incomplete_type: |
106 | case diag::err_illegal_message_expr_incomplete_type: |
107 | case diag::err_incomplete_base_class: |
108 | case diag::err_incomplete_enum: |
109 | case diag::err_incomplete_in_exception_spec: |
110 | case diag::err_incomplete_member_access: |
111 | case diag::err_incomplete_nested_name_spec: |
112 | case diag::err_incomplete_object_call: |
113 | case diag::err_incomplete_receiver_type: |
114 | case diag::err_incomplete_synthesized_property: |
115 | case diag::err_incomplete_type: |
116 | case diag::err_incomplete_type_objc_at_encode: |
117 | case diag::err_incomplete_type_used_in_type_trait_expr: |
118 | case diag::err_incomplete_typeid: |
119 | case diag::err_init_incomplete_type: |
120 | case diag::err_invalid_incomplete_type_use: |
121 | case diag::err_lambda_incomplete_result: |
122 | //case diag::err_matrix_incomplete_index: |
123 | //case diag::err_matrix_separate_incomplete_index: |
124 | case diag::err_memptr_incomplete: |
125 | case diag::err_new_incomplete_or_sizeless_type: |
126 | case diag::err_objc_incomplete_boxed_expression_type: |
127 | case diag::err_objc_index_incomplete_class_type: |
128 | case diag::err_offsetof_incomplete_type: |
129 | case diag::err_omp_firstprivate_incomplete_type: |
130 | case diag::err_omp_incomplete_type: |
131 | case diag::err_omp_lastprivate_incomplete_type: |
132 | case diag::err_omp_linear_incomplete_type: |
133 | case diag::err_omp_private_incomplete_type: |
134 | case diag::err_omp_reduction_incomplete_type: |
135 | case diag::err_omp_section_incomplete_type: |
136 | case diag::err_omp_threadprivate_incomplete_type: |
137 | case diag::err_second_parameter_to_va_arg_incomplete: |
138 | case diag::err_sizeof_alignof_incomplete_or_sizeless_type: |
139 | case diag::err_subscript_incomplete_or_sizeless_type: |
140 | case diag::err_switch_incomplete_class_type: |
141 | case diag::err_temp_copy_incomplete: |
142 | //case diag::err_template_arg_deduced_incomplete_pack: |
143 | case diag::err_template_nontype_parm_incomplete: |
144 | //case diag::err_tentative_def_incomplete_type: |
145 | case diag::err_throw_incomplete: |
146 | case diag::err_throw_incomplete_ptr: |
147 | case diag::err_typecheck_arithmetic_incomplete_or_sizeless_type: |
148 | case diag::err_typecheck_cast_to_incomplete: |
149 | case diag::err_typecheck_decl_incomplete_type: |
150 | //case diag::err_typecheck_incomplete_array_needs_initializer: |
151 | case diag::err_typecheck_incomplete_tag: |
152 | case diag::err_typecheck_incomplete_type_not_modifiable_lvalue: |
153 | case diag::err_typecheck_nonviable_condition_incomplete: |
154 | case diag::err_underlying_type_of_incomplete_enum: |
155 | case diag::ext_incomplete_in_exception_spec: |
156 | //case diag::ext_typecheck_compare_complete_incomplete_pointers: |
157 | case diag::ext_typecheck_decl_incomplete_type: |
158 | case diag::warn_delete_incomplete: |
159 | case diag::warn_incomplete_encoded_type: |
160 | //case diag::warn_printf_incomplete_specifier: |
161 | case diag::warn_return_value_udt_incomplete: |
162 | //case diag::warn_scanf_scanlist_incomplete: |
163 | //case diag::warn_tentative_incomplete_array: |
164 | // clang-format on |
165 | // Incomplete type diagnostics should have a QualType argument for the |
166 | // incomplete type. |
167 | for (unsigned Idx = 0; Idx < Info.getNumArgs(); ++Idx) { |
168 | if (Info.getArgKind(Idx) == DiagnosticsEngine::ak_qualtype) { |
169 | auto QT = QualType::getFromOpaquePtr(Ptr: (void *)Info.getRawArg(Idx)); |
170 | if (const Type *T = QT.getTypePtrOrNull()) { |
171 | if (T->isIncompleteType()) |
172 | return fixIncompleteType(T: *T); |
173 | // `enum x : int;' is not formally an incomplete type. |
174 | // We may need a full definition anyway. |
175 | if (auto * ET = llvm::dyn_cast<EnumType>(Val: T)) |
176 | if (!ET->getDecl()->getDefinition()) |
177 | return fixIncompleteType(T: *T); |
178 | } |
179 | } |
180 | } |
181 | break; |
182 | |
183 | case diag::err_unknown_typename: |
184 | case diag::err_unknown_typename_suggest: |
185 | case diag::err_unknown_type_or_class_name_suggest: |
186 | case diag::err_expected_class_name: |
187 | case diag::err_typename_nested_not_found: |
188 | case diag::err_no_template: |
189 | case diag::err_no_template_suggest: |
190 | case diag::err_undeclared_use: |
191 | case diag::err_undeclared_use_suggest: |
192 | case diag::err_undeclared_var_use: |
193 | case diag::err_undeclared_var_use_suggest: |
194 | case diag::err_no_member: // Could be no member in namespace. |
195 | case diag::err_no_member_suggest: |
196 | case diag::err_no_member_template: |
197 | case diag::err_no_member_template_suggest: |
198 | case diag::warn_implicit_function_decl: |
199 | case diag::ext_implicit_function_decl_c99: |
200 | dlog("Unresolved name at {0}, last typo was {1}" , |
201 | Info.getLocation().printToString(Info.getSourceManager()), |
202 | LastUnresolvedName |
203 | ? LastUnresolvedName->Loc.printToString(Info.getSourceManager()) |
204 | : "none" ); |
205 | if (LastUnresolvedName) { |
206 | // Try to fix unresolved name caused by missing declaration. |
207 | // E.g. |
208 | // clang::SourceManager SM; |
209 | // ~~~~~~~~~~~~~ |
210 | // UnresolvedName |
211 | // or |
212 | // namespace clang { SourceManager SM; } |
213 | // ~~~~~~~~~~~~~ |
214 | // UnresolvedName |
215 | // We only attempt to recover a diagnostic if it has the same location as |
216 | // the last seen unresolved name. |
217 | if (LastUnresolvedName->Loc == Info.getLocation()) |
218 | return fixUnresolvedName(); |
219 | } |
220 | break; |
221 | |
222 | // Cases where clang explicitly knows which header to include. |
223 | // (There's no fix provided for boring formatting reasons). |
224 | case diag::err_implied_std_initializer_list_not_found: |
225 | return only(F: insertHeader(Name: "<initializer_list>" )); |
226 | case diag::err_need_header_before_typeid: |
227 | return only(F: insertHeader(Name: "<typeinfo>" )); |
228 | case diag::err_need_header_before_placement_new: |
229 | case diag::err_implicit_coroutine_std_nothrow_type_not_found: |
230 | return only(F: insertHeader(Name: "<new>" )); |
231 | case diag::err_omp_implied_type_not_found: |
232 | case diag::err_omp_interop_type_not_found: |
233 | return only(F: insertHeader(Name: "<omp.h>" )); |
234 | case diag::err_implied_coroutine_type_not_found: |
235 | return only(F: insertHeader(Name: "<coroutine>" )); |
236 | case diag::err_implied_comparison_category_type_not_found: |
237 | return only(F: insertHeader(Name: "<compare>" )); |
238 | case diag::note_include_header_or_declare: |
239 | if (Info.getNumArgs() > 0) |
240 | if (auto = getArgStr(Info, Index: 0)) |
241 | return only(F: insertHeader(Name: ("<" + *Header + ">" ).str(), |
242 | Symbol: getArgStr(Info, Index: 1).value_or(u: "" ))); |
243 | break; |
244 | } |
245 | |
246 | return {}; |
247 | } |
248 | |
249 | std::optional<Fix> IncludeFixer::(llvm::StringRef Spelled, |
250 | llvm::StringRef Symbol, |
251 | tooling::IncludeDirective Directive) const { |
252 | Fix F; |
253 | |
254 | if (auto Edit = Inserter->insert(VerbatimHeader: Spelled, Directive)) |
255 | F.Edits.push_back(Elt: std::move(*Edit)); |
256 | else |
257 | return std::nullopt; |
258 | |
259 | llvm::StringRef DirectiveSpelling = |
260 | Directive == tooling::IncludeDirective::Include ? "Include" : "Import" ; |
261 | if (Symbol.empty()) |
262 | F.Message = llvm::formatv(Fmt: "{0} {1}" , Vals&: DirectiveSpelling, Vals&: Spelled); |
263 | else |
264 | F.Message = llvm::formatv(Fmt: "{0} {1} for symbol {2}" , |
265 | Vals&: DirectiveSpelling, Vals&: Spelled, Vals&: Symbol); |
266 | |
267 | return F; |
268 | } |
269 | |
270 | std::vector<Fix> IncludeFixer::fixIncompleteType(const Type &T) const { |
271 | // Only handle incomplete TagDecl type. |
272 | const TagDecl *TD = T.getAsTagDecl(); |
273 | if (!TD) |
274 | return {}; |
275 | std::string TypeName = printQualifiedName(*TD); |
276 | trace::Span Tracer("Fix include for incomplete type" ); |
277 | SPAN_ATTACH(Tracer, "type" , TypeName); |
278 | vlog(Fmt: "Trying to fix include for incomplete type {0}" , Vals&: TypeName); |
279 | |
280 | auto ID = getSymbolID(TD); |
281 | if (!ID) |
282 | return {}; |
283 | std::optional<const SymbolSlab *> Symbols = lookupCached(ID: ID); |
284 | if (!Symbols) |
285 | return {}; |
286 | const SymbolSlab &Syms = **Symbols; |
287 | std::vector<Fix> Fixes; |
288 | if (!Syms.empty()) { |
289 | auto &Matched = *Syms.begin(); |
290 | if (!Matched.IncludeHeaders.empty() && Matched.Definition && |
291 | Matched.CanonicalDeclaration.FileURI == Matched.Definition.FileURI) |
292 | Fixes = fixesForSymbols(Syms); |
293 | } |
294 | return Fixes; |
295 | } |
296 | |
297 | std::vector<Fix> IncludeFixer::fixesForSymbols(const SymbolSlab &Syms) const { |
298 | auto Inserted = [&](const Symbol &Sym, llvm::StringRef ) |
299 | -> llvm::Expected<std::pair<std::string, bool>> { |
300 | auto ResolvedDeclaring = |
301 | URI::resolve(FileURI: Sym.CanonicalDeclaration.FileURI, HintPath: File); |
302 | if (!ResolvedDeclaring) |
303 | return ResolvedDeclaring.takeError(); |
304 | auto ResolvedInserted = toHeaderFile(Header, HintPath: File); |
305 | if (!ResolvedInserted) |
306 | return ResolvedInserted.takeError(); |
307 | auto Spelled = Inserter->calculateIncludePath(InsertedHeader: *ResolvedInserted, IncludingFile: File); |
308 | if (!Spelled) |
309 | return error(Fmt: "Header not on include path" ); |
310 | return std::make_pair( |
311 | x: std::move(*Spelled), |
312 | y: Inserter->shouldInsertInclude(DeclaringHeader: *ResolvedDeclaring, InsertedHeader: *ResolvedInserted)); |
313 | }; |
314 | |
315 | std::vector<Fix> Fixes; |
316 | // Deduplicate fixes by include headers. This doesn't distinguish symbols in |
317 | // different scopes from the same header, but this case should be rare and is |
318 | // thus ignored. |
319 | llvm::StringSet<> ; |
320 | for (const auto &Sym : Syms) { |
321 | for (const auto &Inc : getRankedIncludes(Sym)) { |
322 | if ((Inc.Directive & Directive) == 0) |
323 | continue; |
324 | if (auto ToInclude = Inserted(Sym, Inc.Header)) { |
325 | if (ToInclude->second) { |
326 | if (!InsertedHeaders.try_emplace(Key: ToInclude->first).second) |
327 | continue; |
328 | if (auto Fix = |
329 | insertHeader(Spelled: ToInclude->first, Symbol: (Sym.Scope + Sym.Name).str(), |
330 | Directive: Directive == Symbol::Import |
331 | ? tooling::IncludeDirective::Import |
332 | : tooling::IncludeDirective::Include)) |
333 | Fixes.push_back(x: std::move(*Fix)); |
334 | } |
335 | } else { |
336 | vlog(Fmt: "Failed to calculate include insertion for {0} into {1}: {2}" , |
337 | Vals: Inc.Header, Vals: File, Vals: ToInclude.takeError()); |
338 | } |
339 | } |
340 | } |
341 | return Fixes; |
342 | } |
343 | |
344 | // Returns the identifiers qualified by an unresolved name. \p Loc is the |
345 | // start location of the unresolved name. For the example below, this returns |
346 | // "::X::Y" that is qualified by unresolved name "clangd": |
347 | // clang::clangd::X::Y |
348 | // ~ |
349 | std::optional<std::string> qualifiedByUnresolved(const SourceManager &SM, |
350 | SourceLocation Loc, |
351 | const LangOptions &LangOpts) { |
352 | std::string Result; |
353 | // Accept qualifier written within macro arguments, but not macro bodies. |
354 | SourceLocation NextLoc = SM.getTopMacroCallerLoc(Loc); |
355 | while (auto CCTok = Lexer::findNextToken(Loc: NextLoc, SM, LangOpts)) { |
356 | if (!CCTok->is(K: tok::coloncolon)) |
357 | break; |
358 | auto IDTok = Lexer::findNextToken(Loc: CCTok->getLocation(), SM, LangOpts); |
359 | if (!IDTok || !IDTok->is(K: tok::raw_identifier)) |
360 | break; |
361 | Result.append(str: ("::" + IDTok->getRawIdentifier()).str()); |
362 | NextLoc = IDTok->getLocation(); |
363 | } |
364 | if (Result.empty()) |
365 | return std::nullopt; |
366 | return Result; |
367 | } |
368 | |
369 | // An unresolved name and its scope information that can be extracted cheaply. |
370 | struct CheapUnresolvedName { |
371 | std::string Name; |
372 | // This is the part of what was typed that was resolved, and it's in its |
373 | // resolved form not its typed form (think `namespace clang { clangd::x }` --> |
374 | // `clang::clangd::`). |
375 | std::optional<std::string> ResolvedScope; |
376 | |
377 | // Unresolved part of the scope. When the unresolved name is a specifier, we |
378 | // use the name that comes after it as the alternative name to resolve and use |
379 | // the specifier as the extra scope in the accessible scopes. |
380 | std::optional<std::string> UnresolvedScope; |
381 | }; |
382 | |
383 | std::optional<std::string> getSpelledSpecifier(const CXXScopeSpec &SS, |
384 | const SourceManager &SM) { |
385 | // Support specifiers written within a single macro argument. |
386 | if (!SM.isWrittenInSameFile(Loc1: SS.getBeginLoc(), Loc2: SS.getEndLoc())) |
387 | return std::nullopt; |
388 | SourceRange Range(SM.getTopMacroCallerLoc(Loc: SS.getBeginLoc()), SM.getTopMacroCallerLoc(Loc: SS.getEndLoc())); |
389 | if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID()) |
390 | return std::nullopt; |
391 | |
392 | return (toSourceCode(SM, R: Range) + "::" ).str(); |
393 | } |
394 | |
395 | // Extracts unresolved name and scope information around \p Unresolved. |
396 | // FIXME: try to merge this with the scope-wrangling code in CodeComplete. |
397 | std::optional<CheapUnresolvedName> ( |
398 | const SourceManager &SM, const DeclarationNameInfo &Unresolved, |
399 | CXXScopeSpec *SS, const LangOptions &LangOpts, bool UnresolvedIsSpecifier) { |
400 | CheapUnresolvedName Result; |
401 | Result.Name = Unresolved.getAsString(); |
402 | if (SS && SS->isNotEmpty()) { // "::" or "ns::" |
403 | if (auto *Nested = SS->getScopeRep()) { |
404 | if (Nested->getKind() == NestedNameSpecifier::Global) { |
405 | Result.ResolvedScope = "" ; |
406 | } else if (const auto *NS = Nested->getAsNamespace()) { |
407 | std::string SpecifiedNS = printNamespaceScope(*NS); |
408 | std::optional<std::string> Spelling = getSpelledSpecifier(SS: *SS, SM); |
409 | |
410 | // Check the specifier spelled in the source. |
411 | // If the resolved scope doesn't end with the spelled scope, the |
412 | // resolved scope may come from a sema typo correction. For example, |
413 | // sema assumes that "clangd::" is a typo of "clang::" and uses |
414 | // "clang::" as the specified scope in: |
415 | // namespace clang { clangd::X; } |
416 | // In this case, we use the "typo" specifier as extra scope instead |
417 | // of using the scope assumed by sema. |
418 | if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(Suffix: *Spelling)) { |
419 | Result.ResolvedScope = std::move(SpecifiedNS); |
420 | } else { |
421 | Result.UnresolvedScope = std::move(*Spelling); |
422 | } |
423 | } else if (const auto *ANS = Nested->getAsNamespaceAlias()) { |
424 | Result.ResolvedScope = printNamespaceScope(*ANS->getNamespace()); |
425 | } else { |
426 | // We don't fix symbols in scopes that are not top-level e.g. class |
427 | // members, as we don't collect includes for them. |
428 | return std::nullopt; |
429 | } |
430 | } |
431 | } |
432 | |
433 | if (UnresolvedIsSpecifier) { |
434 | // If the unresolved name is a specifier e.g. |
435 | // clang::clangd::X |
436 | // ~~~~~~ |
437 | // We try to resolve clang::clangd::X instead of clang::clangd. |
438 | // FIXME: We won't be able to fix include if the specifier is what we |
439 | // should resolve (e.g. it's a class scope specifier). Collecting include |
440 | // headers for nested types could make this work. |
441 | |
442 | // Not using the end location as it doesn't always point to the end of |
443 | // identifier. |
444 | if (auto QualifiedByUnresolved = |
445 | qualifiedByUnresolved(SM, Loc: Unresolved.getBeginLoc(), LangOpts)) { |
446 | auto Split = splitQualifiedName(QName: *QualifiedByUnresolved); |
447 | if (!Result.UnresolvedScope) |
448 | Result.UnresolvedScope.emplace(); |
449 | // If UnresolvedSpecifiedScope is already set, we simply append the |
450 | // extra scope. Suppose the unresolved name is "index" in the following |
451 | // example: |
452 | // namespace clang { clangd::index::X; } |
453 | // ~~~~~~ ~~~~~ |
454 | // "clangd::" is assumed to be clang:: by Sema, and we would have used |
455 | // it as extra scope. With "index" being a specifier, we append "index::" |
456 | // to the extra scope. |
457 | Result.UnresolvedScope->append(str: (Result.Name + Split.first).str()); |
458 | Result.Name = std::string(Split.second); |
459 | } |
460 | } |
461 | return Result; |
462 | } |
463 | |
464 | /// Returns all namespace scopes that the unqualified lookup would visit. |
465 | std::vector<std::string> |
466 | collectAccessibleScopes(Sema &Sem, const DeclarationNameInfo &Typo, Scope *S, |
467 | Sema::LookupNameKind LookupKind) { |
468 | // Collects contexts visited during a Sema name lookup. |
469 | struct VisitedContextCollector : public VisibleDeclConsumer { |
470 | VisitedContextCollector(std::vector<std::string> &Out) : Out(Out) {} |
471 | void EnteredContext(DeclContext *Ctx) override { |
472 | if (llvm::isa<NamespaceDecl>(Val: Ctx)) |
473 | Out.push_back(x: printNamespaceScope(DC: *Ctx)); |
474 | } |
475 | void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx, |
476 | bool InBaseClass) override {} |
477 | std::vector<std::string> &Out; |
478 | }; |
479 | |
480 | std::vector<std::string> Scopes; |
481 | Scopes.push_back(x: "" ); |
482 | VisitedContextCollector Collector(Scopes); |
483 | Sem.LookupVisibleDecls(S, Kind: LookupKind, Consumer&: Collector, |
484 | /*IncludeGlobalScope=*/false, |
485 | /*LoadExternal=*/false); |
486 | llvm::sort(C&: Scopes); |
487 | Scopes.erase(first: llvm::unique(R&: Scopes), last: Scopes.end()); |
488 | return Scopes; |
489 | } |
490 | |
491 | class IncludeFixer::UnresolvedNameRecorder : public ExternalSemaSource { |
492 | public: |
493 | UnresolvedNameRecorder(std::optional<UnresolvedName> &LastUnresolvedName) |
494 | : LastUnresolvedName(LastUnresolvedName) {} |
495 | |
496 | void InitializeSema(Sema &S) override { this->SemaPtr = &S; } |
497 | |
498 | // Captures the latest typo and treat it as an unresolved name that can |
499 | // potentially be fixed by adding #includes. |
500 | TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, |
501 | Scope *S, CXXScopeSpec *SS, |
502 | CorrectionCandidateCallback &CCC, |
503 | DeclContext *MemberContext, bool EnteringContext, |
504 | const ObjCObjectPointerType *OPT) override { |
505 | dlog("CorrectTypo: {0}" , Typo.getAsString()); |
506 | assert(SemaPtr && "Sema must have been set." ); |
507 | if (SemaPtr->isSFINAEContext()) |
508 | return TypoCorrection(); |
509 | if (!isInsideMainFile(Loc: Typo.getLoc(), SM: SemaPtr->SourceMgr)) |
510 | return clang::TypoCorrection(); |
511 | |
512 | auto = extractUnresolvedNameCheaply( |
513 | SM: SemaPtr->SourceMgr, Unresolved: Typo, SS, LangOpts: SemaPtr->LangOpts, |
514 | UnresolvedIsSpecifier: static_cast<Sema::LookupNameKind>(LookupKind) == |
515 | Sema::LookupNameKind::LookupNestedNameSpecifierName); |
516 | if (!Extracted) |
517 | return TypoCorrection(); |
518 | |
519 | UnresolvedName Unresolved; |
520 | Unresolved.Name = Extracted->Name; |
521 | Unresolved.Loc = Typo.getBeginLoc(); |
522 | if (!Extracted->ResolvedScope && !S) // Give up if no scope available. |
523 | return TypoCorrection(); |
524 | |
525 | if (Extracted->ResolvedScope) |
526 | Unresolved.Scopes.push_back(x: *Extracted->ResolvedScope); |
527 | else // no qualifier or qualifier is unresolved. |
528 | Unresolved.Scopes = collectAccessibleScopes( |
529 | Sem&: *SemaPtr, Typo, S, LookupKind: static_cast<Sema::LookupNameKind>(LookupKind)); |
530 | |
531 | if (Extracted->UnresolvedScope) { |
532 | for (std::string &Scope : Unresolved.Scopes) |
533 | Scope += *Extracted->UnresolvedScope; |
534 | } |
535 | |
536 | LastUnresolvedName = std::move(Unresolved); |
537 | |
538 | // Never return a valid correction to try to recover. Our suggested fixes |
539 | // always require a rebuild. |
540 | return TypoCorrection(); |
541 | } |
542 | |
543 | private: |
544 | Sema *SemaPtr = nullptr; |
545 | |
546 | std::optional<UnresolvedName> &LastUnresolvedName; |
547 | }; |
548 | |
549 | llvm::IntrusiveRefCntPtr<ExternalSemaSource> |
550 | IncludeFixer::unresolvedNameRecorder() { |
551 | return new UnresolvedNameRecorder(LastUnresolvedName); |
552 | } |
553 | |
554 | std::vector<Fix> IncludeFixer::fixUnresolvedName() const { |
555 | assert(LastUnresolvedName); |
556 | auto &Unresolved = *LastUnresolvedName; |
557 | vlog(Fmt: "Trying to fix unresolved name \"{0}\" in scopes: [{1}]" , |
558 | Vals: Unresolved.Name, Vals: llvm::join(R: Unresolved.Scopes, Separator: ", " )); |
559 | |
560 | FuzzyFindRequest Req; |
561 | Req.AnyScope = false; |
562 | Req.Query = Unresolved.Name; |
563 | Req.Scopes = Unresolved.Scopes; |
564 | Req.RestrictForCodeCompletion = true; |
565 | Req.Limit = 100; |
566 | |
567 | if (std::optional<const SymbolSlab *> Syms = fuzzyFindCached(Req)) |
568 | return fixesForSymbols(Syms: **Syms); |
569 | |
570 | return {}; |
571 | } |
572 | |
573 | std::optional<const SymbolSlab *> |
574 | IncludeFixer::fuzzyFindCached(const FuzzyFindRequest &Req) const { |
575 | auto ReqStr = llvm::formatv(Fmt: "{0}" , Vals: toJSON(Request: Req)).str(); |
576 | auto I = FuzzyFindCache.find(Key: ReqStr); |
577 | if (I != FuzzyFindCache.end()) |
578 | return &I->second; |
579 | |
580 | if (IndexRequestCount >= IndexRequestLimit) |
581 | return std::nullopt; |
582 | IndexRequestCount++; |
583 | |
584 | SymbolSlab::Builder Matches; |
585 | Index.fuzzyFind(Req, Callback: [&](const Symbol &Sym) { |
586 | if (Sym.Name != Req.Query) |
587 | return; |
588 | if (!Sym.IncludeHeaders.empty()) |
589 | Matches.insert(S: Sym); |
590 | }); |
591 | auto Syms = std::move(Matches).build(); |
592 | auto E = FuzzyFindCache.try_emplace(Key: ReqStr, Args: std::move(Syms)); |
593 | return &E.first->second; |
594 | } |
595 | |
596 | std::optional<const SymbolSlab *> |
597 | IncludeFixer::lookupCached(const SymbolID &ID) const { |
598 | LookupRequest Req; |
599 | Req.IDs.insert(V: ID); |
600 | |
601 | auto I = LookupCache.find(Val: ID); |
602 | if (I != LookupCache.end()) |
603 | return &I->second; |
604 | |
605 | if (IndexRequestCount >= IndexRequestLimit) |
606 | return std::nullopt; |
607 | IndexRequestCount++; |
608 | |
609 | // FIXME: consider batching the requests for all diagnostics. |
610 | SymbolSlab::Builder Matches; |
611 | Index.lookup(Req, Callback: [&](const Symbol &Sym) { Matches.insert(S: Sym); }); |
612 | auto Syms = std::move(Matches).build(); |
613 | |
614 | std::vector<Fix> Fixes; |
615 | if (!Syms.empty()) { |
616 | auto &Matched = *Syms.begin(); |
617 | if (!Matched.IncludeHeaders.empty() && Matched.Definition && |
618 | Matched.CanonicalDeclaration.FileURI == Matched.Definition.FileURI) |
619 | Fixes = fixesForSymbols(Syms); |
620 | } |
621 | auto E = LookupCache.try_emplace(Key: ID, Args: std::move(Syms)); |
622 | return &E.first->second; |
623 | } |
624 | |
625 | } // namespace clangd |
626 | } // namespace clang |
627 | |