Skip to content

Commit f5e2f19

Browse files
committed
[clang] Emit unified error for 'auto' combined with type specifiers
This change improves the diagnostic when 'auto' is combined with a type specifier by emitting a unified error message "'auto' cannot be combined with a type specifier" instead of the generic "cannot combine with previous 'auto' declaration specifier" error. The parser now checks for: 1. Builtin types (int, void, char, etc.) after 'auto' using lookahead 2. Identifier types (typedefs, template parameters, etc.) using semantic lookup via Actions.getTypeName() To avoid false positives, the check is limited to: - Simple declaration contexts (DSC_normal, DSC_top_level, DSC_class) - Simple variable declarations: 'auto Type variable_name' pattern - Skips complex declarators (function pointers, member pointers, qualified names, etc.) - Skips ambiguous cases like 'auto Type = value' where Type could be a variable name For builtin types, the parser emits the error and exits early. For identifier types, it uses semantic lookup to determine if the identifier is a known type before emitting the error. Test updates: - Updated cwg3xx.cpp to account for C++98 vs C++11 behavior differences - Updated p2.cpp to include expected warning for empty parentheses in block scope Fixes incorrect diagnostics for cases like: auto int x; // Now correctly diagnosed auto Ty x; // Now correctly diagnosed (Ty is a type) auto S<T>::f(); // Correctly skipped (out-of-line definition) auto Class::* // Correctly skipped (member pointer) Signed-off-by: Osama Abdelkader <[email protected]>
1 parent 88511de commit f5e2f19

File tree

4 files changed

+89
-12
lines changed

4 files changed

+89
-12
lines changed

