From: Richard Smith Date: Wed, 10 May 2017 02:30:28 +0000 (+0000) Subject: When we see a '<' operator, check whether it's a probable typo for a template-id. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=327d010bc81cbf3656ac46faa24109c7c0f1f624;p=clang 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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@302615 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 1f3815984c..07bb0bf694 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -8191,6 +8191,15 @@ def err_undeclared_var_use_suggest : Error< def err_no_template_suggest : Error<"no template named %0; did you mean %1?">; def err_no_member_template_suggest : Error< "no template named %0 in %1; did you mean %select{|simply }2%3?">; +def err_non_template_in_template_id : Error< + "%0 does not name a template but is followed by template arguments">; +def err_non_template_in_template_id_suggest : Error< + "%0 does not name a template but is followed by template arguments; " + "did you mean %1?">; +def err_non_template_in_member_template_id_suggest : Error< + "member %0 of %1 is not a template; did you mean %select{|simply }2%3?">; +def note_non_template_in_template_id_found : Note< + "non-template declaration found by name lookup">; def err_mem_init_not_member_or_class_suggest : Error< "initializer %0 does not name a non-static data member or base " "class; did you mean the %select{base class|member}1 %2?">; diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index 8d0935dec1..f7a5bb5ed1 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -1488,6 +1488,8 @@ private: K == tok::plusplus || K == tok::minusminus); } + bool diagnoseUnknownTemplateId(ExprResult TemplateName, SourceLocation Less); + ExprResult ParsePostfixExpressionSuffix(ExprResult LHS); ExprResult ParseUnaryExprOrTypeTraitExpression(); ExprResult ParseBuiltinPrimaryExpression(); diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index c17660669e..4faf2a3605 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -1738,6 +1738,23 @@ public: TemplateNameKindForDiagnostics getTemplateNameKindForDiagnostics(TemplateName Name); + /// Determine whether it's plausible that E was intended to be a + /// template-name. + bool mightBeIntendedToBeTemplateName(ExprResult E) { + if (!getLangOpts().CPlusPlus || E.isInvalid()) + return false; + if (auto *DRE = dyn_cast(E.get())) + return !DRE->hasExplicitTemplateArgs(); + if (auto *ME = dyn_cast(E.get())) + return !ME->hasExplicitTemplateArgs(); + // Any additional cases recognized here should also be handled by + // diagnoseExprIntendedAsTemplateName. + return false; + } + void diagnoseExprIntendedAsTemplateName(Scope *S, ExprResult TemplateName, + SourceLocation Less, + SourceLocation Greater); + Decl *ActOnDeclarator(Scope *S, Declarator &D); NamedDecl *HandleDeclarator(Scope *S, Declarator &D, diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 38c1b2676d..727fd35009 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -235,6 +235,30 @@ bool Parser::isNotExpressionStart() { return isKnownToBeDeclarationSpecifier(); } +/// We've parsed something that could plausibly be intended to be a template +/// name (\p LHS) followed by a '<' token, and the following code can't possibly +/// be an expression. Determine if this is likely to be a template-id and if so, +/// diagnose it. +bool Parser::diagnoseUnknownTemplateId(ExprResult LHS, SourceLocation Less) { + TentativeParsingAction TPA(*this); + // FIXME: We could look at the token sequence in a lot more detail here. + if (SkipUntil(tok::greater, tok::greatergreater, tok::greatergreatergreater, + StopAtSemi | StopBeforeMatch)) { + TPA.Commit(); + + SourceLocation Greater; + ParseGreaterThanInTemplateList(Greater, true, false); + Actions.diagnoseExprIntendedAsTemplateName(getCurScope(), LHS, + Less, Greater); + return true; + } + + // There's no matching '>' token, this probably isn't supposed to be + // interpreted as a template-id. Parse it as an (ill-formed) comparison. + TPA.Revert(); + return false; +} + static bool isFoldOperator(prec::Level Level) { return Level > prec::Unknown && Level != prec::Conditional; } @@ -276,6 +300,16 @@ Parser::ParseRHSOfBinaryExpression(ExprResult LHS, prec::Level MinPrec) { return LHS; } + // If a '<' token is followed by a type that can be a template argument and + // cannot be an expression, then this is ill-formed, but might be intended + // to be a template-id. + if (OpToken.is(tok::less) && Actions.mightBeIntendedToBeTemplateName(LHS) && + (isKnownToBeDeclarationSpecifier() || + Tok.isOneOf(tok::greater, tok::greatergreater, + tok::greatergreatergreater)) && + diagnoseUnknownTemplateId(LHS, OpToken.getLocation())) + return ExprError(); + // If the next token is an ellipsis, then this is a fold-expression. Leave // it alone so we can handle it in the paren expression. if (isFoldOperator(NextTokPrec) && Tok.is(tok::ellipsis)) { diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index f9a7a27667..5e2c9b154a 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -455,6 +455,85 @@ void Sema::LookupTemplateName(LookupResult &Found, } } +void Sema::diagnoseExprIntendedAsTemplateName(Scope *S, ExprResult TemplateName, + SourceLocation Less, + SourceLocation Greater) { + if (TemplateName.isInvalid()) + return; + + DeclarationNameInfo NameInfo; + CXXScopeSpec SS; + LookupNameKind LookupKind; + + DeclContext *LookupCtx = nullptr; + NamedDecl *Found = nullptr; + + // Figure out what name we looked up. + if (auto *ME = dyn_cast(TemplateName.get())) { + NameInfo = ME->getMemberNameInfo(); + SS.Adopt(ME->getQualifierLoc()); + LookupKind = LookupMemberName; + LookupCtx = ME->getBase()->getType()->getAsCXXRecordDecl(); + Found = ME->getMemberDecl(); + } else { + auto *DRE = cast(TemplateName.get()); + NameInfo = DRE->getNameInfo(); + SS.Adopt(DRE->getQualifierLoc()); + LookupKind = LookupOrdinaryName; + Found = DRE->getFoundDecl(); + } + + // Try to correct the name by looking for templates and C++ named casts. + struct TemplateCandidateFilter : CorrectionCandidateCallback { + TemplateCandidateFilter() { + WantTypeSpecifiers = false; + WantExpressionKeywords = false; + WantRemainingKeywords = false; + WantCXXNamedCasts = true; + }; + bool ValidateCandidate(const TypoCorrection &Candidate) override { + if (auto *ND = Candidate.getCorrectionDecl()) + return isAcceptableTemplateName(ND->getASTContext(), ND, true); + return Candidate.isKeyword(); + } + }; + + DeclarationName Name = NameInfo.getName(); + if (TypoCorrection Corrected = + CorrectTypo(NameInfo, LookupKind, S, &SS, + llvm::make_unique(), + CTK_ErrorRecovery, LookupCtx)) { + auto *ND = Corrected.getFoundDecl(); + if (ND) + ND = isAcceptableTemplateName(Context, ND, + /*AllowFunctionTemplates*/ true); + if (ND || Corrected.isKeyword()) { + if (LookupCtx) { + std::string CorrectedStr(Corrected.getAsString(getLangOpts())); + bool DroppedSpecifier = Corrected.WillReplaceSpecifier() && + Name.getAsString() == CorrectedStr; + diagnoseTypo(Corrected, + PDiag(diag::err_non_template_in_member_template_id_suggest) + << Name << LookupCtx << DroppedSpecifier + << SS.getRange()); + } else { + diagnoseTypo(Corrected, + PDiag(diag::err_non_template_in_template_id_suggest) + << Name); + } + if (Found) + Diag(Found->getLocation(), + diag::note_non_template_in_template_id_found); + return; + } + } + + Diag(NameInfo.getLoc(), diag::err_non_template_in_template_id) + << Name << SourceRange(Less, Greater); + if (Found) + Diag(Found->getLocation(), diag::note_non_template_in_template_id_found); +} + /// ActOnDependentIdExpression - Handle a dependent id-expression that /// was just parsed. This is only possible with an explicit scope /// specifier naming a dependent type. diff --git a/test/SemaCXX/cxx1y-variable-templates_top_level.cpp b/test/SemaCXX/cxx1y-variable-templates_top_level.cpp index b496364683..a78548b6f1 100644 --- a/test/SemaCXX/cxx1y-variable-templates_top_level.cpp +++ b/test/SemaCXX/cxx1y-variable-templates_top_level.cpp @@ -9,7 +9,7 @@ #endif template -T pi = T(3.1415926535897932385); // expected-note {{template is declared here}} +T pi = T(3.1415926535897932385); // expected-note 2{{declared here}} template CONST T cpi = T(3.1415926535897932385); // expected-note {{template is declared here}} @@ -58,10 +58,9 @@ namespace use_in_top_level_funcs { namespace shadow { void foo() { int ipi0 = pi; - int pi; + int pi; // expected-note {{found}} int a = pi; - int ipi = pi; // expected-error {{expected '(' for function-style cast or type construction}} \ - // expected-error {{expected expression}} + int ipi = pi; // expected-error {{'pi' does not name a template but is followed by template arguments; did you mean '::pi'?}} } } diff --git a/test/SemaTemplate/typo-template-name.cpp b/test/SemaTemplate/typo-template-name.cpp new file mode 100644 index 0000000000..fe5201a8e2 --- /dev/null +++ b/test/SemaTemplate/typo-template-name.cpp @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -std=c++1z %s -verify -Wno-unused + +namespace InExpr { + namespace A { + void typo_first_a(); // expected-note {{found}} + template void typo_first_b(); // expected-note 2{{declared here}} + } + void testA() { A::typo_first_a(); } // expected-error {{'typo_first_a' does not name a template but is followed by template arguments; did you mean 'typo_first_b'?}} + + namespace B { + void typo_first_b(); // expected-note {{found}} + } + void testB() { B::typo_first_b(); } // expected-error {{'typo_first_b' does not name a template but is followed by template arguments; did you mean 'A::typo_first_b'?}} + + struct Base { + template static void foo(); // expected-note 4{{declared here}} + int n; + }; + struct Derived : Base { + void foo(); // expected-note {{found}} + }; + // We probably don't want to suggest correcting to .Base::foo + void testMember() { Derived().foo(); } // expected-error-re {{does not name a template but is followed by template arguments{{$}}}} + + struct Derived2 : Base { + void goo(); // expected-note {{found}} + }; + void testMember2() { Derived2().goo(); } // expected-error {{member 'goo' of 'InExpr::Derived2' is not a template; did you mean 'foo'?}} + + void no_correction() { + int foo; // expected-note 3{{found}} + + foo(); // expected-error {{'foo' does not name a template but is followed by template arguments; did you mean 'Base::foo'?}} + foo<>(); // expected-error {{'foo' does not name a template but is followed by template arguments; did you mean 'Base::foo'?}} + foo(); // expected-error {{'foo' does not name a template but is followed by template arguments; did you mean 'Base::foo'?}} + + // These are valid expressions. + foo(0); + foo(false); + foo