diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 50d3bbbc97e91..c37c3d586de7b 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -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 ---------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index bcae9e9f30093..bafc2308f522f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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< @@ -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">; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 3cd4010740d19..c3b5c705178a1 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -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, @@ -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; @@ -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()) { diff --git a/clang/test/Preprocessor/macro_with_initializer_list.cpp b/clang/test/Preprocessor/macro_with_initializer_list.cpp index 40f53164b263d..d9e653106d4a1 100644 --- a/clang/test/Preprocessor/macro_with_initializer_list.cpp +++ b/clang/test/Preprocessor/macro_with_initializer_list.cpp @@ -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. @@ -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()); @@ -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}} } diff --git a/clang/test/Sema/typo-correction.c b/clang/test/Sema/typo-correction.c index 4157207a9ac42..e000ecdf234e3 100644 --- a/clang/test/Sema/typo-correction.c +++ b/clang/test/Sema/typo-correction.c @@ -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(...)'?}} +}