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
diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h
index 68016e4..2d6d743 100644
--- a/include/clang/Sema/Sema.h
+++ b/include/clang/Sema/Sema.h
@@ -1835,10 +1835,18 @@
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 9537c30..0bb378b 100644
--- a/include/clang/Sema/TypoCorrection.h
+++ b/include/clang/Sema/TypoCorrection.h
@@ -135,6 +135,32 @@
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 e9c842b..f21d766 100644
--- a/lib/Sema/SemaDeclCXX.cpp
+++ b/lib/Sema/SemaDeclCXX.cpp
@@ -1745,6 +1745,30 @@
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<FieldDecl>(ND))
+ return Member->getDeclContext()->getRedeclContext()->Equals(ClassDecl);
+ else
+ return isa<TypeDecl>(ND);
+ }
+ return false;
+ }
+
+ private:
+ CXXRecordDecl *ClassDecl;
+};
+
+}
+
/// \brief Handle a C++ member initializer.
MemInitResult
Sema::BuildMemInitializer(Decl *ConstructorD,
@@ -1837,24 +1861,23 @@
// 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<FieldDecl>()) {
- 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;
+ // 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);
- }
+ return BuildMemberInitializer(Member, Args, IdLoc);
} else if (TypeDecl *Type = Corr.getCorrectionDeclAs<TypeDecl>()) {
const CXXBaseSpecifier *DirectBaseSpec;
const CXXBaseSpecifier *VirtualBaseSpec;
@@ -5750,35 +5773,47 @@
}
}
+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<NamespaceDecl>(ND) || isa<NamespaceAliasDecl>(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<NamespaceDecl>() ||
- Corrected.getCorrectionDeclAs<NamespaceAliasDecl>()) {
- 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 eab1517..5bf0ca5 100644
--- a/lib/Sema/SemaLookup.cpp
+++ b/lib/Sema/SemaLookup.cpp
@@ -3310,13 +3310,13 @@
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<ObjCInterfaceDecl>(MemberContext)) {
- if (CTC == Sema::CTC_ObjCIvarLookup) {
+ if (isObjCIvarLookup) {
if (ObjCIvarDecl *Ivar = Class->lookupInstanceVariable(Name)) {
Res.addDecl(Ivar);
Res.resolveKind();
@@ -3357,61 +3357,11 @@
/// \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;
+ Scope *S, CorrectionCandidateCallback &CCC) {
+ if (CCC.WantObjCSuper)
+ Consumer.addKeywordResult("super");
- 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) {
+ 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 @@
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 @@
}
}
- if (WantRemainingKeywords) {
+ if (CCC.WantRemainingKeywords) {
if (SemaRef.getCurFunctionOrMethodDecl() || SemaRef.getCurBlock()) {
// Statements.
const char *CStmts[] = {
@@ -3533,6 +3483,77 @@
}
}
+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 @@
/// \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 @@
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 @@
}
}
- 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 @@
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<IdentifierInfo*, 16> QualifiedResults;
LookupResult TmpRes(*this, TypoName, LookupKind);
TmpRes.suppressDiagnostics();
@@ -3715,16 +3739,21 @@
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 @@
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<NamedDecl>());
++I;
+ if (CCC && !CCC->ValidateCandidate(Prev->second))
+ DI->second->erase(Prev);
break;
}
+
+ }
}
if (DI->second->empty())
@@ -3790,6 +3827,8 @@
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<NamedDecl>(),
@@ -3863,7 +3902,12 @@
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 be3fb84..0fb33bc 100644
--- a/test/SemaCXX/typo-correction.cpp
+++ b/test/SemaCXX/typo-correction.cpp
@@ -29,3 +29,14 @@
inline error_condition make_error_condition(errc _e) {
return error_condition(static_cast<int>(_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'?}}
+};