Skip to content

Commit 42bc73a

Browse files
committed
When we see a '<' operator, check whether it's a probable typo for a template-id.
The heuristic that we use here is: * the left-hand side must be a simple identifier or a class member access * the right-hand side must be '<' followed by either a '>' or by a type-id that cannot be an expression (in particular, not followed by '(' or '{') * there is a '>' token matching the '<' token The second condition guarantees the expression would otherwise be ill-formed. If we're confident that the user intended the name before the '<' to be interpreted as a template, diagnose the fact that we didn't interpret it that way, rather than diagnosing that the template arguments are not valid expressions. llvm-svn: 302615
1 parent 9fc0ba9 commit 42bc73a

File tree

7 files changed

+187
-4
lines changed

7 files changed

+187
-4
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8191,6 +8191,15 @@ def err_undeclared_var_use_suggest : Error<
81918191
def err_no_template_suggest : Error<"no template named %0; did you mean %1?">;
81928192
def err_no_member_template_suggest : Error<
81938193
"no template named %0 in %1; did you mean %select{|simply }2%3?">;
8194+
def err_non_template_in_template_id : Error<
8195+
"%0 does not name a template but is followed by template arguments">;
8196+
def err_non_template_in_template_id_suggest : Error<
8197+
"%0 does not name a template but is followed by template arguments; "
8198+
"did you mean %1?">;
8199+
def err_non_template_in_member_template_id_suggest : Error<
8200+
"member %0 of %1 is not a template; did you mean %select{|simply }2%3?">;
8201+
def note_non_template_in_template_id_found : Note<
8202+
"non-template declaration found by name lookup">;
81948203
def err_mem_init_not_member_or_class_suggest : Error<
81958204
"initializer %0 does not name a non-static data member or base "
81968205
"class; did you mean the %select{base class|member}1 %2?">;

clang/include/clang/Parse/Parser.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,8 @@ class Parser : public CodeCompletionHandler {
14881488
K == tok::plusplus || K == tok::minusminus);
14891489
}
14901490

1491+
bool diagnoseUnknownTemplateId(ExprResult TemplateName, SourceLocation Less);
1492+
14911493
ExprResult ParsePostfixExpressionSuffix(ExprResult LHS);
14921494
ExprResult ParseUnaryExprOrTypeTraitExpression();
14931495
ExprResult ParseBuiltinPrimaryExpression();

