Skip to content

[clang][Sema] Add diagnostic note for reference of function-like macros requiring without parentheses #123495

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 12 commits 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
4 changes: 3 additions & 1 deletion clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ Improvements to Clang's diagnostics
which are supposed to only exist once per program, but may get duplicated when
built into a shared library.
- Fixed a bug where Clang's Analysis did not correctly model the destructor behavior of ``union`` members (#GH119415).

- Clang now provides a diagnostic note for function-like macros that are
missing the required parentheses (#GH123038).

Improvements to Clang's time-trace
----------------------------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5961,6 +5961,8 @@ def err_fold_expression_limit_exceeded: Error<
"instantiating fold expression with %0 arguments exceeded expression nesting "
"limit of %1">, DefaultFatal, NoSFINAE;

def note_function_like_macro_requires_parens
: Note<"'%0' defined here as a function-like macro">;
def err_unexpected_typedef : Error<
"unexpected type name %0: expected expression">;
def err_unexpected_namespace : Error<
Expand Down Expand Up @@ -10855,6 +10857,8 @@ def err_undeclared_use_suggest : Error<
"use of undeclared %0; did you mean %1?">;
def err_undeclared_var_use_suggest : Error<
"use of undeclared identifier %0; did you mean %1?">;
def err_undeclared_var_use_suggest_func_like_macro : Error<
"'%0' is defined as an object-like macro; did you mean '%0(...)'?">;
def err_no_template : Error<"no template named %0">;
def err_no_template_suggest : Error<"no template named %0; did you mean %1?">;
def err_no_member_template : Error<"no template named %0 in %1">;
Expand Down
27 changes: 27 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2347,6 +2347,28 @@ Sema::BuildDeclRefExpr(ValueDecl *D, QualType Ty, ExprValueKind VK,
return E;
}

// Diagnose when a macro cannot be expanded because it's a function-like macro
// being used as an object-like macro. Returns true if a diagnostic is emitted.
static bool diagnoseFunctionLikeMacro(Sema &SemaRef, DeclarationName Name,
SourceLocation TypoLoc) {

if (IdentifierInfo *II = Name.getAsIdentifierInfo()) {
if (II->hasMacroDefinition()) {
MacroInfo *MI = SemaRef.PP.getMacroInfo(II);
if (MI && MI->isFunctionLike()) {
SemaRef.Diag(TypoLoc,
diag::err_undeclared_var_use_suggest_func_like_macro)
<< II ->getName();
SemaRef.Diag(MI->getDefinitionLoc(),
diag::note_function_like_macro_requires_parens)
<< II->getName();
return true;
}
}
}
return false;
}

void
Sema::DecomposeUnqualifiedId(const UnqualifiedId &Id,
TemplateArgumentListInfo &Buffer,
Expand Down Expand Up @@ -2382,6 +2404,8 @@ static void emitEmptyLookupTypoDiagnostic(
if (Ctx)
SemaRef.Diag(TypoLoc, diag::err_no_member) << Typo << Ctx
<< SS.getRange();
else if (diagnoseFunctionLikeMacro(SemaRef, Typo, TypoLoc))
return;
else
SemaRef.Diag(TypoLoc, DiagnosticID) << Typo;
return;
Expand Down Expand Up @@ -2624,6 +2648,9 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,
}
R.clear();

if (diagnoseFunctionLikeMacro(SemaRef, Name, R.getNameLoc()))
return true;

// Emit a special diagnostic for failed member lookups.
// FIXME: computing the declaration context might fail here (?)
if (!SS.isEmpty()) {
Expand Down
14 changes: 8 additions & 6 deletions clang/test/Preprocessor/macro_with_initializer_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ void test_NE() {
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{110:9-110:9}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{110:32-110:32}:")"

#define INIT(var, init) Foo var = init; // expected-note 3{{defined here}}
#define INIT(var, init) Foo var = init; // expected-note 3{{macro 'INIT' defined here}}
// expected-note@-1 2{{'INIT' defined here as a function-like macro}}
// Can't use an initializer list as a macro argument. The commas in the list
// will be interpretted as argument separaters and adding parenthesis will
// make it no longer an initializer list.
Expand All @@ -149,22 +150,23 @@ void test() {
// Can't be fixed by parentheses.
INIT(e, {1, 2, 3});
// expected-error@-1 {{too many arguments provided}}
// expected-error@-2 {{use of undeclared identifier}}
// expected-error@-2 {{'INIT' is defined as an object-like macro; did you mean 'INIT(...)'?}}
// expected-note@-3 {{cannot use initializer list at the beginning of a macro argument}}

// Can't be fixed by parentheses.
INIT(e, {1, 2, 3} + {1, 2, 3});
// expected-error@-1 {{too many arguments provided}}
// expected-error@-2 {{use of undeclared identifier}}
// expected-error@-2 {{'INIT' is defined as an object-like macro; did you mean 'INIT(...)'?}}
// expected-note@-3 {{cannot use initializer list at the beginning of a macro argument}}
}

// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{145:11-145:11}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{145:23-145:23}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{146:11-146:11}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{146:23-146:23}:")"

#define M(name,a,b,c,d,e,f,g,h,i,j,k,l) \
Foo name = a + b + c + d + e + f + g + h + i + j + k + l;
// expected-note@-2 2{{defined here}}
// expected-note@-3 {{'M' defined here as a function-like macro}}
void test2() {
M(F1, Foo(), Foo(), Foo(), Foo(), Foo(), Foo(),
Foo(), Foo(), Foo(), Foo(), Foo(), Foo());
Expand All @@ -177,6 +179,6 @@ void test2() {
M(F3, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3},
{1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3});
// expected-error@-2 {{too many arguments provided}}
// expected-error@-3 {{use of undeclared identifier}}
// expected-error@-3 {{'M' is defined as an object-like macro; did you mean 'M(...)'?}}
// expected-note@-4 {{cannot use initializer list at the beginning of a macro argument}}
}
22 changes: 22 additions & 0 deletions clang/test/Sema/typo-correction.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,25 @@ void PR40286_3(int the_value) {
void PR40286_4(int the_value) { // expected-note {{'the_value' declared here}}
PR40286_h(the_value, the_value, the_walue); // expected-error {{use of undeclared identifier 'the_walue'; did you mean 'the_value'?}}
}

#define FOO1() 10
// expected-note@-1 4 {{'FOO1' defined here as a function-like macro}}

int x = FOO1; // expected-error {{'FOO1' is defined as an object-like macro; did you mean 'FOO1(...)'?}}

void test3() {
int iter = FOO1;
// expected-error@-1 {{'FOO1' is defined as an object-like macro; did you mean 'FOO1(...)'?}}
}

void bar(int);

void test4() {
int FOO; // expected-note {{'FOO' declared here}}
int x = FOO1; // expected-error {{use of undeclared identifier 'FOO1'; did you mean 'FOO'?}}
}

void test5() {
FOO1 + 1; // expected-error {{'FOO1' is defined as an object-like macro; did you mean 'FOO1(...)'?}}
bar(FOO1); // expected-error {{'FOO1' is defined as an object-like macro; did you mean 'FOO1(...)'?}}
}