From: Douglas Gregor Date: Wed, 14 Apr 2010 20:04:41 +0000 (+0000) Subject: Teach typo correction about various language keywords. We can't X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=aaf87162c5fbfbf320072da3a8e83392e1bbf041;p=clang Teach typo correction about various language keywords. We can't generally recover from typos in keywords (since we would effectively have to mangle the token stream). However, there are still benefits to typo-correcting with keywords: - We don't make stupid suggestions when the user typed something that is similar to a keyword. - We can suggest the keyword in a diagnostic (did you mean "static_cast"?), even if we can't recover and therefore don't have a fix-it. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@101274 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index d198cf9d2b..eea0a2bf9e 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -1431,9 +1431,33 @@ public: void LookupVisibleDecls(DeclContext *Ctx, LookupNameKind Kind, VisibleDeclConsumer &Consumer); + /// \brief The context in which typo-correction occurs. + /// + /// The typo-correction context affects which keywords (if any) are + /// considered when trying to correct for typos. + enum CorrectTypoContext { + /// \brief An unknown context, where any keyword might be valid. + CTC_Unknown, + /// \brief A context where no keywords are used (e.g. we expect an actual + /// name). + CTC_NoKeywords, + /// \brief A context where we're correcting a type name. + CTC_Type, + /// \brief An expression context. + CTC_Expression, + /// \brief A type cast, or anything else that can be followed by a '<'. + CTC_CXXCasts, + /// \brief A member lookup context. + CTC_MemberLookup, + /// \brief The receiver of an Objective-C message send within an + /// Objective-C method where 'super' is a valid keyword. + CTC_ObjCMessageReceiver + }; + DeclarationName CorrectTypo(LookupResult &R, Scope *S, CXXScopeSpec *SS, DeclContext *MemberContext = 0, bool EnteringContext = false, + CorrectTypoContext CTC = CTC_Unknown, const ObjCObjectPointerType *OPT = 0); void FindAssociatedClassesAndNamespaces(Expr **Args, unsigned NumArgs, diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp index 81b3e3116a..e9d206e53b 100644 --- a/lib/Sema/SemaCXXScopeSpec.cpp +++ b/lib/Sema/SemaCXXScopeSpec.cpp @@ -483,7 +483,8 @@ Sema::CXXScopeTy *Sema::BuildCXXNestedNameSpecifier(Scope *S, // We haven't found anything, and we're not recovering from a // different kind of error, so look for typos. DeclarationName Name = Found.getLookupName(); - if (CorrectTypo(Found, S, &SS, LookupCtx, EnteringContext) && + if (CorrectTypo(Found, S, &SS, LookupCtx, EnteringContext, + CTC_NoKeywords) && Found.isSingleResult() && isAcceptableNestedNameSpecifier(Found.getAsSingle())) { if (LookupCtx) diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index a2d0e18728..8e46b9b657 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -246,32 +246,36 @@ bool Sema::DiagnoseUnknownTypeName(const IdentifierInfo &II, LookupResult Lookup(*this, &II, IILoc, LookupOrdinaryName, NotForRedeclaration); - // FIXME: It would be nice if we could correct for typos in built-in - // names, such as "itn" for "int". - - if (CorrectTypo(Lookup, S, SS) && Lookup.isSingleResult()) { - NamedDecl *Result = Lookup.getAsSingle(); - if ((isa(Result) || isa(Result)) && - !Result->isInvalidDecl()) { - // We found a similarly-named type or interface; suggest that. - if (!SS || !SS->isSet()) - Diag(IILoc, diag::err_unknown_typename_suggest) - << &II << Lookup.getLookupName() - << FixItHint::CreateReplacement(SourceRange(IILoc), - Result->getNameAsString()); - else if (DeclContext *DC = computeDeclContext(*SS, false)) - Diag(IILoc, diag::err_unknown_nested_typename_suggest) - << &II << DC << Lookup.getLookupName() << SS->getRange() - << FixItHint::CreateReplacement(SourceRange(IILoc), - Result->getNameAsString()); - else - llvm_unreachable("could not have corrected a typo here"); + if (DeclarationName Corrected = CorrectTypo(Lookup, S, SS, 0, 0, CTC_Type)) { + if (NamedDecl *Result = Lookup.getAsSingle()) { + if ((isa(Result) || isa(Result)) && + !Result->isInvalidDecl()) { + // We found a similarly-named type or interface; suggest that. + if (!SS || !SS->isSet()) + Diag(IILoc, diag::err_unknown_typename_suggest) + << &II << Lookup.getLookupName() + << FixItHint::CreateReplacement(SourceRange(IILoc), + Result->getNameAsString()); + else if (DeclContext *DC = computeDeclContext(*SS, false)) + Diag(IILoc, diag::err_unknown_nested_typename_suggest) + << &II << DC << Lookup.getLookupName() << SS->getRange() + << FixItHint::CreateReplacement(SourceRange(IILoc), + Result->getNameAsString()); + else + llvm_unreachable("could not have corrected a typo here"); - Diag(Result->getLocation(), diag::note_previous_decl) - << Result->getDeclName(); - - SuggestedType = getTypeName(*Result->getIdentifier(), IILoc, S, SS); - return true; + Diag(Result->getLocation(), diag::note_previous_decl) + << Result->getDeclName(); + + SuggestedType = getTypeName(*Result->getIdentifier(), IILoc, S, SS); + return true; + } + } else if (Lookup.empty()) { + // We corrected to a keyword. + // FIXME: Actually recover with the keyword we suggest, and emit a fix-it. + Diag(IILoc, diag::err_unknown_typename_suggest) + << &II << Corrected; + return true; } } @@ -605,7 +609,7 @@ ObjCInterfaceDecl *Sema::getObjCInterfaceDecl(IdentifierInfo *&Id, // Perform typo correction at the given location, but only if we // find an Objective-C class name. LookupResult R(*this, Id, RecoverLoc, LookupOrdinaryName); - if (CorrectTypo(R, TUScope, 0) && + if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) && (IDecl = R.getAsSingle())) { Diag(RecoverLoc, diag::err_undef_interface_suggest) << Id << IDecl->getDeclName() diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 214d57c7dc..24695f35cc 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -1099,7 +1099,8 @@ Sema::ActOnMemInitializer(DeclPtrTy ConstructorD, // If no results were found, try to correct typos. if (R.empty() && BaseType.isNull() && - CorrectTypo(R, S, &SS, ClassDecl) && R.isSingleResult()) { + CorrectTypo(R, S, &SS, ClassDecl, 0, CTC_NoKeywords) && + R.isSingleResult()) { if (FieldDecl *Member = R.getAsSingle()) { if (Member->getDeclContext()->getLookupContext()->Equals(ClassDecl)) { // We have found a non-static data member with a similar diff --git a/lib/Sema/SemaDeclObjC.cpp b/lib/Sema/SemaDeclObjC.cpp index eb3f4222b6..f56a0b196e 100644 --- a/lib/Sema/SemaDeclObjC.cpp +++ b/lib/Sema/SemaDeclObjC.cpp @@ -120,7 +120,7 @@ ActOnStartClassInterface(SourceLocation AtInterfaceLoc, if (!PrevDecl) { // Try to correct for a typo in the superclass name. LookupResult R(*this, SuperName, SuperLoc, LookupOrdinaryName); - if (CorrectTypo(R, TUScope, 0) && + if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) && (PrevDecl = R.getAsSingle())) { Diag(SuperLoc, diag::err_undef_superclass_suggest) << SuperName << ClassName << PrevDecl->getDeclName(); @@ -317,7 +317,7 @@ Sema::FindProtocolDeclaration(bool WarnOnDeclarations, if (!PDecl) { LookupResult R(*this, ProtocolId[i].first, ProtocolId[i].second, LookupObjCProtocolName); - if (CorrectTypo(R, TUScope, 0) && + if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) && (PDecl = R.getAsSingle())) { Diag(ProtocolId[i].second, diag::err_undeclared_protocol_suggest) << ProtocolId[i].first << R.getLookupName(); @@ -554,7 +554,7 @@ Sema::DeclPtrTy Sema::ActOnStartClassImplementation( // We did not find anything with the name ClassName; try to correct for // typos in the class name. LookupResult R(*this, ClassName, ClassLoc, LookupOrdinaryName); - if (CorrectTypo(R, TUScope, 0) && + if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) && (IDecl = R.getAsSingle())) { // Suggest the (potentially) correct interface name. However, put the // fix-it hint itself in a separate note, since changing the name in diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index d10ea6d4ab..2a9af99493 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -946,43 +946,55 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, } // We didn't find anything, so try to correct for a typo. - if (S && CorrectTypo(R, S, &SS)) { - if (isa(*R.begin()) || isa(*R.begin())) { - if (SS.isEmpty()) - Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); - else - Diag(R.getNameLoc(), diag::err_no_member_suggest) - << Name << computeDeclContext(SS, false) << R.getLookupName() - << SS.getRange() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); - if (NamedDecl *ND = R.getAsSingle()) - Diag(ND->getLocation(), diag::note_previous_decl) - << ND->getDeclName(); - - // Tell the callee to try to recover. - return false; - } + DeclarationName Corrected; + if (S && (Corrected = CorrectTypo(R, S, &SS))) { + if (!R.empty()) { + if (isa(*R.begin()) || isa(*R.begin())) { + if (SS.isEmpty()) + Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName() + << FixItHint::CreateReplacement(R.getNameLoc(), + R.getLookupName().getAsString()); + else + Diag(R.getNameLoc(), diag::err_no_member_suggest) + << Name << computeDeclContext(SS, false) << R.getLookupName() + << SS.getRange() + << FixItHint::CreateReplacement(R.getNameLoc(), + R.getLookupName().getAsString()); + if (NamedDecl *ND = R.getAsSingle()) + Diag(ND->getLocation(), diag::note_previous_decl) + << ND->getDeclName(); + + // Tell the callee to try to recover. + return false; + } + + if (isa(*R.begin()) || isa(*R.begin())) { + // FIXME: If we ended up with a typo for a type name or + // Objective-C class name, we're in trouble because the parser + // is in the wrong place to recover. Suggest the typo + // correction, but don't make it a fix-it since we're not going + // to recover well anyway. + if (SS.isEmpty()) + Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName(); + else + Diag(R.getNameLoc(), diag::err_no_member_suggest) + << Name << computeDeclContext(SS, false) << R.getLookupName() + << SS.getRange(); - if (isa(*R.begin()) || isa(*R.begin())) { - // FIXME: If we ended up with a typo for a type name or - // Objective-C class name, we're in trouble because the parser - // is in the wrong place to recover. Suggest the typo - // correction, but don't make it a fix-it since we're not going - // to recover well anyway. + // Don't try to recover; it won't work. + return true; + } + } else { + // FIXME: We found a keyword. Suggest it, but don't provide a fix-it + // because we aren't able to recover. if (SS.isEmpty()) - Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName(); + Diag(R.getNameLoc(), diagnostic_suggest) << Name << Corrected; else Diag(R.getNameLoc(), diag::err_no_member_suggest) - << Name << computeDeclContext(SS, false) << R.getLookupName() - << SS.getRange(); - - // Don't try to recover; it won't work. + << Name << computeDeclContext(SS, false) << Corrected + << SS.getRange(); return true; } - R.clear(); } @@ -2575,7 +2587,8 @@ LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R, // We didn't find anything with the given name, so try to correct // for typos. DeclarationName Name = R.getLookupName(); - if (SemaRef.CorrectTypo(R, 0, &SS, DC) && + if (SemaRef.CorrectTypo(R, 0, &SS, DC, false, Sema::CTC_MemberLookup) && + !R.empty() && (isa(*R.begin()) || isa(*R.begin()))) { SemaRef.Diag(R.getNameLoc(), diag::err_no_member_suggest) << Name << DC << R.getLookupName() << SS.getRange() @@ -3032,7 +3045,7 @@ Sema::LookupMemberExpr(LookupResult &R, Expr *&BaseExpr, // Attempt to correct for typos in ivar names. LookupResult Res(*this, R.getLookupName(), R.getNameLoc(), LookupMemberName); - if (CorrectTypo(Res, 0, 0, IDecl) && + if (CorrectTypo(Res, 0, 0, IDecl, false, CTC_MemberLookup) && (IV = Res.getAsSingle())) { Diag(R.getNameLoc(), diag::err_typecheck_member_reference_ivar_suggest) diff --git a/lib/Sema/SemaExprObjC.cpp b/lib/Sema/SemaExprObjC.cpp index f7d76b8b9b..faa1ffc31e 100644 --- a/lib/Sema/SemaExprObjC.cpp +++ b/lib/Sema/SemaExprObjC.cpp @@ -372,7 +372,7 @@ HandleExprPropertyRefExpr(const ObjCObjectPointerType *OPT, // Attempt to correct for typos in property names. LookupResult Res(*this, MemberName, MemberLoc, LookupOrdinaryName); - if (CorrectTypo(Res, 0, 0, IFace, false, OPT) && + if (CorrectTypo(Res, 0, 0, IFace, false, CTC_NoKeywords, OPT) && Res.getAsSingle()) { DeclarationName TypoResult = Res.getLookupName(); Diag(MemberLoc, diag::err_property_not_found_suggest) @@ -523,18 +523,37 @@ Sema::ObjCMessageKind Sema::getObjCMessageKind(Scope *S, } } - if (CorrectTypo(Result, S, 0) && Result.isSingleResult()) { - NamedDecl *ND = Result.getFoundDecl(); - if (isa(ND)) { + // Determine our typo-correction context. + CorrectTypoContext CTC = CTC_Expression; + if (ObjCMethodDecl *Method = getCurMethodDecl()) + if (Method->getClassInterface() && + Method->getClassInterface()->getSuperClass()) + CTC = CTC_ObjCMessageReceiver; + + if (DeclarationName Corrected = CorrectTypo(Result, S, 0, 0, false, CTC)) { + if (Result.isSingleResult()) { + // If we found a declaration, correct when it refers to an Objective-C + // class. + NamedDecl *ND = Result.getFoundDecl(); + if (isa(ND)) { + Diag(NameLoc, diag::err_unknown_receiver_suggest) + << Name << Result.getLookupName() + << FixItHint::CreateReplacement(SourceRange(NameLoc), + ND->getNameAsString()); + Diag(ND->getLocation(), diag::note_previous_decl) + << Corrected; + + Name = ND->getIdentifier(); + return ObjCClassMessage; + } + } else if (Result.empty() && Corrected.getAsIdentifierInfo() && + Corrected.getAsIdentifierInfo()->isStr("super")) { + // If we've found the keyword "super", this is a send to super. Diag(NameLoc, diag::err_unknown_receiver_suggest) - << Name << Result.getLookupName() - << FixItHint::CreateReplacement(SourceRange(NameLoc), - ND->getNameAsString()); - Diag(ND->getLocation(), diag::note_previous_decl) - << ND->getDeclName(); - - Name = ND->getIdentifier(); - return ObjCClassMessage; + << Name << Corrected + << FixItHint::CreateReplacement(SourceRange(NameLoc), "super"); + Name = Corrected.getAsIdentifierInfo(); + return ObjCSuperMessage; } } diff --git a/lib/Sema/SemaInit.cpp b/lib/Sema/SemaInit.cpp index 52a5cb1372..dae3d3ae7e 100644 --- a/lib/Sema/SemaInit.cpp +++ b/lib/Sema/SemaInit.cpp @@ -1354,7 +1354,8 @@ InitListChecker::CheckDesignatedInitializer(const InitializedEntity &Entity, // was a typo for another field name. LookupResult R(SemaRef, FieldName, D->getFieldLoc(), Sema::LookupMemberName); - if (SemaRef.CorrectTypo(R, /*Scope=*/0, /*SS=*/0, RT->getDecl()) && + if (SemaRef.CorrectTypo(R, /*Scope=*/0, /*SS=*/0, RT->getDecl(), false, + Sema::CTC_NoKeywords) && (ReplacementField = R.getAsSingle()) && ReplacementField->getDeclContext()->getLookupContext() ->Equals(RT->getDecl())) { diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index 0e2a10aeb0..18b6e72050 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -2421,6 +2421,9 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer { /// found (so far) with the typo name. llvm::SmallVector BestResults; + /// \brief The keywords that have the smallest edit distance. + llvm::SmallVector BestKeywords; + /// \brief The best edit distance found so far. unsigned BestEditDistance; @@ -2429,13 +2432,23 @@ public: : Typo(Typo->getName()) { } virtual void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, bool InBaseClass); + void addKeywordResult(ASTContext &Context, llvm::StringRef Keyword); typedef llvm::SmallVector::const_iterator iterator; iterator begin() const { return BestResults.begin(); } iterator end() const { return BestResults.end(); } - bool empty() const { return BestResults.empty(); } - - unsigned getBestEditDistance() const { return BestEditDistance; } + void clear_decls() { BestResults.clear(); } + + bool empty() const { return BestResults.empty() && BestKeywords.empty(); } + + typedef llvm::SmallVector::const_iterator + keyword_iterator; + keyword_iterator keyword_begin() const { return BestKeywords.begin(); } + keyword_iterator keyword_end() const { return BestKeywords.end(); } + bool keyword_empty() const { return BestKeywords.empty(); } + unsigned keyword_size() const { return BestKeywords.size(); } + + unsigned getBestEditDistance() const { return BestEditDistance; } }; } @@ -2457,11 +2470,12 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding, // entity. If this edit distance is not worse than the best edit // distance we've seen so far, add it to the list of results. unsigned ED = Typo.edit_distance(Name->getName()); - if (!BestResults.empty()) { + if (!BestResults.empty() || !BestKeywords.empty()) { if (ED < BestEditDistance) { // This result is better than any we've seen before; clear out // the previous results. BestResults.clear(); + BestKeywords.clear(); BestEditDistance = ED; } else if (ED > BestEditDistance) { // This result is worse than the best results we've seen so far; @@ -2474,6 +2488,28 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding, BestResults.push_back(ND); } +void TypoCorrectionConsumer::addKeywordResult(ASTContext &Context, + llvm::StringRef Keyword) { + // Compute the edit distance between the typo and this keyword. + // If this edit distance is not worse than the best edit + // distance we've seen so far, add it to the list of results. + unsigned ED = Typo.edit_distance(Keyword); + if (!BestResults.empty() || !BestKeywords.empty()) { + if (ED < BestEditDistance) { + BestResults.clear(); + BestKeywords.clear(); + BestEditDistance = ED; + } else if (ED > BestEditDistance) { + // This result is worse than the best results we've seen so far; + // ignore it. + return; + } + } else + BestEditDistance = ED; + + BestKeywords.push_back(&Context.Idents.get(Keyword)); +} + /// \brief Try to "correct" a typo in the source code by finding /// visible declarations whose names are similar to the name that was /// present in the source code. @@ -2494,6 +2530,9 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding, /// \param EnteringContext whether we're entering the context described by /// the nested-name-specifier SS. /// +/// \param CTC The context in which typo correction occurs, which impacts the +/// set of keywords permitted. +/// /// \param OPT when non-NULL, the search for visible declarations will /// also walk the protocols in the qualified interfaces of \p OPT. /// @@ -2502,8 +2541,10 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding, /// may contain the results of name lookup for the correct name or it may be /// empty. DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS, - DeclContext *MemberContext, bool EnteringContext, - const ObjCObjectPointerType *OPT) { + DeclContext *MemberContext, + bool EnteringContext, + CorrectTypoContext CTC, + const ObjCObjectPointerType *OPT) { if (Diags.hasFatalErrorOccurred()) return DeclarationName(); @@ -2529,8 +2570,10 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS, // instantiation. if (!ActiveTemplateInstantiations.empty()) return DeclarationName(); - + TypoCorrectionConsumer Consumer(Typo); + + // Perform name lookup to find visible, similarly-named entities. if (MemberContext) { LookupVisibleDecls(MemberContext, Res.getLookupKind(), Consumer); @@ -2551,37 +2594,231 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS, LookupVisibleDecls(S, Res.getLookupKind(), Consumer); } + // Add context-dependent keywords. + bool WantTypeSpecifiers = false; + bool WantExpressionKeywords = false; + bool WantCXXNamedCasts = false; + bool WantRemainingKeywords = false; + switch (CTC) { + case CTC_Unknown: + WantTypeSpecifiers = true; + WantExpressionKeywords = true; + WantCXXNamedCasts = true; + WantRemainingKeywords = true; + break; + + case CTC_NoKeywords: + break; + + case CTC_Type: + WantTypeSpecifiers = true; + break; + + case CTC_ObjCMessageReceiver: + Consumer.addKeywordResult(Context, "super"); + // Fall through to handle message receivers like expressions. + + case CTC_Expression: + if (getLangOptions().CPlusPlus) + WantTypeSpecifiers = true; + WantExpressionKeywords = true; + // Fall through to get C++ named casts. + + case CTC_CXXCasts: + WantCXXNamedCasts = true; + break; + + case CTC_MemberLookup: + if (getLangOptions().CPlusPlus) + Consumer.addKeywordResult(Context, "template"); + break; + } + + if (WantTypeSpecifiers) { + // Add type-specifier keywords to the set of results. + const char *CTypeSpecs[] = { + "char", "const", "double", "enum", "float", "int", "long", "short", + "signed", "struct", "union", "unsigned", "void", "volatile", "_Bool", + "_Complex", "_Imaginary", + // storage-specifiers as well + "extern", "inline", "static", "typedef" + }; + + const unsigned NumCTypeSpecs = sizeof(CTypeSpecs) / sizeof(CTypeSpecs[0]); + for (unsigned I = 0; I != NumCTypeSpecs; ++I) + Consumer.addKeywordResult(Context, CTypeSpecs[I]); + + if (getLangOptions().C99) + Consumer.addKeywordResult(Context, "restrict"); + if (getLangOptions().Bool || getLangOptions().CPlusPlus) + Consumer.addKeywordResult(Context, "bool"); + + if (getLangOptions().CPlusPlus) { + Consumer.addKeywordResult(Context, "class"); + Consumer.addKeywordResult(Context, "typename"); + Consumer.addKeywordResult(Context, "wchar_t"); + + if (getLangOptions().CPlusPlus0x) { + Consumer.addKeywordResult(Context, "char16_t"); + Consumer.addKeywordResult(Context, "char32_t"); + Consumer.addKeywordResult(Context, "constexpr"); + Consumer.addKeywordResult(Context, "decltype"); + Consumer.addKeywordResult(Context, "thread_local"); + } + } + + if (getLangOptions().GNUMode) + Consumer.addKeywordResult(Context, "typeof"); + } + + if (WantCXXNamedCasts) { + Consumer.addKeywordResult(Context, "const_cast"); + Consumer.addKeywordResult(Context, "dynamic_cast"); + Consumer.addKeywordResult(Context, "reinterpret_cast"); + Consumer.addKeywordResult(Context, "static_cast"); + } + + if (WantExpressionKeywords) { + Consumer.addKeywordResult(Context, "sizeof"); + if (getLangOptions().Bool || getLangOptions().CPlusPlus) { + Consumer.addKeywordResult(Context, "false"); + Consumer.addKeywordResult(Context, "true"); + } + + if (getLangOptions().CPlusPlus) { + const char *CXXExprs[] = { + "delete", "new", "operator", "throw", "typeid" + }; + const unsigned NumCXXExprs = sizeof(CXXExprs) / sizeof(CXXExprs[0]); + for (unsigned I = 0; I != NumCXXExprs; ++I) + Consumer.addKeywordResult(Context, CXXExprs[I]); + + if (isa(CurContext) && + cast(CurContext)->isInstance()) + Consumer.addKeywordResult(Context, "this"); + + if (getLangOptions().CPlusPlus0x) { + Consumer.addKeywordResult(Context, "alignof"); + Consumer.addKeywordResult(Context, "nullptr"); + } + } + } + + if (WantRemainingKeywords) { + if (getCurFunctionOrMethodDecl() || getCurBlock()) { + // Statements. + const char *CStmts[] = { + "do", "else", "for", "goto", "if", "return", "switch", "while" }; + const unsigned NumCStmts = sizeof(CStmts) / sizeof(CStmts[0]); + for (unsigned I = 0; I != NumCStmts; ++I) + Consumer.addKeywordResult(Context, CStmts[I]); + + if (getLangOptions().CPlusPlus) { + Consumer.addKeywordResult(Context, "catch"); + Consumer.addKeywordResult(Context, "try"); + } + + if (S && S->getBreakParent()) + Consumer.addKeywordResult(Context, "break"); + + if (S && S->getContinueParent()) + Consumer.addKeywordResult(Context, "continue"); + + if (!getSwitchStack().empty()) { + Consumer.addKeywordResult(Context, "case"); + Consumer.addKeywordResult(Context, "default"); + } + } else { + if (getLangOptions().CPlusPlus) { + Consumer.addKeywordResult(Context, "namespace"); + Consumer.addKeywordResult(Context, "template"); + } + + if (S && S->isClassScope()) { + Consumer.addKeywordResult(Context, "explicit"); + Consumer.addKeywordResult(Context, "friend"); + Consumer.addKeywordResult(Context, "mutable"); + Consumer.addKeywordResult(Context, "private"); + Consumer.addKeywordResult(Context, "protected"); + Consumer.addKeywordResult(Context, "public"); + Consumer.addKeywordResult(Context, "virtual"); + } + } + + if (getLangOptions().CPlusPlus) { + Consumer.addKeywordResult(Context, "using"); + + if (getLangOptions().CPlusPlus0x) + Consumer.addKeywordResult(Context, "static_assert"); + } + } + + // If we haven't found anything, we're done. if (Consumer.empty()) return DeclarationName(); // Only allow a single, closest name in the result set (it's okay to // have overloads of that name, though). - TypoCorrectionConsumer::iterator I = Consumer.begin(); - DeclarationName BestName = (*I)->getDeclName(); - - // If we've found an Objective-C ivar or property, don't perform - // name lookup again; we'll just return the result directly. - NamedDecl *FoundBest = 0; - if (isa(*I) || isa(*I)) - FoundBest = *I; - ++I; - for(TypoCorrectionConsumer::iterator IEnd = Consumer.end(); I != IEnd; ++I) { - if (BestName != (*I)->getDeclName()) + DeclarationName BestName; + NamedDecl *BestIvarOrPropertyDecl = 0; + bool FoundIvarOrPropertyDecl = false; + + // Check all of the declaration results to find the best name so far. + for (TypoCorrectionConsumer::iterator I = Consumer.begin(), + IEnd = Consumer.end(); + I != IEnd; ++I) { + if (!BestName) + BestName = (*I)->getDeclName(); + else if (BestName != (*I)->getDeclName()) return DeclarationName(); - // FIXME: If there are both ivars and properties of the same name, - // don't return both because the callee can't handle two - // results. We really need to separate ivar lookup from property - // lookup to avoid this problem. - FoundBest = 0; + // \brief Keep track of either an Objective-C ivar or a property, but not + // both. + if (isa(*I) || isa(*I)) { + if (FoundIvarOrPropertyDecl) + BestIvarOrPropertyDecl = 0; + else { + BestIvarOrPropertyDecl = *I; + FoundIvarOrPropertyDecl = true; + } + } } + // Now check all of the keyword results to find the best name. + switch (Consumer.keyword_size()) { + case 0: + // No keywords matched. + break; + + case 1: + // If we already have a name + if (!BestName) { + // We did not have anything previously, + BestName = *Consumer.keyword_begin(); + } else if (BestName.getAsIdentifierInfo() == *Consumer.keyword_begin()) { + // We have a declaration with the same name as a context-sensitive + // keyword. The keyword takes precedence. + BestIvarOrPropertyDecl = 0; + FoundIvarOrPropertyDecl = false; + Consumer.clear_decls(); + } else { + // Name collision; we will not correct typos. + return DeclarationName(); + } + break; + + default: + // Name collision; we will not correct typos. + return DeclarationName(); + } + // BestName is the closest viable name to what the user // typed. However, to make sure that we don't pick something that's // way off, make sure that the user typed at least 3 characters for // each correction. unsigned ED = Consumer.getBestEditDistance(); - if (ED == 0 || (BestName.getAsIdentifierInfo()->getName().size() / ED) < 3) + if (ED == 0 || !BestName.getAsIdentifierInfo() || + (BestName.getAsIdentifierInfo()->getName().size() / ED) < 3) return DeclarationName(); // Perform name lookup again with the name we chose, and declare @@ -2591,11 +2828,14 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS, // If we found an ivar or property, add that result; no further // lookup is required. - if (FoundBest) - Res.addDecl(FoundBest); + if (BestIvarOrPropertyDecl) + Res.addDecl(BestIvarOrPropertyDecl); // If we're looking into the context of a member, perform qualified // name lookup on the best name. - else if (MemberContext) + else if (!Consumer.keyword_empty()) { + // The best match was a keyword. Return it. + return BestName; + } else if (MemberContext) LookupQualifiedName(Res, MemberContext); // Perform lookup as if we had just parsed the best name. else diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index 9816e76530..28d6f0a40a 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -250,7 +250,8 @@ void Sema::LookupTemplateName(LookupResult &Found, if (Found.empty() && !isDependent) { // If we did not find any names, attempt to correct any typos. DeclarationName Name = Found.getLookupName(); - if (CorrectTypo(Found, S, &SS, LookupCtx)) { + if (DeclarationName Corrected = CorrectTypo(Found, S, &SS, LookupCtx, + false, CTC_CXXCasts)) { FilterAcceptableTemplateNames(Context, Found); if (!Found.empty() && isa(*Found.begin())) { if (LookupCtx) diff --git a/test/FixIt/fixit-unrecoverable.c b/test/FixIt/fixit-unrecoverable.c new file mode 100644 index 0000000000..5c825f23cf --- /dev/null +++ b/test/FixIt/fixit-unrecoverable.c @@ -0,0 +1,12 @@ +/* FIXME: This is a file containing various typos for which we can + suggest corrections but are unable to actually recover from + them. Ideally, we would eliminate all such cases and move these + tests elsewhere. */ + +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// FIXME: Sadly, the following doesn't work within a function. + +unsinged x = 17; // expected-error{{unknown type name 'unsinged'; did you mean 'unsigned'?}} + + diff --git a/test/FixIt/fixit-unrecoverable.cpp b/test/FixIt/fixit-unrecoverable.cpp new file mode 100644 index 0000000000..00ed8978c6 --- /dev/null +++ b/test/FixIt/fixit-unrecoverable.cpp @@ -0,0 +1,11 @@ +/* FIXME: This is a file containing various typos for which we can + suggest corrections but are unable to actually recover from + them. Ideally, we would eliminate all such cases and move these + tests elsewhere. */ + +// RUN: %clang_cc1 -fsyntax-only -verify %s + +float f(int y) { + return static_cst(y); // expected-error{{use of undeclared identifier 'static_cst'; did you mean 'static_cast'?}} +} + diff --git a/test/FixIt/typo.m b/test/FixIt/typo.m index 19602fcc8e..ccd94b78c5 100644 --- a/test/FixIt/typo.m +++ b/test/FixIt/typo.m @@ -87,3 +87,18 @@ void test2(Collide *a) { @interface IPv6 // expected-error{{cannot find protocol declaration for 'Network_Socket'; did you mean 'NetworkSocket'?}} @end + +@interface Super +- (int)method; +@end + +@interface Sub : Super +- (int)method; +@end + +@implementation Sub +- (int)method { + return [supper method]; // expected-error{{unknown receiver 'supper'; did you mean 'super'?}} +} + +@end