clang/include/clang/Sema/Sema.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,23 @@ class Sema {
17381738
TemplateNameKindForDiagnostics
17391739
getTemplateNameKindForDiagnostics(TemplateName Name);
17401740

1741+
/// Determine whether it's plausible that E was intended to be a
1742+
/// template-name.
1743+
bool mightBeIntendedToBeTemplateName(ExprResult E) {
1744+
if (!getLangOpts().CPlusPlus || E.isInvalid())
1745+
return false;
1746+
if (auto *DRE = dyn_cast<DeclRefExpr>(E.get()))
1747+
return !DRE->hasExplicitTemplateArgs();
1748+
if (auto *ME = dyn_cast<MemberExpr>(E.get()))
1749+
return !ME->hasExplicitTemplateArgs();
1750+
// Any additional cases recognized here should also be handled by
1751+
// diagnoseExprIntendedAsTemplateName.
1752+
return false;
1753+
}
1754+
void diagnoseExprIntendedAsTemplateName(Scope *S, ExprResult TemplateName,
1755+
SourceLocation Less,
1756+
SourceLocation Greater);
1757+
17411758
Decl *ActOnDeclarator(Scope *S, Declarator &D);
17421759

17431760
NamedDecl *HandleDeclarator(Scope *S, Declarator &D,

clang/lib/Parse/ParseExpr.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,30 @@ bool Parser::isNotExpressionStart() {
235235
return isKnownToBeDeclarationSpecifier();
236236
}
237237

238+
/// We've parsed something that could plausibly be intended to be a template
239+
/// name (\p LHS) followed by a '<' token, and the following code can't possibly
240+
/// be an expression. Determine if this is likely to be a template-id and if so,
241+
/// diagnose it.
242+
bool Parser::diagnoseUnknownTemplateId(ExprResult LHS, SourceLocation Less) {
243+
TentativeParsingAction TPA(*this);
244+
// FIXME: We could look at the token sequence in a lot more detail here.
245+
if (SkipUntil(tok::greater, tok::greatergreater, tok::greatergreatergreater,
246+
StopAtSemi | StopBeforeMatch)) {
247+
TPA.Commit();
248+
249+
SourceLocation Greater;
250+
ParseGreaterThanInTemplateList(Greater, true, false);
251+
Actions.diagnoseExprIntendedAsTemplateName(getCurScope(), LHS,
252+
Less, Greater);
253+
return true;
254+
}
255+
256+
// There's no matching '>' token, this probably isn't supposed to be
257+
// interpreted as a template-id. Parse it as an (ill-formed) comparison.
258+
TPA.Revert();
259+
return false;
260+
}
261+
238262
static bool isFoldOperator(prec::Level Level) {
239263
return Level > prec::Unknown && Level != prec::Conditional;
240264
}
@@ -276,6 +300,16 @@ Parser::ParseRHSOfBinaryExpression(ExprResult LHS, prec::Level MinPrec) {
276300
return LHS;
277301
}
278302

303+
// If a '<' token is followed by a type that can be a template argument and
304+
// cannot be an expression, then this is ill-formed, but might be intended
305+
// to be a template-id.
306+
if (OpToken.is(tok::less) && Actions.mightBeIntendedToBeTemplateName(LHS) &&
307+
(isKnownToBeDeclarationSpecifier() ||
308+
Tok.isOneOf(tok::greater, tok::greatergreater,
309+
tok::greatergreatergreater)) &&
310+
diagnoseUnknownTemplateId(LHS, OpToken.getLocation()))
311+
return ExprError();
312+
279313
// If the next token is an ellipsis, then this is a fold-expression. Leave
280314
// it alone so we can handle it in the paren expression.
281315
if (isFoldOperator(NextTokPrec) && Tok.is(tok::ellipsis)) {

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,85 @@ void Sema::LookupTemplateName(LookupResult &Found,
455455
}
456456
}
457457

458+
void Sema::diagnoseExprIntendedAsTemplateName(Scope *S, ExprResult TemplateName,
459+
SourceLocation Less,
460+
SourceLocation Greater) {
461+
if (TemplateName.isInvalid())
462+
return;
463+
464+
DeclarationNameInfo NameInfo;
465+
CXXScopeSpec SS;
466+
LookupNameKind LookupKind;
467+
468+
DeclContext *LookupCtx = nullptr;
469+
NamedDecl *Found = nullptr;
470+
471+
// Figure out what name we looked up.
472+
if (auto *ME = dyn_cast<MemberExpr>(TemplateName.get())) {
473+
NameInfo = ME->getMemberNameInfo();
474+
SS.Adopt(ME->getQualifierLoc());
475+
LookupKind = LookupMemberName;
476+
LookupCtx = ME->getBase()->getType()->getAsCXXRecordDecl();
477+
Found = ME->getMemberDecl();
478+
} else {
479+
auto *DRE = cast<DeclRefExpr>(TemplateName.get());
480+
NameInfo = DRE->getNameInfo();
481+
SS.Adopt(DRE->getQualifierLoc());
482+
LookupKind = LookupOrdinaryName;
483+
Found = DRE->getFoundDecl();
484+
}
485+
486+
// Try to correct the name by looking for templates and C++ named casts.
487+
struct TemplateCandidateFilter : CorrectionCandidateCallback {
488+
TemplateCandidateFilter() {
489+
WantTypeSpecifiers = false;
490+
WantExpressionKeywords = false;
491+
WantRemainingKeywords = false;
492+
WantCXXNamedCasts = true;
493+
};
494+
bool ValidateCandidate(const TypoCorrection &Candidate) override {
495+
if (auto *ND = Candidate.getCorrectionDecl())
496+
return isAcceptableTemplateName(ND->getASTContext(), ND, true);
497+
return Candidate.isKeyword();
498+
}
499+
};
500+
501+
DeclarationName Name = NameInfo.getName();
502+
if (TypoCorrection Corrected =
503+
CorrectTypo(NameInfo, LookupKind, S, &SS,
504+
llvm::make_unique<TemplateCandidateFilter>(),
505+
CTK_ErrorRecovery, LookupCtx)) {
506+
auto *ND = Corrected.getFoundDecl();
507+
if (ND)
508+
ND = isAcceptableTemplateName(Context, ND,
509+
/*AllowFunctionTemplates*/ true);
510+
if (ND || Corrected.isKeyword()) {
511+
if (LookupCtx) {
512+
std::string CorrectedStr(Corrected.getAsString(getLangOpts()));
513+
bool DroppedSpecifier = Corrected.WillReplaceSpecifier() &&
514+
Name.getAsString() == CorrectedStr;
515+
diagnoseTypo(Corrected,
516+
PDiag(diag::err_non_template_in_member_template_id_suggest)
517+
<< Name << LookupCtx << DroppedSpecifier
518+
<< SS.getRange());
519+
} else {
520+
diagnoseTypo(Corrected,
521+
PDiag(diag::err_non_template_in_template_id_suggest)
522+
<< Name);
523+
}
524+
if (Found)
525+
Diag(Found->getLocation(),
526+
diag::note_non_template_in_template_id_found);
527+
return;
528+
}
529+
}
530+
531+
Diag(NameInfo.getLoc(), diag::err_non_template_in_template_id)
532+
<< Name << SourceRange(Less, Greater);
533+
if (Found)
534+
Diag(Found->getLocation(), diag::note_non_template_in_template_id_found);
535+
}
536+
458537
/// ActOnDependentIdExpression - Handle a dependent id-expression that
459538
/// was just parsed. This is only possible with an explicit scope
460539
/// specifier naming a dependent type.

clang/test/SemaCXX/cxx1y-variable-templates_top_level.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#endif
1010

1111
template<typename T>
12-
T pi = T(3.1415926535897932385); // expected-note {{template is declared here}}
12+
T pi = T(3.1415926535897932385); // expected-note 2{{declared here}}
1313

1414
template<typename T>
1515
CONST T cpi = T(3.1415926535897932385); // expected-note {{template is declared here}}
@@ -58,10 +58,9 @@ namespace use_in_top_level_funcs {
5858
namespace shadow {
5959
void foo() {
6060
int ipi0 = pi<int>;
61-
int pi;
61+
int pi; // expected-note {{found}}
6262
int a = pi;
63-
int ipi = pi<int>; // expected-error {{expected '(' for function-style cast or type construction}} \
64-
// expected-error {{expected expression}}
63+
int ipi = pi<int>; // expected-error {{'pi' does not name a template but is followed by template arguments; did you mean '::pi'?}}
6564
}
6665
}
6766

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %clang_cc1 -std=c++1z %s -verify -Wno-unused
2+
3+
namespace InExpr {
4+
namespace A {
5+
void typo_first_a(); // expected-note {{found}}
6+
template<typename T> void typo_first_b(); // expected-note 2{{declared here}}
7+
}
8+
void testA() { A::typo_first_a<int>(); } // expected-error {{'typo_first_a' does not name a template but is followed by template arguments; did you mean 'typo_first_b'?}}
9+
10+
namespace B {
11+
void typo_first_b(); // expected-note {{found}}
12+
}
13+
void testB() { B::typo_first_b<int>(); } // expected-error {{'typo_first_b' does not name a template but is followed by template arguments; did you mean 'A::typo_first_b'?}}
14+
15+
struct Base {
16+
template<typename T> static void foo(); // expected-note 4{{declared here}}
17+
int n;
18+
};
19+
struct Derived : Base {
20+
void foo(); // expected-note {{found}}
21+
};
22+
// We probably don't want to suggest correcting to .Base::foo<int>
23+
void testMember() { Derived().foo<int>(); } // expected-error-re {{does not name a template but is followed by template arguments{{$}}}}
24+
25+
struct Derived2 : Base {
26+
void goo(); // expected-note {{found}}
27+
};
28+
void testMember2() { Derived2().goo<int>(); } // expected-error {{member 'goo' of 'InExpr::Derived2' is not a template; did you mean 'foo'?}}
29+
30+
void no_correction() {
31+
int foo; // expected-note 3{{found}}
32+
33+
foo<int>(); // expected-error {{'foo' does not name a template but is followed by template arguments; did you mean 'Base::foo'?}}
34+
foo<>(); // expected-error {{'foo' does not name a template but is followed by template arguments; did you mean 'Base::foo'?}}
35+
foo<Base *>(); // expected-error {{'foo' does not name a template but is followed by template arguments; did you mean 'Base::foo'?}}
36+
37+
// These are valid expressions.
38+
foo<foo; // expected-warning {{self-comparison}}
39+
foo<int()>(0);
40+
foo<int(), true>(false);
41+
foo<Base{}.n;
42+
}
43+
}

0 commit comments

Comments
 (0)