From: Douglas Gregor Date: Tue, 28 Jun 2011 16:20:02 +0000 (+0000) Subject: Add support for C++ namespace-aware typo correction, e.g., correcting X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d8bba9c15230d2b1b3893e272106aa79efc50251;p=clang Add support for C++ namespace-aware typo correction, e.g., correcting vector to std::vector Patch by Kaelyn Uhrain, with minor tweaks + PCH support from me. Fixes PR5776/. Thanks Kaelyn! git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@134007 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Sema/ExternalSemaSource.h b/include/clang/Sema/ExternalSemaSource.h index e2b083e830..072e1b58d1 100644 --- a/include/clang/Sema/ExternalSemaSource.h +++ b/include/clang/Sema/ExternalSemaSource.h @@ -49,6 +49,11 @@ public: /// instance and factory methods, respectively, with this selector. virtual std::pair ReadMethodPool(Selector Sel); + /// \brief Load the set of namespaces that are known to the external source, + /// which will be used during typo correction. + virtual void ReadKnownNamespaces( + llvm::SmallVectorImpl &Namespaces); + /// \brief Do last resort, unqualified lookup on a LookupResult that /// Sema cannot find. /// diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 2e51117f26..65ce34a7a2 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -21,6 +21,7 @@ #include "clang/Sema/ObjCMethodList.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/LocInfoType.h" +#include "clang/Sema/TypoCorrection.h" #include "clang/AST/Expr.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/ExternalASTSource.h" @@ -46,6 +47,8 @@ namespace clang { class ASTConsumer; class ASTContext; class ASTMutationListener; + class ASTReader; + class ASTWriter; class ArrayType; class AttributeList; class BlockDecl; @@ -1623,6 +1626,16 @@ private: bool ConstThis, bool VolatileThis); + // \brief The set of known/encountered (unique, canonicalized) NamespaceDecls. + // + // The boolean value will be true to indicate that the namespace was loaded + // from an AST/PCH file, or false otherwise. + llvm::DenseMap KnownNamespaces; + + /// \brief Whether we have already loaded known namespaces from an extenal + /// source. + bool LoadedExternalKnownNamespaces; + public: /// \brief Look up a name, looking for a single declaration. Return /// null if the results were absent, ambiguous, or overloaded. @@ -1699,11 +1712,13 @@ public: CTC_ObjCMessageReceiver }; - DeclarationName CorrectTypo(LookupResult &R, Scope *S, CXXScopeSpec *SS, - DeclContext *MemberContext = 0, - bool EnteringContext = false, - CorrectTypoContext CTC = CTC_Unknown, - const ObjCObjectPointerType *OPT = 0); + TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, + Sema::LookupNameKind LookupKind, + Scope *S, CXXScopeSpec *SS, + DeclContext *MemberContext = NULL, + bool EnteringContext = false, + CorrectTypoContext CTC = CTC_Unknown, + const ObjCObjectPointerType *OPT = NULL); void FindAssociatedClassesAndNamespaces(Expr **Args, unsigned NumArgs, AssociatedNamespaceSet &AssociatedNamespaces, @@ -4721,7 +4736,7 @@ public: /// \brief The number of typos corrected by CorrectTypo. unsigned TyposCorrected; - typedef llvm::DenseMap > + typedef llvm::DenseMap UnqualifiedTyposCorrectedMap; /// \brief A cache containing the results of typo correction for unqualified @@ -5918,6 +5933,8 @@ private: protected: friend class Parser; friend class InitializationSequence; + friend class ASTReader; + friend class ASTWriter; public: /// \brief Retrieve the parser's current scope. diff --git a/include/clang/Sema/TypoCorrection.h b/include/clang/Sema/TypoCorrection.h new file mode 100644 index 0000000000..9965953538 --- /dev/null +++ b/include/clang/Sema/TypoCorrection.h @@ -0,0 +1,105 @@ +//===--- TypoCorrection.h - Class for typo correction results ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the TypoCorrection class, which stores the results of +// Sema's typo correction (Sema::CorrectTypo). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SEMA_TYPOCORRECTION_H +#define LLVM_CLANG_SEMA_TYPOCORRECTION_H + +#include "clang/AST/DeclCXX.h" + +namespace clang { + +/// @brief Simple class containing the result of Sema::CorrectTypo +class TypoCorrection { +public: + TypoCorrection(const DeclarationName &Name, NamedDecl *NameDecl, + NestedNameSpecifier *NNS=NULL, unsigned distance=0) + : CorrectionName(Name), + CorrectionNameSpec(NNS), + CorrectionDecl(NameDecl), + EditDistance(distance) {} + + TypoCorrection(NamedDecl *Name, NestedNameSpecifier *NNS=NULL, + unsigned distance=0) + : CorrectionName(Name->getDeclName()), + CorrectionNameSpec(NNS), + CorrectionDecl(Name), + EditDistance(distance) {} + + TypoCorrection(DeclarationName Name, NestedNameSpecifier *NNS=NULL, + unsigned distance=0) + : CorrectionName(Name), + CorrectionNameSpec(NNS), + CorrectionDecl(NULL), + EditDistance(distance) {} + + TypoCorrection() + : CorrectionName(), CorrectionNameSpec(NULL), CorrectionDecl(NULL), + EditDistance(0) {} + + /// \brief Gets the DeclarationName of the typo correction + DeclarationName getCorrection() const { return CorrectionName; } + IdentifierInfo* getCorrectionAsIdentifierInfo() const { + return CorrectionName.getAsIdentifierInfo(); + } + + /// \brief Gets the NestedNameSpecifier needed to use the typo correction + NestedNameSpecifier* getCorrectionSpecifier() const { + return CorrectionNameSpec; + } + void setCorrectionSpecifier(NestedNameSpecifier* NNS) { + CorrectionNameSpec = NNS; + } + + /// \brief Gets the "edit distance" of the typo correction from the typo + unsigned getEditDistance() const { return EditDistance; } + + /// \brief Gets the pointer to the declaration of the typo correction + NamedDecl* getCorrectionDecl() const { + return isKeyword() ? NULL : CorrectionDecl; + } + template + DeclClass *getCorrectionDeclAs() const { + return dyn_cast_or_null(getCorrectionDecl()); + } + + void setCorrectionDecl(NamedDecl *CDecl) { + CorrectionDecl = CDecl; + if (!CorrectionName) + CorrectionName = CDecl->getDeclName(); + } + + std::string getAsString(const LangOptions &LO) const; + std::string getQuoted(const LangOptions &LO) const { + return "'" + getAsString(LO) + "'"; + } + + operator bool() const { return bool(CorrectionName); } + + static inline NamedDecl *KeywordDecl() { return (NamedDecl*)-1; } + bool isKeyword() const { return CorrectionDecl == KeywordDecl(); } + + // Returns true if the correction either is a keyword or has a known decl. + bool isResolved() const { return CorrectionDecl != NULL; } + +private: + // Results. + DeclarationName CorrectionName; + NestedNameSpecifier *CorrectionNameSpec; + NamedDecl *CorrectionDecl; + unsigned EditDistance; +}; + +} + +#endif diff --git a/include/clang/Serialization/ASTBitCodes.h b/include/clang/Serialization/ASTBitCodes.h index 15fe3c61ae..a4e90aa265 100644 --- a/include/clang/Serialization/ASTBitCodes.h +++ b/include/clang/Serialization/ASTBitCodes.h @@ -371,7 +371,11 @@ namespace clang { /// \brief Record code for the table of offsets into the block /// of file source-location information. - FILE_SOURCE_LOCATION_OFFSETS = 45 + FILE_SOURCE_LOCATION_OFFSETS = 45, + + /// \brief Record code for the set of known namespaces, which are used + /// for typo correction. + KNOWN_NAMESPACES = 46 }; diff --git a/include/clang/Serialization/ASTReader.h b/include/clang/Serialization/ASTReader.h index 8923e2ab0d..a6c32aafd3 100644 --- a/include/clang/Serialization/ASTReader.h +++ b/include/clang/Serialization/ASTReader.h @@ -625,6 +625,9 @@ private: /// \brief The OpenCL extension settings. llvm::SmallVector OpenCLExtensions; + /// \brief A list of the namespaces we've seen. + llvm::SmallVector KnownNamespaces; + //@} /// \brief Diagnostic IDs and their mappings that the user changed. @@ -1125,6 +1128,11 @@ public: virtual std::pair ReadMethodPool(Selector Sel); + /// \brief Load the set of namespaces that are known to the external source, + /// which will be used during typo correction. + virtual void ReadKnownNamespaces( + llvm::SmallVectorImpl &Namespaces); + /// \brief Load a selector from disk, registering its ID if it exists. void LoadSelector(Selector Sel); diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp index 7d22031ecb..9550f9aad2 100644 --- a/lib/Sema/Sema.cpp +++ b/lib/Sema/Sema.cpp @@ -154,6 +154,8 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, AnalysisWarnings(*this) { TUScope = 0; + LoadedExternalKnownNamespaces = false; + if (getLangOptions().CPlusPlus) FieldCollector.reset(new CXXFieldCollector()); @@ -778,6 +780,10 @@ ExternalSemaSource::ReadMethodPool(Selector Sel) { return std::pair(); } +void ExternalSemaSource::ReadKnownNamespaces( + llvm::SmallVectorImpl &Namespaces) { +} + void PrettyDeclStackTraceEntry::print(llvm::raw_ostream &OS) const { SourceLocation Loc = this->Loc; if (!Loc.isValid() && TheDecl) Loc = TheDecl->getLocation(); diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp index 61d9e93f2f..e54857196a 100644 --- a/lib/Sema/SemaCXXScopeSpec.cpp +++ b/lib/Sema/SemaCXXScopeSpec.cpp @@ -464,26 +464,29 @@ bool 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, - CTC_NoKeywords) && - Found.isSingleResult() && - isAcceptableNestedNameSpecifier(Found.getAsSingle())) { + TypoCorrection Corrected; + Found.clear(); + if ((Corrected = CorrectTypo(Found.getLookupNameInfo(), + Found.getLookupKind(), S, &SS, LookupCtx, + EnteringContext, CTC_NoKeywords)) && + isAcceptableNestedNameSpecifier(Corrected.getCorrectionDecl())) { + std::string CorrectedStr(Corrected.getAsString(getLangOptions())); + std::string CorrectedQuotedStr(Corrected.getQuoted(getLangOptions())); if (LookupCtx) Diag(Found.getNameLoc(), diag::err_no_member_suggest) - << Name << LookupCtx << Found.getLookupName() << SS.getRange() - << FixItHint::CreateReplacement(Found.getNameLoc(), - Found.getLookupName().getAsString()); + << Name << LookupCtx << CorrectedQuotedStr << SS.getRange() + << FixItHint::CreateReplacement(Found.getNameLoc(), CorrectedStr); else Diag(Found.getNameLoc(), diag::err_undeclared_var_use_suggest) - << Name << Found.getLookupName() - << FixItHint::CreateReplacement(Found.getNameLoc(), - Found.getLookupName().getAsString()); + << Name << CorrectedQuotedStr + << FixItHint::CreateReplacement(Found.getNameLoc(), CorrectedStr); - if (NamedDecl *ND = Found.getAsSingle()) - Diag(ND->getLocation(), diag::note_previous_decl) - << ND->getDeclName(); + if (NamedDecl *ND = Corrected.getCorrectionDecl()) { + Diag(ND->getLocation(), diag::note_previous_decl) << CorrectedQuotedStr; + Found.addDecl(ND); + } + Found.setLookupName(Corrected.getCorrection()); } else { - Found.clear(); Found.setLookupName(&Identifier); } } diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index efb06f6687..2779faeb15 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -287,41 +287,42 @@ bool Sema::DiagnoseUnknownTypeName(const IdentifierInfo &II, // There may have been a typo in the name of the type. Look up typo // results, in case we have something that we can suggest. - LookupResult Lookup(*this, &II, IILoc, LookupOrdinaryName, - NotForRedeclaration); + if (TypoCorrection Corrected = CorrectTypo(DeclarationNameInfo(&II, IILoc), + LookupOrdinaryName, S, SS, NULL, + false, CTC_Type)) { + std::string CorrectedStr(Corrected.getAsString(getLangOptions())); + std::string CorrectedQuotedStr(Corrected.getQuoted(getLangOptions())); - if (DeclarationName Corrected = CorrectTypo(Lookup, S, SS, 0, 0, CTC_Type)) { - if (NamedDecl *Result = Lookup.getAsSingle()) { + if (Corrected.isKeyword()) { + // 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 << CorrectedQuotedStr; + return true; + } else { + NamedDecl *Result = Corrected.getCorrectionDecl(); 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()); + << &II << CorrectedQuotedStr + << FixItHint::CreateReplacement(SourceRange(IILoc), CorrectedStr); 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()); + << &II << DC << CorrectedQuotedStr << SS->getRange() + << FixItHint::CreateReplacement(SourceRange(IILoc), CorrectedStr); else llvm_unreachable("could not have corrected a typo here"); Diag(Result->getLocation(), diag::note_previous_decl) - << Result->getDeclName(); + << CorrectedQuotedStr; SuggestedType = getTypeName(*Result->getIdentifier(), IILoc, S, SS, false, false, ParsedType(), /*NonTrivialTypeSourceInfo=*/true); 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; } } @@ -509,11 +510,14 @@ Corrected: // Perform typo correction to determine if there is another name that is // close to this name. if (!SecondTry) { - if (DeclarationName Corrected = CorrectTypo(Result, S, &SS)) { + if (TypoCorrection Corrected = CorrectTypo(Result.getLookupNameInfo(), + Result.getLookupKind(), S, &SS)) { unsigned UnqualifiedDiag = diag::err_undeclared_var_use_suggest; unsigned QualifiedDiag = diag::err_no_member_suggest; + std::string CorrectedStr(Corrected.getAsString(getLangOptions())); + std::string CorrectedQuotedStr(Corrected.getQuoted(getLangOptions())); - NamedDecl *FirstDecl = Result.empty()? 0 : *Result.begin(); + NamedDecl *FirstDecl = Corrected.getCorrectionDecl(); NamedDecl *UnderlyingFirstDecl = FirstDecl? FirstDecl->getUnderlyingDecl() : 0; if (getLangOptions().CPlusPlus && NextToken.is(tok::less) && @@ -528,25 +532,34 @@ Corrected: QualifiedDiag = diag::err_unknown_nested_typename_suggest; } + if (Corrected.getCorrectionSpecifier()) + SS.MakeTrivial(Context, Corrected.getCorrectionSpecifier(), SourceRange(NameLoc)); + if (SS.isEmpty()) Diag(NameLoc, UnqualifiedDiag) - << Name << Corrected - << FixItHint::CreateReplacement(NameLoc, Corrected.getAsString()); + << Name << CorrectedQuotedStr + << FixItHint::CreateReplacement(NameLoc, CorrectedStr); else Diag(NameLoc, QualifiedDiag) - << Name << computeDeclContext(SS, false) << Corrected + << Name << computeDeclContext(SS, false) << CorrectedQuotedStr << SS.getRange() - << FixItHint::CreateReplacement(NameLoc, Corrected.getAsString()); + << FixItHint::CreateReplacement(NameLoc, CorrectedStr); // Update the name, so that the caller has the new name. - Name = Corrected.getAsIdentifierInfo(); + Name = Corrected.getCorrectionAsIdentifierInfo(); + // Also update the LookupResult... + // FIXME: This should probably go away at some point + Result.clear(); + Result.setLookupName(Corrected.getCorrection()); + if (FirstDecl) Result.addDecl(FirstDecl); + // Typo correction corrected to a keyword. - if (Result.empty()) - return Corrected.getAsIdentifierInfo(); + if (Corrected.isKeyword()) + return Corrected.getCorrectionAsIdentifierInfo(); Diag(FirstDecl->getLocation(), diag::note_previous_decl) - << FirstDecl->getDeclName(); + << CorrectedQuotedStr; // If we found an Objective-C instance variable, let // LookupInObjCMethod build the appropriate expression to @@ -1137,17 +1150,18 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { /// class could not be found. ObjCInterfaceDecl *Sema::getObjCInterfaceDecl(IdentifierInfo *&Id, SourceLocation IdLoc, - bool TypoCorrection) { + bool DoTypoCorrection) { // The third "scope" argument is 0 since we aren't enabling lazy built-in // creation from this context. NamedDecl *IDecl = LookupSingleName(TUScope, Id, IdLoc, LookupOrdinaryName); - if (!IDecl && TypoCorrection) { + if (!IDecl && DoTypoCorrection) { // Perform typo correction at the given location, but only if we // find an Objective-C class name. - LookupResult R(*this, Id, IdLoc, LookupOrdinaryName); - if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) && - (IDecl = R.getAsSingle())) { + TypoCorrection C; + if ((C = CorrectTypo(DeclarationNameInfo(Id, IdLoc), LookupOrdinaryName, + TUScope, NULL, NULL, false, CTC_NoKeywords)) && + (IDecl = C.getCorrectionDeclAs())) { Diag(IdLoc, diag::err_undef_interface_suggest) << Id << IDecl->getDeclName() << FixItHint::CreateReplacement(IdLoc, IDecl->getNameAsString()); diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index c0d34da922..ac51138b91 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -1426,25 +1426,27 @@ Sema::ActOnMemInitializer(Decl *ConstructorD, } // If no results were found, try to correct typos. + TypoCorrection Corr; if (R.empty() && BaseType.isNull() && - CorrectTypo(R, S, &SS, ClassDecl, 0, CTC_NoKeywords) && - R.isSingleResult()) { - if (FieldDecl *Member = R.getAsSingle()) { + (Corr = CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), S, &SS, + ClassDecl, false, CTC_NoKeywords))) { + std::string CorrectedStr(Corr.getAsString(getLangOptions())); + std::string CorrectedQuotedStr(Corr.getQuoted(getLangOptions())); + if (FieldDecl *Member = Corr.getCorrectionDeclAs()) { if (Member->getDeclContext()->getRedeclContext()->Equals(ClassDecl)) { // We have found a non-static data member with a similar // name to what was typed; complain and initialize that // member. Diag(R.getNameLoc(), diag::err_mem_init_not_member_or_class_suggest) - << MemberOrBase << true << R.getLookupName() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); + << MemberOrBase << true << CorrectedQuotedStr + << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); Diag(Member->getLocation(), diag::note_previous_decl) - << Member->getDeclName(); + << CorrectedQuotedStr; return BuildMemberInitializer(Member, (Expr**)Args, NumArgs, IdLoc, LParenLoc, RParenLoc); } - } else if (TypeDecl *Type = R.getAsSingle()) { + } else if (TypeDecl *Type = Corr.getCorrectionDeclAs()) { const CXXBaseSpecifier *DirectBaseSpec; const CXXBaseSpecifier *VirtualBaseSpec; if (FindBaseInitializer(*this, ClassDecl, @@ -1454,9 +1456,8 @@ Sema::ActOnMemInitializer(Decl *ConstructorD, // similar name to what was typed; complain and initialize // that base class. Diag(R.getNameLoc(), diag::err_mem_init_not_member_or_class_suggest) - << MemberOrBase << false << R.getLookupName() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); + << MemberOrBase << false << CorrectedQuotedStr + << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); const CXXBaseSpecifier *BaseSpec = DirectBaseSpec? DirectBaseSpec : VirtualBaseSpec; @@ -4706,6 +4707,13 @@ Decl *Sema::ActOnStartNamespaceDef(Scope *NamespcScope, // Make our StdNamespace cache point at the first real definition of the // "std" namespace. StdNamespace = Namespc; + + // Add this instance of "std" to the set of known namespaces + KnownNamespaces[Namespc] = false; + } else if (!Namespc->isInline()) { + // Since this is an "original" namespace, add it to the known set of + // namespaces if it is not an inline namespace. + KnownNamespaces[Namespc] = false; } PushOnScopeChains(Namespc, DeclRegionScope); @@ -4841,6 +4849,39 @@ static bool IsUsingDirectiveInToplevelContext(DeclContext *CurContext) { } } +static bool TryNamespaceTypoCorrection(Sema &S, LookupResult &R, Scope *Sc, + CXXScopeSpec &SS, + SourceLocation IdentLoc, + IdentifierInfo *Ident) { + R.clear(); + if (TypoCorrection Corrected = S.CorrectTypo(R.getLookupNameInfo(), + R.getLookupKind(), Sc, &SS, NULL, + false, S.CTC_NoKeywords, NULL)) { + if (Corrected.getCorrectionDeclAs() || + Corrected.getCorrectionDeclAs()) { + std::string CorrectedStr(Corrected.getAsString(S.getLangOptions())); + std::string CorrectedQuotedStr(Corrected.getQuoted(S.getLangOptions())); + if (DeclContext *DC = S.computeDeclContext(SS, false)) + S.Diag(IdentLoc, diag::err_using_directive_member_suggest) + << Ident << DC << CorrectedQuotedStr << SS.getRange() + << FixItHint::CreateReplacement(IdentLoc, CorrectedStr); + else + S.Diag(IdentLoc, diag::err_using_directive_suggest) + << Ident << CorrectedQuotedStr + << FixItHint::CreateReplacement(IdentLoc, CorrectedStr); + + S.Diag(Corrected.getCorrectionDecl()->getLocation(), + diag::note_namespace_defined_here) << CorrectedQuotedStr; + + Ident = Corrected.getCorrectionAsIdentifierInfo(); + R.addDecl(Corrected.getCorrectionDecl()); + return true; + } + R.setLookupName(Ident); + } + return false; +} + Decl *Sema::ActOnUsingDirective(Scope *S, SourceLocation UsingLoc, SourceLocation NamespcLoc, @@ -4869,6 +4910,7 @@ Decl *Sema::ActOnUsingDirective(Scope *S, return 0; if (R.empty()) { + R.clear(); // Allow "using namespace std;" or "using namespace ::std;" even if // "std" hasn't been defined yet, for GCC compatibility. if ((!Qualifier || Qualifier->getKind() == NestedNameSpecifier::Global) && @@ -4878,27 +4920,7 @@ Decl *Sema::ActOnUsingDirective(Scope *S, R.resolveKind(); } // Otherwise, attempt typo correction. - else if (DeclarationName Corrected = CorrectTypo(R, S, &SS, 0, false, - CTC_NoKeywords, 0)) { - if (R.getAsSingle() || - R.getAsSingle()) { - if (DeclContext *DC = computeDeclContext(SS, false)) - Diag(IdentLoc, diag::err_using_directive_member_suggest) - << NamespcName << DC << Corrected << SS.getRange() - << FixItHint::CreateReplacement(IdentLoc, Corrected.getAsString()); - else - Diag(IdentLoc, diag::err_using_directive_suggest) - << NamespcName << Corrected - << FixItHint::CreateReplacement(IdentLoc, Corrected.getAsString()); - Diag(R.getFoundDecl()->getLocation(), diag::note_namespace_defined_here) - << Corrected; - - NamespcName = Corrected.getAsIdentifierInfo(); - } else { - R.clear(); - R.setLookupName(NamespcName); - } - } + else TryNamespaceTypoCorrection(*this, R, S, SS, IdentLoc, NamespcName); } if (!R.empty()) { @@ -5816,30 +5838,7 @@ Decl *Sema::ActOnNamespaceAliasDef(Scope *S, return 0; if (R.empty()) { - if (DeclarationName Corrected = CorrectTypo(R, S, &SS, 0, false, - CTC_NoKeywords, 0)) { - if (R.getAsSingle() || - R.getAsSingle()) { - if (DeclContext *DC = computeDeclContext(SS, false)) - Diag(IdentLoc, diag::err_using_directive_member_suggest) - << Ident << DC << Corrected << SS.getRange() - << FixItHint::CreateReplacement(IdentLoc, Corrected.getAsString()); - else - Diag(IdentLoc, diag::err_using_directive_suggest) - << Ident << Corrected - << FixItHint::CreateReplacement(IdentLoc, Corrected.getAsString()); - - Diag(R.getFoundDecl()->getLocation(), diag::note_namespace_defined_here) - << Corrected; - - Ident = Corrected.getAsIdentifierInfo(); - } else { - R.clear(); - R.setLookupName(Ident); - } - } - - if (R.empty()) { + if (!TryNamespaceTypoCorrection(*this, R, S, SS, IdentLoc, Ident)) { Diag(NamespaceLoc, diag::err_expected_namespace_name) << SS.getRange(); return 0; } diff --git a/lib/Sema/SemaDeclObjC.cpp b/lib/Sema/SemaDeclObjC.cpp index a3f53ec234..a9f4f95c20 100644 --- a/lib/Sema/SemaDeclObjC.cpp +++ b/lib/Sema/SemaDeclObjC.cpp @@ -442,9 +442,10 @@ 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, 0, false, CTC_NoKeywords) && - (PrevDecl = R.getAsSingle())) { + TypoCorrection Corrected = CorrectTypo( + DeclarationNameInfo(SuperName, SuperLoc), LookupOrdinaryName, TUScope, + NULL, NULL, false, CTC_NoKeywords); + if ((PrevDecl = Corrected.getCorrectionDeclAs())) { Diag(SuperLoc, diag::err_undef_superclass_suggest) << SuperName << ClassName << PrevDecl->getDeclName(); Diag(PrevDecl->getLocation(), diag::note_previous_decl) @@ -655,12 +656,12 @@ Sema::FindProtocolDeclaration(bool WarnOnDeclarations, ObjCProtocolDecl *PDecl = LookupProtocol(ProtocolId[i].first, ProtocolId[i].second); if (!PDecl) { - LookupResult R(*this, ProtocolId[i].first, ProtocolId[i].second, - LookupObjCProtocolName); - if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) && - (PDecl = R.getAsSingle())) { + TypoCorrection Corrected = CorrectTypo( + DeclarationNameInfo(ProtocolId[i].first, ProtocolId[i].second), + LookupObjCProtocolName, TUScope, NULL, NULL, false, CTC_NoKeywords); + if ((PDecl = Corrected.getCorrectionDeclAs())) { Diag(ProtocolId[i].second, diag::err_undeclared_protocol_suggest) - << ProtocolId[i].first << R.getLookupName(); + << ProtocolId[i].first << Corrected.getCorrection(); Diag(PDecl->getLocation(), diag::note_previous_decl) << PDecl->getDeclName(); } @@ -897,20 +898,20 @@ Decl *Sema::ActOnStartClassImplementation( } else { // 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, 0, false, CTC_NoKeywords) && - (IDecl = R.getAsSingle())) { + TypoCorrection Corrected = CorrectTypo( + DeclarationNameInfo(ClassName, ClassLoc), LookupOrdinaryName, TUScope, + NULL, NULL, false, CTC_NoKeywords); + if ((IDecl = Corrected.getCorrectionDeclAs())) { // Suggest the (potentially) correct interface name. However, put the // fix-it hint itself in a separate note, since changing the name in // the warning would make the fix-it change semantics.However, don't // provide a code-modification hint or use the typo name for recovery, // because this is just a warning. The program may actually be correct. + DeclarationName CorrectedName = Corrected.getCorrection(); Diag(ClassLoc, diag::warn_undef_interface_suggest) - << ClassName << R.getLookupName(); - Diag(IDecl->getLocation(), diag::note_previous_decl) - << R.getLookupName() - << FixItHint::CreateReplacement(ClassLoc, - R.getLookupName().getAsString()); + << ClassName << CorrectedName; + Diag(IDecl->getLocation(), diag::note_previous_decl) << CorrectedName + << FixItHint::CreateReplacement(ClassLoc, CorrectedName.getAsString()); IDecl = 0; } else { Diag(ClassLoc, diag::warn_undef_interface) << ClassName; diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 3423d71af4..b99076005b 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -1397,39 +1397,44 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R, } // We didn't find anything, so try to correct for a typo. - DeclarationName Corrected; - if (S && (Corrected = CorrectTypo(R, S, &SS, 0, false, CTC))) { - if (!R.empty()) { - if (isa(*R.begin()) || isa(*R.begin())) { + TypoCorrection Corrected; + if (S && (Corrected = CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), + S, &SS, NULL, false, CTC))) { + std::string CorrectedStr(Corrected.getAsString(getLangOptions())); + std::string CorrectedQuotedStr(Corrected.getQuoted(getLangOptions())); + R.setLookupName(Corrected.getCorrection()); + + if (!Corrected.isKeyword()) { + NamedDecl *ND = Corrected.getCorrectionDecl(); + R.addDecl(ND); + if (isa(ND) || isa(ND)) { if (SS.isEmpty()) - Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); + Diag(R.getNameLoc(), diagnostic_suggest) << Name << CorrectedQuotedStr + << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); else Diag(R.getNameLoc(), diag::err_no_member_suggest) - << Name << computeDeclContext(SS, false) << R.getLookupName() + << Name << computeDeclContext(SS, false) << CorrectedQuotedStr << SS.getRange() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); - if (NamedDecl *ND = R.getAsSingle()) + << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); + if (ND) Diag(ND->getLocation(), diag::note_previous_decl) - << ND->getDeclName(); + << CorrectedQuotedStr; // Tell the callee to try to recover. return false; } - if (isa(*R.begin()) || isa(*R.begin())) { + if (isa(ND) || isa(ND)) { // 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(); + Diag(R.getNameLoc(), diagnostic_suggest) << Name << CorrectedQuotedStr; else Diag(R.getNameLoc(), diag::err_no_member_suggest) - << Name << computeDeclContext(SS, false) << R.getLookupName() + << Name << computeDeclContext(SS, false) << CorrectedQuotedStr << SS.getRange(); // Don't try to recover; it won't work. @@ -1439,15 +1444,15 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R, // 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 << Corrected; + Diag(R.getNameLoc(), diagnostic_suggest) << Name << CorrectedQuotedStr; else Diag(R.getNameLoc(), diag::err_no_member_suggest) - << Name << computeDeclContext(SS, false) << Corrected + << Name << computeDeclContext(SS, false) << CorrectedQuotedStr << SS.getRange(); return true; } - R.clear(); } + R.clear(); // Emit a special diagnostic for failed member lookups. // FIXME: computing the declaration context might fail here (?) diff --git a/lib/Sema/SemaExprMember.cpp b/lib/Sema/SemaExprMember.cpp index 9509768205..2488dc8849 100644 --- a/lib/Sema/SemaExprMember.cpp +++ b/lib/Sema/SemaExprMember.cpp @@ -555,20 +555,24 @@ 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, false, Sema::CTC_MemberLookup) && - !R.empty() && - (isa(*R.begin()) || isa(*R.begin()))) { + TypoCorrection Corrected = SemaRef.CorrectTypo(R.getLookupNameInfo(), + R.getLookupKind(), NULL, + &SS, DC, false, + Sema::CTC_MemberLookup); + NamedDecl *ND = Corrected.getCorrectionDecl(); + R.clear(); + if (ND && (isa(ND) || isa(ND))) { + std::string CorrectedStr( + Corrected.getAsString(SemaRef.getLangOptions())); + std::string CorrectedQuotedStr( + Corrected.getQuoted(SemaRef.getLangOptions())); + R.setLookupName(Corrected.getCorrection()); + R.addDecl(ND); SemaRef.Diag(R.getNameLoc(), diag::err_no_member_suggest) - << Name << DC << R.getLookupName() << SS.getRange() - << FixItHint::CreateReplacement(R.getNameLoc(), - R.getLookupName().getAsString()); - if (NamedDecl *ND = R.getAsSingle()) - SemaRef.Diag(ND->getLocation(), diag::note_previous_decl) - << ND->getDeclName(); - return false; - } else { - R.clear(); - R.setLookupName(Name); + << Name << DC << CorrectedQuotedStr << SS.getRange() + << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); + SemaRef.Diag(ND->getLocation(), diag::note_previous_decl) + << ND->getDeclName(); } return false; @@ -1068,10 +1072,10 @@ Sema::LookupMemberExpr(LookupResult &R, ExprResult &BaseExpr, // Attempt to correct for typos in ivar names. LookupResult Res(*this, R.getLookupName(), R.getNameLoc(), LookupMemberName); - if (CorrectTypo(Res, 0, 0, IDecl, false, - IsArrow ? CTC_ObjCIvarLookup - : CTC_ObjCPropertyLookup) && - (IV = Res.getAsSingle())) { + TypoCorrection Corrected = CorrectTypo( + R.getLookupNameInfo(), LookupMemberName, NULL, NULL, IDecl, false, + IsArrow ? CTC_ObjCIvarLookup : CTC_ObjCPropertyLookup); + if ((IV = Corrected.getCorrectionDeclAs())) { Diag(R.getNameLoc(), diag::err_typecheck_member_reference_ivar_suggest) << IDecl->getDeclName() << MemberName << IV->getDeclName() diff --git a/lib/Sema/SemaExprObjC.cpp b/lib/Sema/SemaExprObjC.cpp index 80d3a7451d..c02b634547 100644 --- a/lib/Sema/SemaExprObjC.cpp +++ b/lib/Sema/SemaExprObjC.cpp @@ -663,14 +663,15 @@ 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, CTC_NoKeywords, OPT) && - Res.getAsSingle()) { - DeclarationName TypoResult = Res.getLookupName(); + TypoCorrection Corrected = CorrectTypo( + DeclarationNameInfo(MemberName, MemberLoc), LookupOrdinaryName, NULL, + NULL, IFace, false, CTC_NoKeywords, OPT); + if (ObjCPropertyDecl *Property = + Corrected.getCorrectionDeclAs()) { + DeclarationName TypoResult = Corrected.getCorrection(); Diag(MemberLoc, diag::err_property_not_found_suggest) << MemberName << QualType(OPT, 0) << TypoResult << FixItHint::CreateReplacement(MemberLoc, TypoResult.getAsString()); - ObjCPropertyDecl *Property = Res.getAsSingle(); Diag(Property->getLocation(), diag::note_previous_decl) << Property->getDeclName(); return HandleExprPropertyRefExpr(OPT, BaseExpr, OpLoc, @@ -898,29 +899,30 @@ Sema::ObjCMessageKind Sema::getObjCMessageKind(Scope *S, Method->getClassInterface()->getSuperClass()) CTC = CTC_ObjCMessageReceiver; - if (DeclarationName Corrected = CorrectTypo(Result, S, 0, 0, false, CTC)) { - if (Result.isSingleResult()) { + if (TypoCorrection Corrected = CorrectTypo(Result.getLookupNameInfo(), + Result.getLookupKind(), S, NULL, + NULL, false, CTC)) { + if (NamedDecl *ND = Corrected.getCorrectionDecl()) { // If we found a declaration, correct when it refers to an Objective-C // class. - NamedDecl *ND = Result.getFoundDecl(); if (ObjCInterfaceDecl *Class = dyn_cast(ND)) { Diag(NameLoc, diag::err_unknown_receiver_suggest) - << Name << Result.getLookupName() + << Name << Corrected.getCorrection() << FixItHint::CreateReplacement(SourceRange(NameLoc), ND->getNameAsString()); Diag(ND->getLocation(), diag::note_previous_decl) - << Corrected; + << Corrected.getCorrection(); QualType T = Context.getObjCInterfaceType(Class); TypeSourceInfo *TSInfo = Context.getTrivialTypeSourceInfo(T, NameLoc); ReceiverType = CreateParsedType(T, TSInfo); return ObjCClassMessage; } - } else if (Result.empty() && Corrected.getAsIdentifierInfo() && - Corrected.getAsIdentifierInfo()->isStr("super")) { + } else if (Corrected.isKeyword() && + Corrected.getCorrectionAsIdentifierInfo()->isStr("super")) { // If we've found the keyword "super", this is a send to super. Diag(NameLoc, diag::err_unknown_receiver_suggest) - << Name << Corrected + << Name << Corrected.getCorrection() << FixItHint::CreateReplacement(SourceRange(NameLoc), "super"); return ObjCSuperMessage; } diff --git a/lib/Sema/SemaInit.cpp b/lib/Sema/SemaInit.cpp index 16ba2a2910..da20e0e8fd 100644 --- a/lib/Sema/SemaInit.cpp +++ b/lib/Sema/SemaInit.cpp @@ -1443,19 +1443,23 @@ 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(), false, - Sema::CTC_NoKeywords) && - (ReplacementField = R.getAsSingle()) && + TypoCorrection Corrected = SemaRef.CorrectTypo( + DeclarationNameInfo(FieldName, D->getFieldLoc()), + Sema::LookupMemberName, /*Scope=*/NULL, /*SS=*/NULL, + RT->getDecl(), false, Sema::CTC_NoKeywords); + if ((ReplacementField = Corrected.getCorrectionDeclAs()) && ReplacementField->getDeclContext()->getRedeclContext() ->Equals(RT->getDecl())) { + std::string CorrectedStr( + Corrected.getAsString(SemaRef.getLangOptions())); + std::string CorrectedQuotedStr( + Corrected.getQuoted(SemaRef.getLangOptions())); SemaRef.Diag(D->getFieldLoc(), diag::err_field_designator_unknown_suggest) - << FieldName << CurrentObjectType << R.getLookupName() - << FixItHint::CreateReplacement(D->getFieldLoc(), - R.getLookupName().getAsString()); + << FieldName << CurrentObjectType << CorrectedQuotedStr + << FixItHint::CreateReplacement(D->getFieldLoc(), CorrectedStr); SemaRef.Diag(ReplacementField->getLocation(), - diag::note_previous_decl) - << ReplacementField->getDeclName(); + diag::note_previous_decl) << CorrectedQuotedStr; } else { SemaRef.Diag(D->getFieldLoc(), diag::err_field_designator_unknown) << FieldName << CurrentObjectType; diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index e09ec66457..0c1a1fa216 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -20,6 +20,7 @@ #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/TemplateDeduction.h" #include "clang/Sema/ExternalSemaSource.h" +#include "clang/Sema/TypoCorrection.h" #include "clang/AST/ASTContext.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" @@ -42,6 +43,7 @@ #include #include #include +#include using namespace clang; using namespace sema; @@ -3041,6 +3043,12 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc, //===----------------------------------------------------------------------===// namespace { + +typedef llvm::StringMap TypoResultsMap; +typedef std::map TypoEditDistanceMap; + +static const unsigned MaxTypoDistanceResultSets = 5; + class TypoCorrectionConsumer : public VisibleDeclConsumer { /// \brief The name written that is a typo in the source. llvm::StringRef Typo; @@ -3048,33 +3056,47 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer { /// \brief The results found that have the smallest edit distance /// found (so far) with the typo name. /// - /// The boolean value indicates whether there is a keyword with this name. - llvm::StringMap BestResults; + /// The pointer value being set to the current DeclContext indicates + /// whether there is a keyword with this name. + TypoEditDistanceMap BestResults; - /// \brief The best edit distance found so far. - unsigned BestEditDistance; + /// \brief The worst of the best N edit distances found so far. + unsigned MaxEditDistance; + + Sema &SemaRef; public: - explicit TypoCorrectionConsumer(IdentifierInfo *Typo) + explicit TypoCorrectionConsumer(Sema &SemaRef, IdentifierInfo *Typo) : Typo(Typo->getName()), - BestEditDistance((std::numeric_limits::max)()) { } + MaxEditDistance((std::numeric_limits::max)()), + SemaRef(SemaRef) { } virtual void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, bool InBaseClass); void FoundName(llvm::StringRef Name); - void addKeywordResult(ASTContext &Context, llvm::StringRef Keyword); - - typedef llvm::StringMap::iterator iterator; - iterator begin() { return BestResults.begin(); } - iterator end() { return BestResults.end(); } - void erase(iterator I) { BestResults.erase(I); } + void addKeywordResult(llvm::StringRef Keyword); + void addName(llvm::StringRef Name, NamedDecl *ND, unsigned Distance, + NestedNameSpecifier *NNS=NULL); + void addCorrection(TypoCorrection Correction); + + typedef TypoResultsMap::iterator result_iterator; + typedef TypoEditDistanceMap::iterator distance_iterator; + distance_iterator begin() { return BestResults.begin(); } + distance_iterator end() { return BestResults.end(); } + void erase(distance_iterator I) { BestResults.erase(I); } unsigned size() const { return BestResults.size(); } bool empty() const { return BestResults.empty(); } - bool &operator[](llvm::StringRef Name) { - return BestResults[Name]; + TypoCorrection &operator[](llvm::StringRef Name) { + return BestResults.begin()->second[Name]; } - unsigned getBestEditDistance() const { return BestEditDistance; } + unsigned getMaxEditDistance() const { + return MaxEditDistance; + } + + unsigned getBestEditDistance() { + return (BestResults.empty()) ? MaxEditDistance : BestResults.begin()->first; + } }; } @@ -3099,55 +3121,164 @@ void TypoCorrectionConsumer::FoundName(llvm::StringRef Name) { // Use a simple length-based heuristic to determine the minimum possible // edit distance. If the minimum isn't good enough, bail out early. unsigned MinED = abs((int)Name.size() - (int)Typo.size()); - if (MinED > BestEditDistance || (MinED && Typo.size() / MinED < 3)) + if (MinED > MaxEditDistance || (MinED && Typo.size() / MinED < 3)) return; // Compute an upper bound on the allowable edit distance, so that the // edit-distance algorithm can short-circuit. unsigned UpperBound = - std::min(unsigned((Typo.size() + 2) / 3), BestEditDistance); + std::min(unsigned((Typo.size() + 2) / 3), MaxEditDistance); // Compute the edit distance between the typo and the name of this // 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, true, UpperBound); - if (ED == 0) - return; - if (ED < BestEditDistance) { - // This result is better than any we've seen before; clear out - // the previous results. - BestResults.clear(); - BestEditDistance = ED; - } else if (ED > BestEditDistance) { + if (ED > MaxEditDistance) { // This result is worse than the best results we've seen so far; // ignore it. return; } - // Add this name to the list of results. By not assigning a value, we - // keep the current value if we've seen this name before (either as a - // keyword or as a declaration), or get the default value (not a keyword) - // if we haven't seen it before. - (void)BestResults[Name]; + addName(Name, NULL, ED); } -void TypoCorrectionConsumer::addKeywordResult(ASTContext &Context, - llvm::StringRef Keyword) { +void TypoCorrectionConsumer::addKeywordResult(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 (ED < BestEditDistance) { - BestResults.clear(); - BestEditDistance = ED; - } else if (ED > BestEditDistance) { + if (ED > MaxEditDistance) { // This result is worse than the best results we've seen so far; // ignore it. return; } - BestResults[Keyword] = true; + addName(Keyword, TypoCorrection::KeywordDecl(), ED); +} + +void TypoCorrectionConsumer::addName(llvm::StringRef Name, + NamedDecl *ND, + unsigned Distance, + NestedNameSpecifier *NNS) { + addCorrection(TypoCorrection(&SemaRef.Context.Idents.get(Name), + ND, NNS, Distance)); +} + +void TypoCorrectionConsumer::addCorrection(TypoCorrection Correction) { + llvm::StringRef Name = Correction.getCorrectionAsIdentifierInfo()->getName(); + BestResults[Correction.getEditDistance()][Name] = Correction; + + while (BestResults.size() > MaxTypoDistanceResultSets) { + BestResults.erase(--BestResults.end()); + } +} + +namespace { + +class SpecifierInfo { + public: + DeclContext* DeclCtx; + NestedNameSpecifier* NameSpecifier; + unsigned EditDistance; + + SpecifierInfo(DeclContext *Ctx, NestedNameSpecifier *NNS, unsigned ED) + : DeclCtx(Ctx), NameSpecifier(NNS), EditDistance(ED) {} +}; + +typedef llvm::SmallVector DeclContextList; +typedef llvm::SmallVector SpecifierInfoList; + +class NamespaceSpecifierSet { + ASTContext &Context; + DeclContextList CurContextChain; + bool isSorted; + + SpecifierInfoList Specifiers; + llvm::SmallSetVector Distances; + llvm::DenseMap DistanceMap; + + /// \brief Helper for building the list of DeclContexts between the current + /// context and the top of the translation unit + static DeclContextList BuildContextChain(DeclContext *Start); + + void SortNamespaces(); + + public: + explicit NamespaceSpecifierSet(ASTContext &Context, DeclContext *CurContext) + : Context(Context), CurContextChain(BuildContextChain(CurContext)) {} + + /// \brief Add the namespace to the set, computing the corresponding + /// NestedNameSpecifier and its distance in the process. + void AddNamespace(NamespaceDecl *ND); + + typedef SpecifierInfoList::iterator iterator; + iterator begin() { + if (!isSorted) SortNamespaces(); + return Specifiers.begin(); + } + iterator end() { return Specifiers.end(); } +}; + +} + +DeclContextList NamespaceSpecifierSet::BuildContextChain(DeclContext *Start) { + DeclContextList Chain; + for (DeclContext *DC = Start->getPrimaryContext(); DC != NULL; + DC = DC->getLookupParent()) { + NamespaceDecl *ND = dyn_cast_or_null(DC); + if (!DC->isInlineNamespace() && !DC->isTransparentContext() && + !(ND && ND->isAnonymousNamespace())) + Chain.push_back(DC->getPrimaryContext()); + } + return Chain; +} + +void NamespaceSpecifierSet::SortNamespaces() { + llvm::SmallVector sortedDistances; + sortedDistances.append(Distances.begin(), Distances.end()); + + if (sortedDistances.size() > 1) + std::sort(sortedDistances.begin(), sortedDistances.end()); + + Specifiers.clear(); + for (llvm::SmallVector::iterator DI = sortedDistances.begin(), + DIEnd = sortedDistances.end(); + DI != DIEnd; ++DI) { + SpecifierInfoList &SpecList = DistanceMap[*DI]; + Specifiers.append(SpecList.begin(), SpecList.end()); + } + + isSorted = true; +} + +void NamespaceSpecifierSet::AddNamespace(NamespaceDecl *ND) { + DeclContext *Ctx = dyn_cast(ND); + NestedNameSpecifier *NNS = NULL; + unsigned NumSpecifiers = 0; + DeclContextList NamespaceDeclChain(BuildContextChain(Ctx)); + + // Eliminate common elements from the two DeclContext chains + for (DeclContextList::reverse_iterator C = CurContextChain.rbegin(), + CEnd = CurContextChain.rend(); + C != CEnd && NamespaceDeclChain.back() == *C; ++C) { + NamespaceDeclChain.pop_back(); + } + + // Build the NestedNameSpecifier from what is left of the NamespaceDeclChain + for (DeclContextList::reverse_iterator C = NamespaceDeclChain.rbegin(), + CEnd = NamespaceDeclChain.rend(); + C != CEnd; ++C) { + NamespaceDecl *ND = dyn_cast_or_null(*C); + if (ND) { + NNS = NestedNameSpecifier::Create(Context, NNS, ND); + ++NumSpecifiers; + } + } + + isSorted = false; + Distances.insert(NumSpecifiers); + DistanceMap[NumSpecifiers].push_back(SpecifierInfo(Ctx, NNS, NumSpecifiers)); } /// \brief Perform name lookup for a possible result for typo correction. @@ -3201,14 +3332,191 @@ static void LookupPotentialTypoResult(Sema &SemaRef, } } +/// \brief Add keywords to the consumer as possible typo corrections. +static void AddKeywordsToConsumer(Sema &SemaRef, + TypoCorrectionConsumer &Consumer, + Scope *S, Sema::CorrectTypoContext CTC) { + // Add context-dependent keywords. + bool WantTypeSpecifiers = false; + bool WantExpressionKeywords = false; + bool WantCXXNamedCasts = false; + bool WantRemainingKeywords = false; + switch (CTC) { + case Sema::CTC_Unknown: + WantTypeSpecifiers = true; + WantExpressionKeywords = true; + WantCXXNamedCasts = true; + WantRemainingKeywords = true; + + if (ObjCMethodDecl *Method = SemaRef.getCurMethodDecl()) + if (Method->getClassInterface() && + Method->getClassInterface()->getSuperClass()) + Consumer.addKeywordResult("super"); + + break; + + case Sema::CTC_NoKeywords: + break; + + case Sema::CTC_Type: + WantTypeSpecifiers = true; + break; + + case Sema::CTC_ObjCMessageReceiver: + Consumer.addKeywordResult("super"); + // Fall through to handle message receivers like expressions. + + case Sema::CTC_Expression: + if (SemaRef.getLangOptions().CPlusPlus) + WantTypeSpecifiers = true; + WantExpressionKeywords = true; + // Fall through to get C++ named casts. + + case Sema::CTC_CXXCasts: + WantCXXNamedCasts = true; + break; + + case Sema::CTC_ObjCPropertyLookup: + // FIXME: Add "isa"? + break; + + case Sema::CTC_MemberLookup: + if (SemaRef.getLangOptions().CPlusPlus) + Consumer.addKeywordResult("template"); + break; + + case Sema::CTC_ObjCIvarLookup: + 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(CTypeSpecs[I]); + + if (SemaRef.getLangOptions().C99) + Consumer.addKeywordResult("restrict"); + if (SemaRef.getLangOptions().Bool || SemaRef.getLangOptions().CPlusPlus) + Consumer.addKeywordResult("bool"); + + if (SemaRef.getLangOptions().CPlusPlus) { + Consumer.addKeywordResult("class"); + Consumer.addKeywordResult("typename"); + Consumer.addKeywordResult("wchar_t"); + + if (SemaRef.getLangOptions().CPlusPlus0x) { + Consumer.addKeywordResult("char16_t"); + Consumer.addKeywordResult("char32_t"); + Consumer.addKeywordResult("constexpr"); + Consumer.addKeywordResult("decltype"); + Consumer.addKeywordResult("thread_local"); + } + } + + if (SemaRef.getLangOptions().GNUMode) + Consumer.addKeywordResult("typeof"); + } + + if (WantCXXNamedCasts && SemaRef.getLangOptions().CPlusPlus) { + Consumer.addKeywordResult("const_cast"); + Consumer.addKeywordResult("dynamic_cast"); + Consumer.addKeywordResult("reinterpret_cast"); + Consumer.addKeywordResult("static_cast"); + } + + if (WantExpressionKeywords) { + Consumer.addKeywordResult("sizeof"); + if (SemaRef.getLangOptions().Bool || SemaRef.getLangOptions().CPlusPlus) { + Consumer.addKeywordResult("false"); + Consumer.addKeywordResult("true"); + } + + if (SemaRef.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(CXXExprs[I]); + + if (isa(SemaRef.CurContext) && + cast(SemaRef.CurContext)->isInstance()) + Consumer.addKeywordResult("this"); + + if (SemaRef.getLangOptions().CPlusPlus0x) { + Consumer.addKeywordResult("alignof"); + Consumer.addKeywordResult("nullptr"); + } + } + } + + if (WantRemainingKeywords) { + if (SemaRef.getCurFunctionOrMethodDecl() || SemaRef.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(CStmts[I]); + + if (SemaRef.getLangOptions().CPlusPlus) { + Consumer.addKeywordResult("catch"); + Consumer.addKeywordResult("try"); + } + + if (S && S->getBreakParent()) + Consumer.addKeywordResult("break"); + + if (S && S->getContinueParent()) + Consumer.addKeywordResult("continue"); + + if (!SemaRef.getCurFunction()->SwitchStack.empty()) { + Consumer.addKeywordResult("case"); + Consumer.addKeywordResult("default"); + } + } else { + if (SemaRef.getLangOptions().CPlusPlus) { + Consumer.addKeywordResult("namespace"); + Consumer.addKeywordResult("template"); + } + + if (S && S->isClassScope()) { + Consumer.addKeywordResult("explicit"); + Consumer.addKeywordResult("friend"); + Consumer.addKeywordResult("mutable"); + Consumer.addKeywordResult("private"); + Consumer.addKeywordResult("protected"); + Consumer.addKeywordResult("public"); + Consumer.addKeywordResult("virtual"); + } + } + + if (SemaRef.getLangOptions().CPlusPlus) { + Consumer.addKeywordResult("using"); + + if (SemaRef.getLangOptions().CPlusPlus0x) + Consumer.addKeywordResult("static_assert"); + } + } +} + /// \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. /// -/// \param Res the \c LookupResult structure that contains the name -/// that was present in the source code along with the name-lookup -/// criteria used to search for the name. On success, this structure -/// will contain the results of name lookup. +/// \param TypoName the \c DeclarationNameInfo structure that contains +/// the name that was present in the source code along with its location. +/// +/// \param LookupKind the name-lookup criteria used to search for the name. /// /// \param S the scope in which name lookup occurs. /// @@ -3227,60 +3535,64 @@ static void LookupPotentialTypoResult(Sema &SemaRef, /// \param OPT when non-NULL, the search for visible declarations will /// also walk the protocols in the qualified interfaces of \p OPT. /// -/// \returns the corrected name if the typo was corrected, otherwise returns an -/// empty \c DeclarationName. When a typo was corrected, the result structure -/// 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, - CorrectTypoContext CTC, - const ObjCObjectPointerType *OPT) { +/// \returns a \c TypoCorrection containing the corrected name if the typo +/// along with information such as the \c NamedDecl where the corrected name +/// was declared, and any additional \c NestedNameSpecifier needed to access +/// it (C++ only). The \c TypoCorrection is empty if there is no correction. +TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, + Sema::LookupNameKind LookupKind, + Scope *S, CXXScopeSpec *SS, + DeclContext *MemberContext, + bool EnteringContext, + CorrectTypoContext CTC, + const ObjCObjectPointerType *OPT) { if (Diags.hasFatalErrorOccurred() || !getLangOptions().SpellChecking) - return DeclarationName(); + return TypoCorrection(); // We only attempt to correct typos for identifiers. - IdentifierInfo *Typo = Res.getLookupName().getAsIdentifierInfo(); + IdentifierInfo *Typo = TypoName.getName().getAsIdentifierInfo(); if (!Typo) - return DeclarationName(); + return TypoCorrection(); // If the scope specifier itself was invalid, don't try to correct // typos. if (SS && SS->isInvalid()) - return DeclarationName(); + return TypoCorrection(); // Never try to correct typos during template deduction or // instantiation. if (!ActiveTemplateInstantiations.empty()) - return DeclarationName(); + return TypoCorrection(); + + NamespaceSpecifierSet Namespaces(Context, CurContext); - TypoCorrectionConsumer Consumer(Typo); + TypoCorrectionConsumer Consumer(*this, Typo); // Perform name lookup to find visible, similarly-named entities. bool IsUnqualifiedLookup = false; if (MemberContext) { - LookupVisibleDecls(MemberContext, Res.getLookupKind(), Consumer); + LookupVisibleDecls(MemberContext, LookupKind, Consumer); // Look in qualified interfaces. if (OPT) { for (ObjCObjectPointerType::qual_iterator I = OPT->qual_begin(), E = OPT->qual_end(); I != E; ++I) - LookupVisibleDecls(*I, Res.getLookupKind(), Consumer); + LookupVisibleDecls(*I, LookupKind, Consumer); } } else if (SS && SS->isSet()) { DeclContext *DC = computeDeclContext(*SS, EnteringContext); if (!DC) - return DeclarationName(); + return TypoCorrection(); // Provide a stop gap for files that are just seriously broken. Trying // to correct all typos can turn into a HUGE performance penalty, causing // some files to take minutes to get rejected by the parser. if (TyposCorrected + UnqualifiedTyposCorrected.size() >= 20) - return DeclarationName(); + return TypoCorrection(); ++TyposCorrected; - LookupVisibleDecls(DC, Res.getLookupKind(), Consumer); + LookupVisibleDecls(DC, LookupKind, Consumer); } else { IsUnqualifiedLookup = true; UnqualifiedTyposCorrectedMap::iterator Cached @@ -3290,7 +3602,7 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS, // to correct all typos can turn into a HUGE performance penalty, causing // some files to take minutes to get rejected by the parser. if (TyposCorrected + UnqualifiedTyposCorrected.size() >= 20) - return DeclarationName(); + return TypoCorrection(); // For unqualified lookup, look through all of the names that we have // seen in this translation unit. @@ -3314,313 +3626,224 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS, } else { // Use the cached value, unless it's a keyword. In the keyword case, we'll // end up adding the keyword below. - if (Cached->second.first.empty()) - return DeclarationName(); + if (!Cached->second) + return TypoCorrection(); - if (!Cached->second.second) - Consumer.FoundName(Cached->second.first); + if (!Cached->second.isKeyword()) + Consumer.addCorrection(Cached->second); } } - // 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; + AddKeywordsToConsumer(*this, Consumer, S, CTC); - if (ObjCMethodDecl *Method = getCurMethodDecl()) - if (Method->getClassInterface() && - Method->getClassInterface()->getSuperClass()) - Consumer.addKeywordResult(Context, "super"); - - 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_ObjCPropertyLookup: - // FIXME: Add "isa"? - break; - - case CTC_MemberLookup: - if (getLangOptions().CPlusPlus) - Consumer.addKeywordResult(Context, "template"); - break; + // If we haven't found anything, we're done. + if (Consumer.empty()) { + // If this was an unqualified lookup, note that no correction was found. + if (IsUnqualifiedLookup) + (void)UnqualifiedTyposCorrected[Typo]; - case CTC_ObjCIvarLookup: - break; + return TypoCorrection(); } - 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"); - } + // Make sure that the user typed at least 3 characters for each correction + // made. Otherwise, we don't even both looking at the results. + unsigned ED = Consumer.getBestEditDistance(); + if (ED > 0 && Typo->getName().size() / ED < 3) { + // If this was an unqualified lookup, note that no correction was found. + if (IsUnqualifiedLookup) + (void)UnqualifiedTyposCorrected[Typo]; - if (WantCXXNamedCasts && getLangOptions().CPlusPlus) { - Consumer.addKeywordResult(Context, "const_cast"); - Consumer.addKeywordResult(Context, "dynamic_cast"); - Consumer.addKeywordResult(Context, "reinterpret_cast"); - Consumer.addKeywordResult(Context, "static_cast"); + return TypoCorrection(); } - 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"); - } - } + // Build the NestedNameSpecifiers for the KnownNamespaces + if (getLangOptions().CPlusPlus) { + // Load any externally-known namespaces. + if (ExternalSource && !LoadedExternalKnownNamespaces) { + llvm::SmallVector ExternalKnownNamespaces; + LoadedExternalKnownNamespaces = true; + ExternalSource->ReadKnownNamespaces(ExternalKnownNamespaces); + for (unsigned I = 0, N = ExternalKnownNamespaces.size(); I != N; ++I) + KnownNamespaces[ExternalKnownNamespaces[I]] = true; + } + + for (llvm::DenseMap::iterator + KNI = KnownNamespaces.begin(), + KNIEnd = KnownNamespaces.end(); + KNI != KNIEnd; ++KNI) + Namespaces.AddNamespace(KNI->first); } - 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"); + // Weed out any names that could not be found by name lookup. + llvm::SmallPtrSet QualifiedResults; + LookupResult TmpRes(*this, TypoName, LookupKind); + TmpRes.suppressDiagnostics(); + while (!Consumer.empty()) { + TypoCorrectionConsumer::distance_iterator DI = Consumer.begin(); + unsigned ED = DI->first; + for (TypoCorrectionConsumer::result_iterator I = DI->second.begin(), + IEnd = DI->second.end(); + I != IEnd; /* Increment in loop. */) { + // If the item already has been looked up or is a keyword, keep it + if (I->second.isResolved()) { + ++I; + continue; } - if (S && S->getBreakParent()) - Consumer.addKeywordResult(Context, "break"); + // Perform name lookup on this name. + IdentifierInfo *Name = I->second.getCorrectionAsIdentifierInfo(); + LookupPotentialTypoResult(*this, TmpRes, Name, S, SS, MemberContext, + EnteringContext, CTC); - if (S && S->getContinueParent()) - Consumer.addKeywordResult(Context, "continue"); + switch (TmpRes.getResultKind()) { + case LookupResult::NotFound: + case LookupResult::NotFoundInCurrentInstantiation: + QualifiedResults.insert(Name); + // We didn't find this name in our scope, or didn't like what we found; + // ignore it. + { + TypoCorrectionConsumer::result_iterator Next = I; + ++Next; + DI->second.erase(I); + I = Next; + } + break; - if (!getCurFunction()->SwitchStack.empty()) { - Consumer.addKeywordResult(Context, "case"); - Consumer.addKeywordResult(Context, "default"); - } - } else { - if (getLangOptions().CPlusPlus) { - Consumer.addKeywordResult(Context, "namespace"); - Consumer.addKeywordResult(Context, "template"); - } + case LookupResult::Ambiguous: + // We don't deal with ambiguities. + return TypoCorrection(); - 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"); + case LookupResult::Found: + case LookupResult::FoundOverloaded: + case LookupResult::FoundUnresolvedValue: + I->second.setCorrectionDecl(TmpRes.getAsSingle()); + ++I; + break; } } - if (getLangOptions().CPlusPlus) { - Consumer.addKeywordResult(Context, "using"); + if (DI->second.empty()) + Consumer.erase(DI); + else if (!getLangOptions().CPlusPlus || QualifiedResults.empty() || !ED) + // If there are results in the closest possible bucket, stop + break; - if (getLangOptions().CPlusPlus0x) - Consumer.addKeywordResult(Context, "static_assert"); + // Only perform the qualified lookups for C++ + if (getLangOptions().CPlusPlus) { + TmpRes.suppressDiagnostics(); + for (llvm::SmallPtrSet::iterator QRI = QualifiedResults.begin(), + QRIEnd = QualifiedResults.end(); + QRI != QRIEnd; ++QRI) { + for (NamespaceSpecifierSet::iterator NI = Namespaces.begin(), + NIEnd = Namespaces.end(); + NI != NIEnd; ++NI) { + DeclContext *Ctx = NI->DeclCtx; + unsigned QualifiedED = ED + NI->EditDistance; + + // Stop searching once the namespaces are too far away to create + // acceptable corrections for this identifier (since the namespaces + // are sorted in ascending order by edit distance) + if (QualifiedED > Consumer.getMaxEditDistance()) break; + + TmpRes.clear(); + TmpRes.setLookupName(*QRI); + if (!LookupQualifiedName(TmpRes, Ctx)) continue; + + switch (TmpRes.getResultKind()) { + case LookupResult::Found: + case LookupResult::FoundOverloaded: + case LookupResult::FoundUnresolvedValue: + Consumer.addName((*QRI)->getName(), TmpRes.getAsSingle(), + QualifiedED, NI->NameSpecifier); + break; + case LookupResult::NotFound: + case LookupResult::NotFoundInCurrentInstantiation: + case LookupResult::Ambiguous: + break; + } + } + } } - } - // If we haven't found anything, we're done. - if (Consumer.empty()) { - // If this was an unqualified lookup, note that no correction was found. - if (IsUnqualifiedLookup) - (void)UnqualifiedTyposCorrected[Typo]; - - return DeclarationName(); + QualifiedResults.clear(); } - // Make sure that the user typed at least 3 characters for each correction - // made. Otherwise, we don't even both looking at the results. + // No corrections remain... + if (Consumer.empty()) return TypoCorrection(); + + TypoResultsMap &BestResults = Consumer.begin()->second; + ED = Consumer.begin()->first; - // We also suppress exact matches; those should be handled by a - // different mechanism (e.g., one that introduces qualification in - // C++). - unsigned ED = Consumer.getBestEditDistance(); if (ED > 0 && Typo->getName().size() / ED < 3) { // If this was an unqualified lookup, note that no correction was found. if (IsUnqualifiedLookup) (void)UnqualifiedTyposCorrected[Typo]; - return DeclarationName(); + return TypoCorrection(); } - // Weed out any names that could not be found by name lookup. - bool LastLookupWasAccepted = false; - for (TypoCorrectionConsumer::iterator I = Consumer.begin(), - IEnd = Consumer.end(); - I != IEnd; /* Increment in loop. */) { - // Keywords are always found. - if (I->second) { - ++I; - continue; - } - - // Perform name lookup on this name. - IdentifierInfo *Name = &Context.Idents.get(I->getKey()); - LookupPotentialTypoResult(*this, Res, Name, S, SS, MemberContext, - EnteringContext, CTC); - - switch (Res.getResultKind()) { - case LookupResult::NotFound: - case LookupResult::NotFoundInCurrentInstantiation: - case LookupResult::Ambiguous: - // We didn't find this name in our scope, or didn't like what we found; - // ignore it. - Res.suppressDiagnostics(); - { - TypoCorrectionConsumer::iterator Next = I; - ++Next; - Consumer.erase(I); - I = Next; - } - LastLookupWasAccepted = false; - break; - - case LookupResult::Found: - case LookupResult::FoundOverloaded: - case LookupResult::FoundUnresolvedValue: - ++I; - LastLookupWasAccepted = true; - break; - } - - if (Res.isAmbiguous()) { - // We don't deal with ambiguities. - Res.suppressDiagnostics(); - Res.clear(); - return DeclarationName(); + // If we have multiple possible corrections, eliminate the ones where we + // added namespace qualifiers to try to resolve the ambiguity (and to favor + // corrections without additional namespace qualifiers) + if (getLangOptions().CPlusPlus && BestResults.size() > 1) { + TypoCorrectionConsumer::distance_iterator DI = Consumer.begin(); + for (TypoCorrectionConsumer::result_iterator I = DI->second.begin(), + IEnd = DI->second.end(); + I != IEnd; /* Increment in loop. */) { + if (I->second.getCorrectionSpecifier() != NULL) { + TypoCorrectionConsumer::result_iterator Cur = I; + ++I; + DI->second.erase(Cur); + } else ++I; } } // If only a single name remains, return that result. - if (Consumer.size() == 1) { - IdentifierInfo *Name = &Context.Idents.get(Consumer.begin()->getKey()); - if (Consumer.begin()->second) { - Res.suppressDiagnostics(); - Res.clear(); - - // Don't correct to a keyword that's the same as the typo; the keyword - // wasn't actually in scope. - if (ED == 0) { - Res.setLookupName(Typo); - return DeclarationName(); - } + if (BestResults.size() == 1) { + const llvm::StringMapEntry &Correction = *(BestResults.begin()); + const TypoCorrection &Result = Correction.second; - } else if (!LastLookupWasAccepted) { - // Perform name lookup on this name. - LookupPotentialTypoResult(*this, Res, Name, S, SS, MemberContext, - EnteringContext, CTC); - } + // Don't correct to a keyword that's the same as the typo; the keyword + // wasn't actually in scope. + if (ED == 0 && Result.isKeyword()) return TypoCorrection(); + assert(Result.isResolved() && "correction has not been looked up"); // Record the correction for unqualified lookup. if (IsUnqualifiedLookup) - UnqualifiedTyposCorrected[Typo] - = std::make_pair(Name->getName(), Consumer.begin()->second); + UnqualifiedTyposCorrected[Typo] = Result; - return &Context.Idents.get(Consumer.begin()->getKey()); + return Result; } - else if (Consumer.size() > 1 && CTC == CTC_ObjCMessageReceiver - && Consumer["super"]) { - // Prefix 'super' when we're completing in a message-receiver + else if (BestResults.size() > 1 && CTC == CTC_ObjCMessageReceiver + && BestResults["super"].isKeyword()) { + // Prefer 'super' when we're completing in a message-receiver // context. - Res.suppressDiagnostics(); - Res.clear(); // Don't correct to a keyword that's the same as the typo; the keyword // wasn't actually in scope. - if (ED == 0) { - Res.setLookupName(Typo); - return DeclarationName(); - } + if (ED == 0) return TypoCorrection(); // Record the correction for unqualified lookup. if (IsUnqualifiedLookup) - UnqualifiedTyposCorrected[Typo] - = std::make_pair("super", Consumer.begin()->second); + UnqualifiedTyposCorrected[Typo] = BestResults["super"]; - return &Context.Idents.get("super"); + return BestResults["super"]; } - Res.suppressDiagnostics(); - Res.setLookupName(Typo); - Res.clear(); - // Record the correction for unqualified lookup. if (IsUnqualifiedLookup) (void)UnqualifiedTyposCorrected[Typo]; - return DeclarationName(); + return TypoCorrection(); +} + +std::string TypoCorrection::getAsString(const LangOptions &LO) const { + if (CorrectionNameSpec) { + std::string tmpBuffer; + llvm::raw_string_ostream PrefixOStream(tmpBuffer); + CorrectionNameSpec->print(PrefixOStream, PrintingPolicy(LO)); + return PrefixOStream.str() + CorrectionName.getAsString(); + } + + return CorrectionName.getAsString(); } diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index fa2182a2aa..3b4ccac416 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -294,26 +294,31 @@ 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 (DeclarationName Corrected = CorrectTypo(Found, S, &SS, LookupCtx, - false, CTC_CXXCasts)) { + Found.clear(); + if (TypoCorrection Corrected = CorrectTypo(Found.getLookupNameInfo(), + Found.getLookupKind(), S, &SS, + LookupCtx, false, + CTC_CXXCasts)) { + Found.setLookupName(Corrected.getCorrection()); + if (Corrected.getCorrectionDecl()) + Found.addDecl(Corrected.getCorrectionDecl()); FilterAcceptableTemplateNames(Found); if (!Found.empty()) { + std::string CorrectedStr(Corrected.getAsString(getLangOptions())); + std::string CorrectedQuotedStr(Corrected.getQuoted(getLangOptions())); if (LookupCtx) Diag(Found.getNameLoc(), diag::err_no_member_template_suggest) - << Name << LookupCtx << Found.getLookupName() << SS.getRange() - << FixItHint::CreateReplacement(Found.getNameLoc(), - Found.getLookupName().getAsString()); + << Name << LookupCtx << CorrectedQuotedStr << SS.getRange() + << FixItHint::CreateReplacement(Found.getNameLoc(), CorrectedStr); else Diag(Found.getNameLoc(), diag::err_no_template_suggest) - << Name << Found.getLookupName() - << FixItHint::CreateReplacement(Found.getNameLoc(), - Found.getLookupName().getAsString()); + << Name << CorrectedQuotedStr + << FixItHint::CreateReplacement(Found.getNameLoc(), CorrectedStr); if (TemplateDecl *Template = Found.getAsSingle()) Diag(Template->getLocation(), diag::note_previous_decl) - << Template->getDeclName(); + << CorrectedQuotedStr; } } else { - Found.clear(); Found.setLookupName(Name); } } diff --git a/lib/Sema/SemaTemplateVariadic.cpp b/lib/Sema/SemaTemplateVariadic.cpp index 86d3bc1709..daa1523363 100644 --- a/lib/Sema/SemaTemplateVariadic.cpp +++ b/lib/Sema/SemaTemplateVariadic.cpp @@ -716,17 +716,19 @@ ExprResult Sema::ActOnSizeofParameterPackExpr(Scope *S, case LookupResult::NotFound: case LookupResult::NotFoundInCurrentInstantiation: - if (DeclarationName CorrectedName = CorrectTypo(R, S, 0, 0, false, - CTC_NoKeywords)) { - if (NamedDecl *CorrectedResult = R.getAsSingle()) + if (TypoCorrection Corrected = CorrectTypo(R.getLookupNameInfo(), + R.getLookupKind(), S, 0, 0, + false, CTC_NoKeywords)) { + if (NamedDecl *CorrectedResult = Corrected.getCorrectionDecl()) if (CorrectedResult->isParameterPack()) { + std::string CorrectedQuotedStr(Corrected.getQuoted(getLangOptions())); ParameterPack = CorrectedResult; Diag(NameLoc, diag::err_sizeof_pack_no_pack_name_suggest) - << &Name << CorrectedName - << FixItHint::CreateReplacement(NameLoc, - CorrectedName.getAsString()); + << &Name << CorrectedQuotedStr + << FixItHint::CreateReplacement( + NameLoc, Corrected.getAsString(getLangOptions())); Diag(ParameterPack->getLocation(), diag::note_parameter_pack_here) - << CorrectedName; + << CorrectedQuotedStr; } } diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index 4d7e364882..3e95df4d4d 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -2369,6 +2369,15 @@ ASTReader::ReadASTBlock(PerFileData &F) { TentativeDefinitions.insert(TentativeDefinitions.end(), Record.begin(), Record.end()); break; + + case KNOWN_NAMESPACES: + // Optimization for the first block. + if (KnownNamespaces.empty()) + KnownNamespaces.swap(Record); + else + KnownNamespaces.insert(KnownNamespaces.end(), + Record.begin(), Record.end()); + break; } First = false; } @@ -4452,6 +4461,17 @@ ASTReader::ReadMethodPool(Selector Sel) { return std::pair(); } +void ASTReader::ReadKnownNamespaces( + llvm::SmallVectorImpl &Namespaces) { + Namespaces.clear(); + + for (unsigned I = 0, N = KnownNamespaces.size(); I != N; ++I) { + if (NamespaceDecl *Namespace + = dyn_cast_or_null(GetDecl(KnownNamespaces[I]))) + Namespaces.push_back(Namespace); + } +} + void ASTReader::LoadSelector(Selector Sel) { // It would be complicated to avoid reading the methods anyway. So don't. ReadMethodPool(Sel); diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp index 80e790c796..d616c84000 100644 --- a/lib/Serialization/ASTWriter.cpp +++ b/lib/Serialization/ASTWriter.cpp @@ -781,6 +781,8 @@ void ASTWriter::WriteBlockInfoBlock() { RECORD(FP_PRAGMA_OPTIONS); RECORD(OPENCL_EXTENSIONS); RECORD(DELEGATING_CTORS); + RECORD(FILE_SOURCE_LOCATION_OFFSETS); + RECORD(KNOWN_NAMESPACES); // SourceManager Block. BLOCK(SOURCE_MANAGER_BLOCK); @@ -2845,6 +2847,16 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, MemorizeStatCalls *StatCalls, AddDeclRef(Context.getcudaConfigureCallDecl(), CUDASpecialDeclRefs); } + // Build a record containing all of the known namespaces. + RecordData KnownNamespaces; + for (llvm::DenseMap::iterator + I = SemaRef.KnownNamespaces.begin(), + IEnd = SemaRef.KnownNamespaces.end(); + I != IEnd; ++I) { + if (!I->second) + AddDeclRef(I->first, KnownNamespaces); + } + // Write the remaining AST contents. RecordData Record; Stream.EnterSubblock(AST_BLOCK_ID, 5); @@ -2954,6 +2966,10 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, MemorizeStatCalls *StatCalls, if (!DelegatingCtorDecls.empty()) Stream.EmitRecord(DELEGATING_CTORS, DelegatingCtorDecls); + // Write the known namespaces. + if (!KnownNamespaces.empty()) + Stream.EmitRecord(KNOWN_NAMESPACES, KnownNamespaces); + // Some simple statistics Record.clear(); Record.push_back(NumStatements); diff --git a/test/CXX/basic/basic.lookup/basic.lookup.argdep/p4.cpp b/test/CXX/basic/basic.lookup/basic.lookup.argdep/p4.cpp index ccadf416c9..15d86b7740 100644 --- a/test/CXX/basic/basic.lookup/basic.lookup.argdep/p4.cpp +++ b/test/CXX/basic/basic.lookup/basic.lookup.argdep/p4.cpp @@ -19,8 +19,9 @@ namespace D { } namespace C { - class C {}; - void func(C); + class C {}; // expected-note {{candidate constructor (the implicit copy constructor) not viable: no known conversion from 'B::B' to 'const C::C &' for 1st argument}} + void func(C); // expected-note {{'C::func' declared here}} \ + // expected-note {{passing argument to parameter here}} C operator+(C,C); D::D operator+(D::D,D::D); } @@ -32,7 +33,13 @@ namespace D { namespace Test { void test() { func(A::A()); - func(B::B()); // expected-error {{use of undeclared identifier 'func'}} + // FIXME: namespace-aware typo correction causes an extra, misleading + // message in this case; some form of backtracking, diagnostic message + // delaying, or argument checking before emitting diagnostics is needed to + // avoid accepting and printing out a typo correction that proves to be + // incorrect once argument-dependent lookup resolution has occurred. + func(B::B()); // expected-error {{use of undeclared identifier 'func'; did you mean 'C::func'?}} \ + // expected-error {{no viable conversion from 'B::B' to 'C::C'}} func(C::C()); A::A() + A::A(); B::B() + B::B(); diff --git a/test/FixIt/typo.cpp b/test/FixIt/typo.cpp index f8b5352374..e35a0f5406 100644 --- a/test/FixIt/typo.cpp +++ b/test/FixIt/typo.cpp @@ -74,3 +74,9 @@ int foo() { unsinged *ptr = 0; // expected-error{{use of undeclared identifier 'unsinged'; did you mean 'unsigned'?}} return *i + *ptr + global_val; // expected-error{{use of undeclared identifier 'global_val'; did you mean 'global_value'?}} } + +namespace nonstd { + typedef std::basic_string yarn; // expected-note{{'nonstd::yarn' declared here}} +} + +yarn str4; // expected-error{{unknown type name 'yarn'; did you mean 'nonstd::yarn'?}} diff --git a/test/PCH/Inputs/typo.hpp b/test/PCH/Inputs/typo.hpp new file mode 100644 index 0000000000..c811595007 --- /dev/null +++ b/test/PCH/Inputs/typo.hpp @@ -0,0 +1,8 @@ +namespace boost { + template class function {}; + + namespace graph { + template class adjacency_list { }; + }; +} + diff --git a/test/PCH/typo.cpp b/test/PCH/typo.cpp new file mode 100644 index 0000000000..4c8861454d --- /dev/null +++ b/test/PCH/typo.cpp @@ -0,0 +1,17 @@ + +// In header: expected-note{{ 'boost::function' declared here}} + + +// In header: expected-note{{ 'boost::graph::adjacency_list' declared here}} + + + +adjacent_list g; // expected-error{{no template named 'adjacent_list'; did you mean 'boost::graph::adjacency_list'?}} +Function f; // expected-error{{no template named 'Function'; did you mean 'boost::function'?}} + +// Without PCH +// RUN: %clang_cc1 -include %S/Inputs/typo.hpp -verify %s + +// With PCH +// RUN: %clang_cc1 -x c++-header -emit-pch -o %t %S/Inputs/typo.hpp +// RUN: %clang_cc1 -include-pch %t -verify %s diff --git a/test/SemaCXX/missing-namespace-qualifier-typo-corrections.cpp b/test/SemaCXX/missing-namespace-qualifier-typo-corrections.cpp new file mode 100644 index 0000000000..3896257511 --- /dev/null +++ b/test/SemaCXX/missing-namespace-qualifier-typo-corrections.cpp @@ -0,0 +1,61 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wno-c++0x-extensions %s + +namespace fizbin { class Foobar; } // expected-note{{'fizbin::Foobar' declared here}} +Foobar *my_bar = new Foobar; // expected-error{{unknown type name 'Foobar'; did you mean 'fizbin::Foobar'?}} \ + // expected-error{{expected a type}} + +namespace barstool { int toFoobar() { return 1; } } // expected-note 3 {{'barstool::toFoobar' declared here}} +int Double(int x) { return x + x; } +void empty() { + Double(toFoobar()); // expected-error{{{use of undeclared identifier 'toFoobar'; did you mean 'barstool::toFoobar'?}} +} + +namespace fizbin { + namespace baztool { bool toFoobar() { return true; } } // expected-note{{'fizbin::baztool' declared here}} + namespace nested { bool moreFoobar() { return true; } } // expected-note{{'fizbin::nested::moreFoobar' declared here}} + namespace nested { bool lessFoobar() { return true; } } // expected-note{{'fizbin::nested' declared here}} \ + // expected-note{{'fizbin::nested::lessFoobar' declared here}} + class dummy { // expected-note 2 {{'fizbin::dummy' declared here}} + public: + static bool moreFoobar() { return false; } // expected-note{{'moreFoobar' declared here}} + }; +} +void Check() { // expected-note{{'Check' declared here}} + if (toFoobar()) Double(7); // expected-error{{use of undeclared identifier 'toFoobar'; did you mean 'barstool::toFoobar'?}} + if (noFoobar()) Double(7); // expected-error{{use of undeclared identifier 'noFoobar'; did you mean 'barstool::toFoobar'?}} + if (moreFoobar()) Double(7); // expected-error{{use of undeclared identifier 'moreFoobar'; did you mean 'fizbin::nested::moreFoobar'}} + if (lessFoobar()) Double(7); // expected-error{{use of undeclared identifier 'lessFoobar'; did you mean 'fizbin::nested::lessFoobar'?}} + if (baztool::toFoobar()) Double(7); // expected-error{{use of undeclared identifier 'baztool'; did you mean 'fizbin::baztool'?}} + if (nested::moreFoobar()) Double(7); // expected-error{{use of undeclared identifier 'nested'; did you mean 'fizbin::nested'?}} + if (dummy::moreFoobar()) Double(7); // expected-error{{use of undeclared identifier 'dummy'; did you mean 'fizbin::dummy'?}} + if (dummy::mreFoobar()) Double(7); // expected-error{{use of undeclared identifier 'dummy'; did you mean 'fizbin::dummy'?}} \ + // expected-error{{no member named 'mreFoobar' in 'fizbin::dummy'; did you mean 'moreFoobar'?}} + if (moFoobin()) Double(7); // expected-error{{use of undeclared identifier 'moFoobin'}} +} + +void Alt() { + Cleck(); // expected-error{{{use of undeclared identifier 'Cleck'; did you mean 'Check'?}} +} + +namespace N { + namespace inner { + class myvector { /* ... */ }; // expected-note{{'inner::myvector' declared here}} + } + + void f() { + myvector v; // expected-error{{no type named 'myvector' in namespace 'N::inner'; did you mean 'inner::myvector'?}} + } +} + +namespace realstd { + inline namespace __1 { + class mylinkedlist { /* ... */ }; // expected-note 2 {{'realstd::mylinkedlist' declared here}} + } + + class linkedlist { /* ... */ }; +} + +void f() { + mylinkedlist v; // expected-error{{no type named 'mylinkedlist' in namespace 'realstd'; did you mean 'realstd::mylinkedlist'?}} + nylinkedlist w; // expected-error{{no type named 'nylinkedlist' in namespace 'realstd'; did you mean 'realstd::mylinkedlist'?}} +} diff --git a/test/SemaObjC/ivar-ref-misuse.m b/test/SemaObjC/ivar-ref-misuse.m index ba2f11594f..f6d9c94084 100644 --- a/test/SemaObjC/ivar-ref-misuse.m +++ b/test/SemaObjC/ivar-ref-misuse.m @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -verify %s -@interface Sprite { +@interface Sprite { // expected-note{{'Sprite' declared here}} int sprite, spree; int UseGlobalBar; } @@ -17,7 +17,8 @@ int UseGlobalBar; + (void)setFoo:(int)foo { sprite = foo; // expected-error {{instance variable 'sprite' accessed in class method}} spree = foo; - Xsprite = foo; // expected-error {{use of undeclared identifier 'Xsprite'}} + Xsprite = foo; // expected-error {{unknown type name 'Xsprite'; did you mean 'Sprite'?}} \ + // expected-error{{expected identifier or '('}} UseGlobalBar = 10; } + (void)setSprite:(int)sprite {