From 7d5e6948e6a4e04ee67b607f931d90d3063579f2 Mon Sep 17 00:00:00 2001 From: Kaelyn Uhrain Date: Wed, 11 Jan 2012 19:37:46 +0000 Subject: [PATCH] Add initial callback object support to Sema::CorrectTypo. Also includes two examples of the callback: a wrapper/replacement for the CorrectTypoContext enum, and a conversion of the two calls to CorrectTypo in SemaDeclCXX.cpp (one of which provides verifiable improvement to the typo correction, as demonstrated in the added test). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@147962 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Sema/Sema.h | 12 +- include/clang/Sema/TypoCorrection.h | 26 ++++ lib/Sema/SemaDeclCXX.cpp | 103 ++++++++++------ lib/Sema/SemaLookup.cpp | 182 +++++++++++++++++----------- test/SemaCXX/typo-correction.cpp | 11 ++ 5 files changed, 229 insertions(+), 105 deletions(-) diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 68016e497f..2d6d743087 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -1835,10 +1835,18 @@ public: TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, Sema::LookupNameKind LookupKind, Scope *S, CXXScopeSpec *SS, - DeclContext *MemberContext = NULL, + DeclContext *MemberContext = 0, bool EnteringContext = false, CorrectTypoContext CTC = CTC_Unknown, - const ObjCObjectPointerType *OPT = NULL); + const ObjCObjectPointerType *OPT = 0); + + TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, + Sema::LookupNameKind LookupKind, + Scope *S, CXXScopeSpec *SS, + CorrectionCandidateCallback *CCC, + DeclContext *MemberContext = 0, + bool EnteringContext = false, + const ObjCObjectPointerType *OPT = 0); void FindAssociatedClassesAndNamespaces(Expr **Args, unsigned NumArgs, AssociatedNamespaceSet &AssociatedNamespaces, diff --git a/include/clang/Sema/TypoCorrection.h b/include/clang/Sema/TypoCorrection.h index 9537c3031d..0bb378be37 100644 --- a/include/clang/Sema/TypoCorrection.h +++ b/include/clang/Sema/TypoCorrection.h @@ -135,6 +135,32 @@ private: unsigned EditDistance; }; +// @brief Base class for callback objects used by Sema::CorrectTypo to check the +// validity of a potential typo correction. +class CorrectionCandidateCallback { + public: + CorrectionCandidateCallback() + : WantTypeSpecifiers(true), WantExpressionKeywords(true), + WantCXXNamedCasts(true), WantRemainingKeywords(true), + WantObjCSuper(false), + IsObjCIvarLookup(false) {} + + virtual bool ValidateCandidate(const TypoCorrection &candidate) { + return true; + } + + // Flags for context-dependent keywords. + // TODO: Expand these to apply to non-keywords or possibly remove them. + bool WantTypeSpecifiers; + bool WantExpressionKeywords; + bool WantCXXNamedCasts; + bool WantRemainingKeywords; + bool WantObjCSuper; + // Temporary hack for the one case where a CorrectTypoContext enum is used + // when looking up results. + bool IsObjCIvarLookup; +}; + } #endif diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index e9c842bdc7..f21d7664e5 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -1745,6 +1745,30 @@ Sema::ActOnMemInitializer(Decl *ConstructorD, EllipsisLoc); } +namespace { + +// Callback to only accept typo corrections that are namespaces. +class MemInitializerValidatorCCC : public CorrectionCandidateCallback { + public: + explicit MemInitializerValidatorCCC(CXXRecordDecl *ClassDecl) + : ClassDecl(ClassDecl) {} + + virtual bool ValidateCandidate(const TypoCorrection &candidate) { + if (NamedDecl *ND = candidate.getCorrectionDecl()) { + if (FieldDecl *Member = dyn_cast(ND)) + return Member->getDeclContext()->getRedeclContext()->Equals(ClassDecl); + else + return isa(ND); + } + return false; + } + + private: + CXXRecordDecl *ClassDecl; +}; + +} + /// \brief Handle a C++ member initializer. MemInitResult Sema::BuildMemInitializer(Decl *ConstructorD, @@ -1837,24 +1861,23 @@ Sema::BuildMemInitializer(Decl *ConstructorD, // If no results were found, try to correct typos. TypoCorrection Corr; + MemInitializerValidatorCCC Validator(ClassDecl); if (R.empty() && BaseType.isNull() && (Corr = CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), S, &SS, - ClassDecl, false, CTC_NoKeywords))) { + &Validator, ClassDecl))) { 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 << CorrectedQuotedStr - << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); - Diag(Member->getLocation(), diag::note_previous_decl) - << CorrectedQuotedStr; - - return BuildMemberInitializer(Member, Args, IdLoc); - } + // 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 << CorrectedQuotedStr + << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr); + Diag(Member->getLocation(), diag::note_previous_decl) + << CorrectedQuotedStr; + + return BuildMemberInitializer(Member, Args, IdLoc); } else if (TypeDecl *Type = Corr.getCorrectionDeclAs()) { const CXXBaseSpecifier *DirectBaseSpec; const CXXBaseSpecifier *VirtualBaseSpec; @@ -5750,35 +5773,47 @@ static bool IsUsingDirectiveInToplevelContext(DeclContext *CurContext) { } } +namespace { + +// Callback to only accept typo corrections that are namespaces. +class NamespaceValidatorCCC : public CorrectionCandidateCallback { + public: + virtual bool ValidateCandidate(const TypoCorrection &candidate) { + if (NamedDecl *ND = candidate.getCorrectionDecl()) { + return isa(ND) || isa(ND); + } + return false; + } +}; + +} + static bool TryNamespaceTypoCorrection(Sema &S, LookupResult &R, Scope *Sc, CXXScopeSpec &SS, SourceLocation IdentLoc, IdentifierInfo *Ident) { + NamespaceValidatorCCC Validator; 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); + R.getLookupKind(), Sc, &SS, + &Validator)) { + 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; + S.Diag(Corrected.getCorrectionDecl()->getLocation(), + diag::note_namespace_defined_here) << CorrectedQuotedStr; - Ident = Corrected.getCorrectionAsIdentifierInfo(); - R.addDecl(Corrected.getCorrectionDecl()); - return true; - } - R.setLookupName(Ident); + Ident = Corrected.getCorrectionAsIdentifierInfo(); + R.addDecl(Corrected.getCorrectionDecl()); + return true; } return false; } diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index eab1517b65..5bf0ca535c 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -3310,13 +3310,13 @@ static void LookupPotentialTypoResult(Sema &SemaRef, Scope *S, CXXScopeSpec *SS, DeclContext *MemberContext, bool EnteringContext, - Sema::CorrectTypoContext CTC) { + bool isObjCIvarLookup) { Res.suppressDiagnostics(); Res.clear(); Res.setLookupName(Name); if (MemberContext) { if (ObjCInterfaceDecl *Class = dyn_cast(MemberContext)) { - if (CTC == Sema::CTC_ObjCIvarLookup) { + if (isObjCIvarLookup) { if (ObjCIvarDecl *Ivar = Class->lookupInstanceVariable(Name)) { Res.addDecl(Ivar); Res.resolveKind(); @@ -3357,61 +3357,11 @@ 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"); + Scope *S, CorrectionCandidateCallback &CCC) { + if (CCC.WantObjCSuper) + 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) { + if (CCC.WantTypeSpecifiers) { // Add type-specifier keywords to the set of results. const char *CTypeSpecs[] = { "char", "const", "double", "enum", "float", "int", "long", "short", @@ -3450,14 +3400,14 @@ static void AddKeywordsToConsumer(Sema &SemaRef, Consumer.addKeywordResult("typeof"); } - if (WantCXXNamedCasts && SemaRef.getLangOptions().CPlusPlus) { + if (CCC.WantCXXNamedCasts && SemaRef.getLangOptions().CPlusPlus) { Consumer.addKeywordResult("const_cast"); Consumer.addKeywordResult("dynamic_cast"); Consumer.addKeywordResult("reinterpret_cast"); Consumer.addKeywordResult("static_cast"); } - if (WantExpressionKeywords) { + if (CCC.WantExpressionKeywords) { Consumer.addKeywordResult("sizeof"); if (SemaRef.getLangOptions().Bool || SemaRef.getLangOptions().CPlusPlus) { Consumer.addKeywordResult("false"); @@ -3483,7 +3433,7 @@ static void AddKeywordsToConsumer(Sema &SemaRef, } } - if (WantRemainingKeywords) { + if (CCC.WantRemainingKeywords) { if (SemaRef.getCurFunctionOrMethodDecl() || SemaRef.getCurBlock()) { // Statements. const char *CStmts[] = { @@ -3533,6 +3483,77 @@ static void AddKeywordsToConsumer(Sema &SemaRef, } } +namespace { + +// Simple CorrectionCandidateCallback class that sets the keyword flags based +// on a given CorrectTypoContext, but does not perform any extra validation +// of typo correction candidates. +class CorrectTypoContextReplacementCCC : public CorrectionCandidateCallback { + public: + CorrectTypoContextReplacementCCC( + Sema &SemaRef, Sema::CorrectTypoContext CTC = Sema::CTC_Unknown) { + WantTypeSpecifiers = false; + WantExpressionKeywords = false; + WantCXXNamedCasts = false; + WantRemainingKeywords = false; + switch (CTC) { + case Sema::CTC_Unknown: + WantTypeSpecifiers = true; + WantExpressionKeywords = true; + WantCXXNamedCasts = true; + WantRemainingKeywords = true; + if (ObjCMethodDecl *Method = SemaRef.getCurMethodDecl()) + WantObjCSuper = Method->getClassInterface() && + Method->getClassInterface()->getSuperClass(); + break; + + case Sema::CTC_Type: + WantTypeSpecifiers = true; + break; + + case Sema::CTC_ObjCMessageReceiver: + WantObjCSuper = true; + // 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_MemberLookup: + case Sema::CTC_NoKeywords: + case Sema::CTC_ObjCPropertyLookup: + break; + + case Sema::CTC_ObjCIvarLookup: + IsObjCIvarLookup = true; + break; + } + } +}; + +} + +/// \brief Compatibility wrapper for call sites that pass a CorrectTypoContext +/// value to CorrectTypo instead of providing a callback object. +TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, + Sema::LookupNameKind LookupKind, + Scope *S, CXXScopeSpec *SS, + DeclContext *MemberContext, + bool EnteringContext, + CorrectTypoContext CTC, + const ObjCObjectPointerType *OPT) { + CorrectTypoContextReplacementCCC CTCVerifier(*this, CTC); + + return CorrectTypo(TypoName, LookupKind, S, SS, &CTCVerifier, MemberContext, + EnteringContext, OPT); +} + /// \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. @@ -3547,15 +3568,16 @@ static void AddKeywordsToConsumer(Sema &SemaRef, /// \param SS the nested-name-specifier that precedes the name we're /// looking for, if present. /// +/// \param CCC A CorrectionCandidateCallback object that provides further +/// validation of typo correction candidates. It also provides flags for +/// determining the set of keywords permitted. +/// /// \param MemberContext if non-NULL, the context in which to look for /// a member access expression. /// /// \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. /// @@ -3566,9 +3588,9 @@ static void AddKeywordsToConsumer(Sema &SemaRef, TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, Sema::LookupNameKind LookupKind, Scope *S, CXXScopeSpec *SS, + CorrectionCandidateCallback *CCC, DeclContext *MemberContext, bool EnteringContext, - CorrectTypoContext CTC, const ObjCObjectPointerType *OPT) { if (Diags.hasFatalErrorOccurred() || !getLangOptions().SpellChecking) return TypoCorrection(); @@ -3665,7 +3687,8 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, } } - AddKeywordsToConsumer(*this, Consumer, S, CTC); + CorrectTypoContextReplacementCCC DefaultCCC(*this); + AddKeywordsToConsumer(*this, Consumer, S, CCC ? *CCC : DefaultCCC); // If we haven't found anything, we're done. if (Consumer.empty()) { @@ -3705,7 +3728,8 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, Namespaces.AddNamespace(KNI->first); } - // Weed out any names that could not be found by name lookup. + // Weed out any names that could not be found by name lookup or, if a + // CorrectionCandidateCallback object was provided, failed validation. llvm::SmallPtrSet QualifiedResults; LookupResult TmpRes(*this, TypoName, LookupKind); TmpRes.suppressDiagnostics(); @@ -3715,16 +3739,21 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, 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 the item already has been looked up or is a keyword, keep it. + // If a validator callback object was given, drop the correction + // unless it passes validation. if (I->second.isResolved()) { + TypoCorrectionConsumer::result_iterator Prev = I; ++I; + if (CCC && !CCC->ValidateCandidate(Prev->second)) + DI->second->erase(Prev); continue; } // Perform name lookup on this name. IdentifierInfo *Name = I->second.getCorrectionAsIdentifierInfo(); LookupPotentialTypoResult(*this, TmpRes, Name, S, SS, MemberContext, - EnteringContext, CTC); + EnteringContext, CCC && CCC->IsObjCIvarLookup); switch (TmpRes.getResultKind()) { case LookupResult::NotFound: @@ -3746,20 +3775,28 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, return TypoCorrection(); case LookupResult::FoundOverloaded: { + TypoCorrectionConsumer::result_iterator Prev = I; // Store all of the Decls for overloaded symbols for (LookupResult::iterator TRD = TmpRes.begin(), TRDEnd = TmpRes.end(); TRD != TRDEnd; ++TRD) I->second.addCorrectionDecl(*TRD); ++I; + if (CCC && !CCC->ValidateCandidate(Prev->second)) + DI->second->erase(Prev); break; } - case LookupResult::Found: + case LookupResult::Found: { + TypoCorrectionConsumer::result_iterator Prev = I; I->second.setCorrectionDecl(TmpRes.getAsSingle()); ++I; + if (CCC && !CCC->ValidateCandidate(Prev->second)) + DI->second->erase(Prev); break; } + + } } if (DI->second->empty()) @@ -3790,6 +3827,8 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, TmpRes.setLookupName(*QRI); if (!LookupQualifiedName(TmpRes, Ctx)) continue; + // Any corrections added below will be validated in subsequent + // iterations of the main while() loop over the Consumer's contents. switch (TmpRes.getResultKind()) { case LookupResult::Found: Consumer.addName((*QRI)->getName(), TmpRes.getAsSingle(), @@ -3863,7 +3902,12 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, return Result; } - else if (BestResults.size() > 1 && CTC == CTC_ObjCMessageReceiver + else if (BestResults.size() > 1 + // Ugly hack equivalent to CTC == CTC_ObjCMessageReceiver; + // WantObjCSuper is only true for CTC_ObjCMessageReceiver and for + // some instances of CTC_Unknown, while WantRemainingKeywords is true + // for CTC_Unknown but not for CTC_ObjCMessageReceiver. + && CCC && CCC->WantObjCSuper && !CCC->WantRemainingKeywords && BestResults["super"].isKeyword()) { // Prefer 'super' when we're completing in a message-receiver // context. diff --git a/test/SemaCXX/typo-correction.cpp b/test/SemaCXX/typo-correction.cpp index be3fb84fe5..0fb33bc781 100644 --- a/test/SemaCXX/typo-correction.cpp +++ b/test/SemaCXX/typo-correction.cpp @@ -29,3 +29,14 @@ public: inline error_condition make_error_condition(errc _e) { return error_condition(static_cast(_e)); } + + +// Prior to the introduction of a callback object to further filter possible +// typo corrections, this example would not trigger a suggestion as "base_type" +// is a closer match to "basetype" than is "BaseType" but "base_type" does not +// refer to a base class or non-static data member. +struct BaseType { }; +struct Derived : public BaseType { // expected-note {{base class 'BaseType' specified here}} + static int base_type; + Derived() : basetype() {} // expected-error{{initializer 'basetype' does not name a non-static data member or base class; did you mean the base class 'BaseType'?}} +}; -- 2.40.0