Skip to content

[Clang] suggest headers on undeclared errors #140247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">,
Expand Down
30 changes: 30 additions & 0 deletions clang/lib/Parse/ParseTentative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a layering violation, though not a circular dependency. Parser currently does not link against tooling. In your patch, you link tooling into Sema which Parser then picks up transitively. Tooling does not currently link in Parser or Sema, hence no circular dependency.

I kind of think the standard library stuff needs to be lifted into AST. It does seem to rely on Decl.h internally, so it can't be lifted into Basic.

CC @erichkeane for other opinions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but we want to rewrite anyway. The implementation (StandardLibrary.cpp) seems fairly inefficient. We should try to TableGen a trie and store version information in there, have some support for typo correction, etc


using namespace clang;

/// isCXXDeclarationStatement - C++-specialized function that disambiguates
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,5 @@ add_clang_library(clangSema
clangEdit
clangLex
clangSupport
)
clangToolingInclusionsStdlib
)
17 changes: 17 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}

Expand Down
12 changes: 9 additions & 3 deletions clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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?}} \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<ranges> is far from the only header that provides overloads of std::get, but probably the heaviest one for compile times. Also, this code is run in C++17 mode, and there's no <ranges> header in C++17.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this does not seem helpful, it feels actively harmful actually.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh dear. StdSpecialSymbolMap doesn't even specify the namespace corresponding to get(). Thanks. I didn't know there was such a point.

// 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}}
Expand Down Expand Up @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions clang/test/CXX/drs/cwg3xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,7 @@ namespace cwg387 { // cwg387: 2.8
a = gcd(a, b);
b = gcd(3, 4);
// expected-error@-1 {{use of undeclared identifier 'gcd'}}
// expected-note@-2 {{perhaps `#include <numeric>` is needed?}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// expected-note@-2 {{perhaps `#include <numeric>` is needed?}}
// expected-note@-2 {{perhaps `#include <numeric>` is needed?}}

}
}

Expand All @@ -1489,6 +1490,7 @@ namespace cwg387 { // cwg387: 2.8
a = gcd(a, b);
b = gcd(3, 4);
// expected-error@-1 {{use of undeclared identifier 'gcd'}}
// expected-note@-2 {{perhaps `#include <numeric>` is needed?}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// expected-note@-2 {{perhaps `#include <numeric>` is needed?}}
// expected-note@-2 {{perhaps `#include <numeric>` is needed?}}

}
}
} // namespace cwg387
Expand Down
12 changes: 6 additions & 6 deletions clang/test/Modules/implicit-declared-allocation-functions.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions clang/test/Modules/macro-reexport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@
#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'}}
#else
// 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
1 change: 1 addition & 0 deletions clang/test/OpenMP/allocate_modifiers_messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ int main() {
#pragma omp scope private(b) allocate(align
// expected-error@+1 {{duplicate modifier 'align' in 'allocate' clause}}
#pragma omp scope private(a) allocate(align(8), align(4) : a)
// expected-note@+6 {{perhaps `#include <memory>` is needed?}}
// expected-error@+5 {{use of undeclared identifier 'align'}}
// expected-error@+4 {{expected ',' or ')' in 'allocate' clause}}
// expected-error@+3 {{expected ')'}}
Expand Down
10 changes: 6 additions & 4 deletions clang/test/Sema/builtin-setjmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ void use(void) {
setjmp(0);
#if NO_SETJMP
// cxx-error@-2 {{undeclared identifier 'setjmp'}}
// c-error@-3 {{call to undeclared function 'setjmp'; ISO C99 and later do not support implicit function declarations}}
// cxx-note@-3 {{perhaps `#include <csetjmp>` is needed?}}
// c-error@-4 {{call to undeclared function 'setjmp'; ISO C99 and later do not support implicit function declarations}}
#elif ONLY_JMP_BUF
// cxx-error@-5 {{undeclared identifier 'setjmp'}}
// c-error@-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@-7 {{include the header <setjmp.h> or explicitly provide a declaration for 'setjmp'}}
// cxx-error@-6 {{undeclared identifier 'setjmp'}}
// cxx-note@-7 {{perhaps `#include <csetjmp>` is needed?}}
// c-error@-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@-9 {{include the header <setjmp.h> or explicitly provide a declaration for 'setjmp'}}
#else
// cxx-no-diagnostics
#endif
Expand Down
10 changes: 10 additions & 0 deletions clang/test/Sema/note-suggest-header.cpp
Original file line number Diff line number Diff line change
@@ -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?}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note sounds like <iostream> provides std, which doesn't feel right.

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an empty newline at the end

2 changes: 1 addition & 1 deletion clang/test/SemaCXX/no-implicit-builtin-decls.cpp
Original file line number Diff line number Diff line change
@@ -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);
Loading