[clang] [Clang] suggest headers on undeclared errors (#120388) (PR #140247)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 16 05:49:58 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Jongmyeong Choi (jongmyeong-choi)
<details>
<summary>Changes</summary>
When a “use of undeclared identifier” error happens for a function listed in StdSymbolMap, emit a note telling the user which header to include.
Because nested-name-specifier errors occur before the function name is known, perform this lookup later in isCXXDeclarationSpecifier() after a parse failure.
---
Full diff: https://github.com/llvm/llvm-project/pull/140247.diff
12 Files Affected:
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+1)
- (modified) clang/lib/Parse/ParseTentative.cpp (+30)
- (modified) clang/lib/Sema/CMakeLists.txt (+2-1)
- (modified) clang/lib/Sema/SemaExpr.cpp (+17)
- (modified) clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp (+9-3)
- (modified) clang/test/CXX/drs/cwg3xx.cpp (+2)
- (modified) clang/test/Modules/implicit-declared-allocation-functions.cppm (+6-6)
- (modified) clang/test/Modules/macro-reexport.cpp (+3-3)
- (modified) clang/test/OpenMP/allocate_modifiers_messages.cpp (+1)
- (modified) clang/test/Sema/builtin-setjmp.c (+6-4)
- (added) clang/test/Sema/note-suggest-header.cpp (+10)
- (modified) clang/test/SemaCXX/no-implicit-builtin-decls.cpp (+1-1)
``````````diff
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 604bb56d28252..bbc8e56175665 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5984,6 +5984,7 @@ def err_unexpected_typedef : Error<
def err_unexpected_namespace : Error<
"unexpected namespace name %0: expected expression">;
def err_undeclared_var_use : Error<"use of undeclared identifier %0">;
+def note_include_for_declaration : Note<"perhaps `#include %0` is needed?">;
def ext_undeclared_unqual_id_with_dependent_base : ExtWarn<
"use of undeclared identifier %0; "
"unqualified lookup into dependent bases of class template %1 is a Microsoft extension">,
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index fcd76c75c9bfb..3cb46cbc4b665 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -14,6 +14,8 @@
#include "clang/Basic/DiagnosticParse.h"
#include "clang/Parse/Parser.h"
#include "clang/Sema/ParsedTemplate.h"
+#include "clang/Tooling/Inclusions/StandardLibrary.h"
+
using namespace clang;
/// isCXXDeclarationStatement - C++-specialized function that disambiguates
@@ -1442,6 +1444,34 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
// If annotation failed, assume it's a non-type.
// FIXME: If this happens due to an undeclared identifier, treat it as
// ambiguous.
+
+ if (Tok.is(tok::annot_cxxscope)) {
+ CXXScopeSpec SS;
+ Actions.RestoreNestedNameSpecifierAnnotation(
+ Tok.getAnnotationValue(), Tok.getAnnotationRange(), SS);
+ auto Next = NextToken();
+ if (SS.isInvalid() && Next.is(tok::identifier)) {
+ auto SymbolName = Next.getIdentifierInfo()->getName();
+ SourceRange SR = Tok.getAnnotationRange();
+ CharSourceRange CSR = CharSourceRange::getCharRange(
+ SR.getBegin(), SR.getEnd().getLocWithOffset(2));
+ StringRef ScopeText = Lexer::getSourceText(CSR, PP.getSourceManager(),
+ PP.getLangOpts());
+
+ auto header = [](llvm::StringRef symbolName, llvm::StringRef scope) {
+ for (const auto &symbol : clang::tooling::stdlib::Symbol::all()) {
+ if (symbol.name() == symbolName && symbol.scope() == scope)
+ return symbol.header()->name();
+ }
+ return llvm::StringRef();
+ }(SymbolName, ScopeText);
+
+ if (header.size() > 0) {
+ Diag(Next.getLocation(), diag::note_include_for_declaration)
+ << header;
+ }
+ }
+ }
if (Tok.is(tok::identifier))
return TPResult::False;
}
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 4b87004e4b8ea..de084d01cc553 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -114,4 +114,5 @@ add_clang_library(clangSema
clangEdit
clangLex
clangSupport
- )
+ clangToolingInclusionsStdlib
+)
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d72d97addfac2..9c12eea2083b8 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -58,6 +58,7 @@
#include "clang/Sema/SemaOpenMP.h"
#include "clang/Sema/SemaPseudoObject.h"
#include "clang/Sema/Template.h"
+#include "clang/Tooling/Inclusions/StandardLibrary.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/ADT/StringExtras.h"
@@ -2647,6 +2648,22 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,
// Give up, we can't recover.
Diag(R.getNameLoc(), diagnostic) << Name << NameRange;
+
+ llvm::errs() << "cheese ##1) name's kind : " << Name.getAsString() << "\n";
+ auto header = [](const std::string &symbolName) {
+ for (const auto &symbol : clang::tooling::stdlib::Symbol::all()) {
+ if (symbol.header() && symbol.name() == symbolName)
+ return symbol.header()->name();
+ }
+ return llvm::StringRef();
+ }(Name.getAsString());
+
+ llvm::errs() << "cheese ##2) header : " << header << "\n";
+ bool IsFunctionContext = !Args.empty() || ExplicitTemplateArgs != nullptr;
+ if (diagnostic == diag::err_undeclared_var_use && header.size() > 0 &&
+ IsFunctionContext) {
+ Diag(R.getNameLoc(), diag::note_include_for_declaration) << header;
+ }
return true;
}
diff --git a/clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp b/clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp
index ce5eefc6bfdb4..60b66aac9e5ca 100644
--- a/clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp
+++ b/clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp
@@ -26,14 +26,18 @@ void no_get_1() {
auto [a0, a1] = A(); // expected-error {{decomposes into 3 elements}}
auto [b0, b1] = B(); // expected-error {{decomposes into 3 elements}}
}
- auto [a0, a1, a2] = A(); // expected-error {{undeclared identifier 'get'}} expected-note {{in implicit initialization of binding declaration 'a0'}}
+ auto [a0, a1, a2] = A(); // expected-error {{undeclared identifier 'get'}} \
+ // expected-note {{perhaps `#include <ranges>` is needed?}} \
+ // expected-note {{in implicit initialization of binding declaration 'a0'}}
}
int get(A);
void no_get_2() {
// FIXME: This diagnostic is not great.
- auto [a0, a1, a2] = A(); // expected-error {{undeclared identifier 'get'}} expected-note {{in implicit initialization of binding declaration 'a0'}}
+ auto [a0, a1, a2] = A(); // expected-error {{undeclared identifier 'get'}} \
+ // expected-note {{perhaps `#include <ranges>` is needed?}} \
+ // expected-note {{in implicit initialization of binding declaration 'a0'}}
}
template<int> float &get(A); // expected-note 2 {{no known conversion}}
@@ -172,7 +176,9 @@ template<int> int get(ADL::X);
template<> struct std::tuple_size<ADL::X> { static const int value = 1; };
template<> struct std::tuple_element<0, ADL::X> { typedef int type; };
void adl_only_bad() {
- auto [x] = ADL::X(); // expected-error {{undeclared identifier 'get'}} expected-note {{in implicit init}}
+ auto [x] = ADL::X(); // expected-error {{undeclared identifier 'get'}} \
+ // expected-note {{perhaps `#include <ranges>` is needed?}} \
+ // expected-note {{in implicit init}}
}
template<typename ElemType, typename GetTypeLV, typename GetTypeRV>
diff --git a/clang/test/CXX/drs/cwg3xx.cpp b/clang/test/CXX/drs/cwg3xx.cpp
index 8b035cf6f2370..c94c478d8e3ac 100644
--- a/clang/test/CXX/drs/cwg3xx.cpp
+++ b/clang/test/CXX/drs/cwg3xx.cpp
@@ -1474,6 +1474,7 @@ namespace cwg387 { // cwg387: 2.8
a = gcd(a, b);
b = gcd(3, 4);
// expected-error at -1 {{use of undeclared identifier 'gcd'}}
+ // expected-note at -2 {{perhaps `#include <numeric>` is needed?}}
}
}
@@ -1489,6 +1490,7 @@ namespace cwg387 { // cwg387: 2.8
a = gcd(a, b);
b = gcd(3, 4);
// expected-error at -1 {{use of undeclared identifier 'gcd'}}
+ // expected-note at -2 {{perhaps `#include <numeric>` is needed?}}
}
}
} // namespace cwg387
diff --git a/clang/test/Modules/implicit-declared-allocation-functions.cppm b/clang/test/Modules/implicit-declared-allocation-functions.cppm
index b378a1d1365ee..5507e19fda83d 100644
--- a/clang/test/Modules/implicit-declared-allocation-functions.cppm
+++ b/clang/test/Modules/implicit-declared-allocation-functions.cppm
@@ -17,14 +17,14 @@ export void alloc_wrapper() {
// std::align_val_t is ill-formed unless a standard library declaration
// ([cstddef.syn], [new.syn], [std.modules]) of that name precedes
// ([basic.lookup.general]) the use of that name.
- void *b = ::operator new((std::size_t)32); // expected-error {{use of undeclared identifier 'std'}}
- void *c = ::operator new((std::size_t)32, // expected-error {{use of undeclared identifier 'std'}}
- (std::align_val_t)64); // expected-error {{use of undeclared identifier 'std'}}
+ void *b = ::operator new((std::size_t)32); // expected-error {{use of undeclared identifier 'std'}} expected-note {{perhaps `#include <cstddef>` is needed?}}
+ void *c = ::operator new((std::size_t)32, // expected-error {{use of undeclared identifier 'std'}} expected-note {{perhaps `#include <cstddef>` is needed?}}
+ (std::align_val_t)64); // expected-error {{use of undeclared identifier 'std'}} expected-note {{perhaps `#include <new>` is needed?}}
::operator delete(a);
- ::operator delete(b, (std::size_t)32); // expected-error {{use of undeclared identifier 'std'}}
- ::operator delete(c, (std::size_t)32, // expected-error {{use of undeclared identifier 'std'}}
- (std::align_val_t)64); // expected-error {{use of undeclared identifier 'std'}}
+ ::operator delete(b, (std::size_t)32); // expected-error {{use of undeclared identifier 'std'}} expected-note {{perhaps `#include <cstddef>` is needed?}}
+ ::operator delete(c, (std::size_t)32, // expected-error {{use of undeclared identifier 'std'}} expected-note {{perhaps `#include <cstddef>` is needed?}}
+ (std::align_val_t)64); // expected-error {{use of undeclared identifier 'std'}} expected-note {{perhaps `#include <new>` is needed?}}
}
//--- new
diff --git a/clang/test/Modules/macro-reexport.cpp b/clang/test/Modules/macro-reexport.cpp
index 4e825a07bd803..145b2e9c5d01b 100644
--- a/clang/test/Modules/macro-reexport.cpp
+++ b/clang/test/Modules/macro-reexport.cpp
@@ -21,13 +21,13 @@
#include "f1.h"
void f() { return assert(true); } // expected-error {{undeclared identifier 'd'}}
#include "e2.h" // undefines d1's macro
-void g() { return assert(true); } // expected-error {{undeclared identifier 'assert'}}
+void g() { return assert(true); } // expected-error {{undeclared identifier 'assert'}} expected-note {{perhaps `#include <cassert>` is needed?}}
#elif defined(D1)
#include "e1.h" // undefines c1's macro but not d1's macro
#include "d1.h"
void f() { return assert(true); } // expected-error {{undeclared identifier 'd'}}
#include "e2.h" // undefines d1's macro
-void g() { return assert(true); } // expected-error {{undeclared identifier 'assert'}}
+void g() { return assert(true); } // expected-error {{undeclared identifier 'assert'}} expected-note {{perhaps `#include <cassert>` is needed?}}
#elif defined(D2)
#include "d2.h"
void f() { return assert(true); } // expected-error {{undeclared identifier 'b'}}
@@ -35,5 +35,5 @@ void f() { return assert(true); } // expected-error {{undeclared identifier 'b'}
// e2 undefines d1's macro, which overrides c1's macro.
#include "e2.h"
#include "c1.h"
-void f() { return assert(true); } // expected-error {{undeclared identifier 'assert'}}
+void f() { return assert(true); } // expected-error {{undeclared identifier 'assert'}} expected-note {{perhaps `#include <cassert>` is needed?}}
#endif
diff --git a/clang/test/OpenMP/allocate_modifiers_messages.cpp b/clang/test/OpenMP/allocate_modifiers_messages.cpp
index 6867e78a89ee9..2e6fa37096973 100644
--- a/clang/test/OpenMP/allocate_modifiers_messages.cpp
+++ b/clang/test/OpenMP/allocate_modifiers_messages.cpp
@@ -108,6 +108,7 @@ int main() {
#pragma omp scope private(b) allocate(align
// expected-error at +1 {{duplicate modifier 'align' in 'allocate' clause}}
#pragma omp scope private(a) allocate(align(8), align(4) : a)
+ // expected-note at +6 {{perhaps `#include <memory>` is needed?}}
// expected-error at +5 {{use of undeclared identifier 'align'}}
// expected-error at +4 {{expected ',' or ')' in 'allocate' clause}}
// expected-error at +3 {{expected ')'}}
diff --git a/clang/test/Sema/builtin-setjmp.c b/clang/test/Sema/builtin-setjmp.c
index a71f87162612d..3369468a4a79d 100644
--- a/clang/test/Sema/builtin-setjmp.c
+++ b/clang/test/Sema/builtin-setjmp.c
@@ -35,11 +35,13 @@ void use(void) {
setjmp(0);
#if NO_SETJMP
// cxx-error at -2 {{undeclared identifier 'setjmp'}}
- // c-error at -3 {{call to undeclared function 'setjmp'; ISO C99 and later do not support implicit function declarations}}
+ // cxx-note at -3 {{perhaps `#include <csetjmp>` is needed?}}
+ // c-error at -4 {{call to undeclared function 'setjmp'; ISO C99 and later do not support implicit function declarations}}
#elif ONLY_JMP_BUF
- // cxx-error at -5 {{undeclared identifier 'setjmp'}}
- // c-error at -6 {{call to undeclared library function 'setjmp' with type 'int (jmp_buf)' (aka 'int (int *)'); ISO C99 and later do not support implicit function declarations}}
- // c-note at -7 {{include the header <setjmp.h> or explicitly provide a declaration for 'setjmp'}}
+ // cxx-error at -6 {{undeclared identifier 'setjmp'}}
+ // cxx-note at -7 {{perhaps `#include <csetjmp>` is needed?}}
+ // c-error at -8 {{call to undeclared library function 'setjmp' with type 'int (jmp_buf)' (aka 'int (int *)'); ISO C99 and later do not support implicit function declarations}}
+ // c-note at -9 {{include the header <setjmp.h> or explicitly provide a declaration for 'setjmp'}}
#else
// cxx-no-diagnostics
#endif
diff --git a/clang/test/Sema/note-suggest-header.cpp b/clang/test/Sema/note-suggest-header.cpp
new file mode 100644
index 0000000000000..7444763224903
--- /dev/null
+++ b/clang/test/Sema/note-suggest-header.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+void f(void) {
+ int_val2 = 0; // expected-error{{use of undeclared identifier}}
+ sin(0); // expected-error{{use of undeclared identifier 'sin'}} \
+ // expected-note{{perhaps `#include <cmath>` is needed?}}
+
+ std::cout << "Hello world\n"; // expected-error{{use of undeclared identifier 'std'}} \
+ // expected-note{{perhaps `#include <iostream>` is needed?}}
+}
\ No newline at end of file
diff --git a/clang/test/SemaCXX/no-implicit-builtin-decls.cpp b/clang/test/SemaCXX/no-implicit-builtin-decls.cpp
index d82f7f18bffe0..d848c9740dc6e 100644
--- a/clang/test/SemaCXX/no-implicit-builtin-decls.cpp
+++ b/clang/test/SemaCXX/no-implicit-builtin-decls.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
void f() {
- void *p = malloc(sizeof(int) * 10); // expected-error{{use of undeclared identifier 'malloc'}}
+ void *p = malloc(sizeof(int) * 10); // expected-error{{use of undeclared identifier 'malloc'}} expected-note {{perhaps `#include <cstdlib>` is needed?}}
}
int malloc(double);
``````````
</details>
https://github.com/llvm/llvm-project/pull/140247
More information about the cfe-commits
mailing list