From: Douglas Gregor Date: Thu, 31 Dec 2009 05:20:13 +0000 (+0000) Subject: Implement typo correction for id-expressions, e.g., X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=bb092bafa984e9fa05136b5cef40fd4374dea0f6;p=clang Implement typo correction for id-expressions, e.g., typo.cpp:22:10: error: use of undeclared identifier 'radious'; did you mean 'radius'? return radious * pi; ^~~~~~~ radius This was super-easy, since we already had decent recovery by looking for names in dependent base classes. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@92341 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/Diagnostic.h b/include/clang/Basic/Diagnostic.h index 9c69b59c6c..a2ccea7525 100644 --- a/include/clang/Basic/Diagnostic.h +++ b/include/clang/Basic/Diagnostic.h @@ -45,7 +45,7 @@ namespace clang { DIAG_START_PARSE = DIAG_START_LEX + 300, DIAG_START_AST = DIAG_START_PARSE + 300, DIAG_START_SEMA = DIAG_START_AST + 100, - DIAG_START_ANALYSIS = DIAG_START_SEMA + 1100, + DIAG_START_ANALYSIS = DIAG_START_SEMA + 1500, DIAG_UPPER_LIMIT = DIAG_START_ANALYSIS + 100 }; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 5ab522069f..ffe328c370 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -2552,6 +2552,11 @@ def err_unknown_typename_suggest : Error< "unknown type name %0; did you mean %1?">; def err_unknown_nested_typename_suggest : Error< "no type named %0 in %1; did you mean %2?">; +def err_no_member_suggest : Error<"no member named %0 in %1; did you mean %2?">; +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?">; } diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 06927b6d1d..b92bd4fcea 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -1209,8 +1209,7 @@ public: VisibleDeclConsumer &Consumer); bool CorrectTypo(LookupResult &R, Scope *S, const CXXScopeSpec *SS, - bool AllowBuiltinCreation = false, - bool EnteringContext = false); + bool EnteringContext = false); void FindAssociatedClassesAndNamespaces(Expr **Args, unsigned NumArgs, AssociatedNamespaceSet &AssociatedNamespaces, @@ -1458,7 +1457,7 @@ public: bool HasTrailingLParen, bool IsAddressOfOperand); - bool DiagnoseEmptyLookup(const CXXScopeSpec &SS, LookupResult &R); + bool DiagnoseEmptyLookup(Scope *S, const CXXScopeSpec &SS, LookupResult &R); OwningExprResult LookupInObjCMethod(LookupResult &R, Scope *S, diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 77ddbc29dd..c87a274122 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -879,28 +879,25 @@ static void DiagnoseInstanceReference(Sema &SemaRef, /// Diagnose an empty lookup. /// /// \return false if new lookup candidates were found -bool Sema::DiagnoseEmptyLookup(const CXXScopeSpec &SS, +bool Sema::DiagnoseEmptyLookup(Scope *S, const CXXScopeSpec &SS, LookupResult &R) { DeclarationName Name = R.getLookupName(); - // We don't know how to recover from bad qualified lookups. - if (!SS.isEmpty()) { - Diag(R.getNameLoc(), diag::err_no_member) - << Name << computeDeclContext(SS, false) - << SS.getRange(); - return true; - } - unsigned diagnostic = diag::err_undeclared_var_use; + unsigned diagnostic_suggest = diag::err_undeclared_var_use_suggest; if (Name.getNameKind() == DeclarationName::CXXOperatorName || Name.getNameKind() == DeclarationName::CXXLiteralOperatorName || - Name.getNameKind() == DeclarationName::CXXConversionFunctionName) + Name.getNameKind() == DeclarationName::CXXConversionFunctionName) { diagnostic = diag::err_undeclared_use; + diagnostic_suggest = diag::err_undeclared_use_suggest; + } - // Fake an unqualified lookup. This is useful when (for example) - // the original lookup would not have found something because it was - // a dependent name. - for (DeclContext *DC = CurContext; DC; DC = DC->getParent()) { + // If the original lookup was an unqualified lookup, fake an + // unqualified lookup. This is useful when (for example) the + // original lookup would not have found something because it was a + // dependent name. + for (DeclContext *DC = SS.isEmpty()? CurContext : 0; + DC; DC = DC->getParent()) { if (isa(DC)) { LookupQualifiedName(R, DC); @@ -933,6 +930,33 @@ bool Sema::DiagnoseEmptyLookup(const CXXScopeSpec &SS, } } + // We didn't find anything, so try to correct for a typo. + if (S && CorrectTypo(R, S, &SS) && + (isa(*R.begin()) || isa(*R.begin()))) { + if (SS.isEmpty()) + Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName() + << CodeModificationHint::CreateReplacement(R.getNameLoc(), + R.getLookupName().getAsString()); + else + Diag(R.getNameLoc(), diag::err_no_member_suggest) + << Name << computeDeclContext(SS, false) << R.getLookupName() + << SS.getRange() + << CodeModificationHint::CreateReplacement(R.getNameLoc(), + R.getLookupName().getAsString()); + + // Tell the callee to try to recover. + return false; + } + + // Emit a special diagnostic for failed member lookups. + // FIXME: computing the declaration context might fail here (?) + if (!SS.isEmpty()) { + Diag(R.getNameLoc(), diag::err_no_member) + << Name << computeDeclContext(SS, false) + << SS.getRange(); + return true; + } + // Give up, we can't recover. Diag(R.getNameLoc(), diagnostic) << Name; return true; @@ -1010,7 +1034,7 @@ Sema::OwningExprResult Sema::ActOnIdExpression(Scope *S, // If this name wasn't predeclared and if this is not a function // call, diagnose the problem. if (R.empty()) { - if (DiagnoseEmptyLookup(SS, R)) + if (DiagnoseEmptyLookup(S, SS, R)) return ExprError(); assert(!R.empty() && diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index 72779c339d..9abbd575dc 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -2134,11 +2134,14 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding) { /// \param SS the nested-name-specifier that precedes the name we're /// looking for, if present. /// +/// \param EnteringContext whether we're entering the context described by +/// the nested-name-specifier SS. +/// /// \returns true if the typo was corrected, in which case the \p Res /// structure will contain the results of name lookup for the /// corrected name. Otherwise, returns false. bool Sema::CorrectTypo(LookupResult &Res, Scope *S, const CXXScopeSpec *SS, - bool AllowBuiltinCreation, bool EnteringContext) { + bool EnteringContext) { // We only attempt to correct typos for identifiers. IdentifierInfo *Typo = Res.getLookupName().getAsIdentifierInfo(); if (!Typo) @@ -2190,6 +2193,12 @@ bool Sema::CorrectTypo(LookupResult &Res, Scope *S, const CXXScopeSpec *SS, // success if we found something that was not ambiguous. Res.clear(); Res.setLookupName(BestName); - LookupParsedName(Res, S, SS, AllowBuiltinCreation, EnteringContext); - return Res.getResultKind() != LookupResult::NotFound && !Res.isAmbiguous(); + LookupParsedName(Res, S, SS, /*AllowBuiltinCreation=*/false, EnteringContext); + + if (Res.isAmbiguous()) { + Res.suppressDiagnostics(); + return false; + } + + return Res.getResultKind() != LookupResult::NotFound; } diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index 99dbaba37b..5892081736 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -4822,7 +4822,7 @@ BuildRecoveryCallExpr(Sema &SemaRef, Expr *Fn, LookupResult R(SemaRef, ULE->getName(), ULE->getNameLoc(), Sema::LookupOrdinaryName); - if (SemaRef.DiagnoseEmptyLookup(SS, R)) + if (SemaRef.DiagnoseEmptyLookup(/*Scope=*/0, SS, R)) return Destroy(SemaRef, Fn, Args, NumArgs); assert(!R.empty() && "lookup results empty despite recovery"); diff --git a/test/FixIt/typo.cpp b/test/FixIt/typo.cpp index b884ffa755..6c232f7403 100644 --- a/test/FixIt/typo.cpp +++ b/test/FixIt/typo.cpp @@ -1,7 +1,11 @@ // RUN: %clang_cc1 -fsyntax-only -verify %s // RUN: %clang_cc1 -fsyntax-only -fixit -o - | %clang_cc1 -fsyntax-only -pedantic -Werror -x c++ - namespace std { - template class basic_string { }; + template class basic_string { + int find(const char *substr); + static const int npos = -1; + }; + typedef basic_string string; } @@ -13,3 +17,11 @@ using namespace std; otherstd::strng str1; // expected-error{{no type named 'strng' in namespace 'otherstd'; did you mean 'string'?}} tring str2; // expected-error{{unknown type name 'tring'; did you mean 'string'?}} + +float area(float radius, float pi) { + return radious * pi; // expected-error{{use of undeclared identifier 'radious'; did you mean 'radius'?}} +} + +bool test_string(std::string s) { + return s.find("hello") == std::string::pos; // expected-error{{no member named 'pos' in 'class std::basic_string'; did you mean 'npos'?}} +} diff --git a/test/Sema/var-redecl.c b/test/Sema/var-redecl.c index d4d900d302..e67499bcc4 100644 --- a/test/Sema/var-redecl.c +++ b/test/Sema/var-redecl.c @@ -53,7 +53,8 @@ void outer_shadowing_test() { void g18(void) { extern int g19; } -int *p=&g19; // expected-error{{use of undeclared identifier 'g19'}} +int *p=&g19; // expected-error{{use of undeclared identifier 'g19'}} \ + // expected-warning{{incompatible pointer types}} // PR3645 static int a;