clang/lib/Parse/ParseDecl.cpp

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4092,24 +4092,23 @@ void Parser::ParseDeclarationSpecifiers(
40924092
PrevSpec, DiagID, Policy);
40934093
isStorageClass = true;
40944094
break;
4095-
case tok::kw_auto:
4096-
if (isKnownToBeTypeSpecifier(GetLookAheadToken(1))) {
4095+
case tok::kw_auto: {
4096+
const Token &LA = GetLookAheadToken(1);
4097+
if (isKnownToBeTypeSpecifier(LA) || LA.is(tok::annot_typename)) {
40974098
// 'auto' cannot be combined with a type specifier, except in C23 and
40984099
// C++98.
4099-
if (getLangOpts().C23) {
4100-
// C23 allows 'auto' as storage class with type specifier.
4101-
isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
4102-
PrevSpec, DiagID, Policy);
4103-
} else if (getLangOpts().CPlusPlus11 || getLangOpts().OpenCL) {
4100+
if (getLangOpts().CPlusPlus11 || getLangOpts().OpenCL) {
41044101
// In C++11+ or OpenCL, 'auto' cannot be combined with a type
41054102
// specifier.
41064103
isInvalid = true;
41074104
PrevSpec = Tok.getIdentifierInfo()->getNameStart();
41084105
DiagID = diag::err_auto_type_specifier;
4106+
DS.SetTypeSpecError();
41094107
} else {
41104108
// In C++98 or C, 'auto' can be a storage class specifier with a type.
41114109
isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
41124110
PrevSpec, DiagID, Policy);
4111+
isStorageClass = true;
41134112
}
41144113
} else {
41154114
// 'auto' is not followed by a type specifier.
@@ -4121,10 +4120,78 @@ void Parser::ParseDeclarationSpecifiers(
41214120
// In C (not C++11+ and not C23), 'auto' is a storage class specifier.
41224121
isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
41234122
PrevSpec, DiagID, Policy);
4123+
isStorageClass = true;
41244124
}
41254125
}
4126-
isStorageClass = true;
4127-
break;
4126+
// Only if Next token is a known type name (typedef, template param, etc
4127+
// ) Check this only if we set 'auto' as a type specifier and haven't
4128+
// already detected an error. Only check in simple declaration contexts
4129+
// to avoid false positives with member pointers, out-of-line
4130+
// definitions, etc. Only check when we're sure it's a simple variable
4131+
// declaration: 'auto Type variable_name' where the identifier is
4132+
// followed by another identifier (the variable name), not by '(', '::',
4133+
// '<', '[', etc.
4134+
if (getLangOpts().CPlusPlus11 && !isInvalid &&
4135+
DS.getTypeSpecType() == DeclSpec::TST_auto &&
4136+
(DSContext == DeclSpecContext::DSC_normal ||
4137+
DSContext == DeclSpecContext::DSC_top_level ||
4138+
DSContext == DeclSpecContext::DSC_class)) {
4139+
// Re-check the lookahead token in case it was annotated
4140+
const Token &NextLA = GetLookAheadToken(1);
4141+
4142+
// Check for builtin type keywords (int, void, char, etc.)
4143+
if (NextLA.isOneOf(tok::kw_int, tok::kw_void, tok::kw_char,
4144+
tok::kw_char8_t, tok::kw_char16_t, tok::kw_char32_t,
4145+
tok::kw_wchar_t, tok::kw_bool, tok::kw_float,
4146+
tok::kw_double, tok::kw__Bool, tok::kw___bool,
4147+
tok::kw_half, tok::kw___bf16, tok::kw__ExtInt,
4148+
tok::kw__BitInt)) {
4149+
// Builtin type after 'auto' => always an error in C++11+
4150+
Diag(NextLA.getLocation(), diag::err_auto_type_specifier);
4151+
4152+
DS.SetTypeSpecError();
4153+
ConsumeToken(); // Consume 'auto'
4154+
ConsumeToken(); // Consume the builtin type token
4155+
goto DoneWithDeclSpec;
4156+
}
4157+
4158+
// Check for identifier types
4159+
if (NextLA.is(tok::identifier)) {
4160+
// Check what comes after the identifier to determine if this is a
4161+
// simple variable declaration or something more complex.
4162+
const Token &AfterNext = GetLookAheadToken(2);
4163+
// Skip if followed by: '(', '::', '<', '[', '*', '&', etc.
4164+
// Only check if followed by another identifier (variable name).
4165+
// Don't check if followed by '=' because 'auto Type = value' is
4166+
// ambiguous: Type could be a variable name that happens to share a
4167+
// name with a type.
4168+
if (AfterNext.isOneOf(tok::l_paren, tok::coloncolon, tok::less,
4169+
tok::l_square, tok::star, tok::amp, tok::ampamp,
4170+
tok::kw_const, tok::kw_volatile,
4171+
tok::kw_restrict, tok::equal)) {
4172+
// Complex declarator or ambiguous case - skip the check
4173+
} else if (AfterNext.is(tok::identifier)) {
4174+
// This looks like a simple variable declaration: 'auto Type name'
4175+
// Query Sema: Is this identifier a known-type at this point?
4176+
IdentifierInfo *II = NextLA.getIdentifierInfo();
4177+
ParsedType TypeRep = Actions.getTypeName(
4178+
*II, NextLA.getLocation(), getCurScope(), nullptr, false, false,
4179+
nullptr, false, false,
4180+
isClassTemplateDeductionContext(DSContext));
4181+
4182+
if (TypeRep) {
4183+
// We have 'auto' followed by a known type => always an error in
4184+
// C++11+
4185+
Diag(NextLA.getLocation(), diag::err_auto_type_specifier);
4186+
DS.SetTypeSpecError();
4187+
ConsumeToken(); // Consume 'auto'
4188+
ConsumeToken(); // Consume the type name token
4189+
goto DoneWithDeclSpec;
4190+
}
4191+
}
4192+
}
4193+
}
4194+
} break;
41284195
case tok::kw___auto_type:
41294196
Diag(Tok, diag::ext_auto_type);
41304197
isInvalid = DS.SetTypeSpecType(DeclSpec::TST_auto_type, Loc, PrevSpec,

clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p2.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ void foo(auto int ap, register int rp) {
6161
auto void abf();
6262
#if __cplusplus >= 201103L // C++11 or later
6363
// expected-error@-2 {{'auto' cannot be combined with a type specifier}}
64+
// expected-warning@-3 {{empty parentheses interpreted as a function declaration}}
65+
// expected-note@-4 {{replace parentheses with an initializer to declare a variable}}
6466
#else
65-
// expected-error@-4 {{illegal storage class on function}}
67+
// expected-error@-6 {{illegal storage class on function}}
6668
#endif
6769

6870
register int rbo;

clang/test/CXX/drs/cwg3xx.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,8 +1739,8 @@ namespace cwg396 { // cwg396: 3.0
17391739
int (i); // #cwg396-i
17401740
auto int (i);
17411741
// since-cxx11-error@-1 {{'auto' cannot be combined with a type specifier}}
1742-
// expected-error@-2 {{redefinition of 'i'}}
1743-
// expected-note@#cwg396-i {{previous definition is here}}
1742+
// cxx98-error@-2 {{redefinition of 'i'}}
1743+
// cxx98-note@#cwg396-i {{previous definition is here}}
17441744
}
17451745
} // namespace cwg396
17461746

clang/test/Parser/cxx-auto-type-specifier.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ void h() {
2020
auto z = 'c';
2121
}
2222

23+
template <typename Ty>
24+
void g() {
25+
auto Ty x; // expected-error {{'auto' cannot be combined with a type specifier}}
26+
}
27+
28+
void test() {
29+
g<float>();
30+
}

0 commit comments

Comments
 (0)