On Fri, Oct 3, 2014 at 11:45 AM, David Blaikie <[email protected]> wrote:
> Given that these are parts of a patch series, some of which have already
> been signed off on/I haven't looked at, it's a bit hard to review the
> overall design - but I believe you & Richard talked that over, so I'm not
> too worried about that (just explaining why my review feedback's going to
> be a bit narrow)
>
> + if (NumTypos > 0 && !ExprEvalContexts.empty())
> + ExprEvalContexts.back().NumTypos += NumTypos;
> + else
> + assert(NumTypos == 0 && "There are outstanding typos after popping the "
> + "last ExpressionEvaluationContextRecord");
>
> Could this be simplified? I think it's equivalent to:
>
> if (!ExprEvalContexts.empty())
> ExprEvalContexts.back().NumTypos += NumTypos;
> else
> assert(NumTypos == 0 ... );
>
> or:
>
> assert(ExprEvalContexts.empty() || NumTypos != 0 && ...)
> if (!ExprEvalContexts.empty())
> ...
>
> Yes, the "NumTypos > 0" was unnecessary and has been removed. (FYI, the
second form you suggested doesn't quite work because the assert would fail
on the common case where ExprEvalContexts is not empty and there are no
typos.)
> There's an unnecessary semicolon at the end of the "while (true)" loop in
> TransformTypos::Transform
>
> Removed.
>
>
>
> I'd roll this up a bit:
>
>
> + auto &State = SemaRef.getTypoExprState(TE);
> + if (State.DiagHandler)
> + (*State.DiagHandler)(State.Consumer->getCurrentCorrection());
>
> into:
>
> if (auto *Handler = SemaRef.getTypoExprState(TE).DiagHandler)
> (*DiagHandler)(State.Consumer->getCurrentCorrection());
>
> That rollup doesn't work because State would be unknown when accessing
State.Consumer in the argument to the DiagHandler.
> count in the condition, plus insert in the body of the if in
> TransformTypos::TransformTypoExpr would be more efficient as a single
> insert-and-check (by avoiding two separate lookups into the set):
> if (TypoExprs.insert(E).second) // means this element previously didn't
> exist in the set, and now does
>
> I've changed the count-then-insert to just an insert since
SmallPtrSetImpl::insert returns true when the item wasn't already in the
set.
> This:
>
> TransformCache.find(E) != TransformCache.end()
>
> might commonly be written as:
>
> TransformCache.count(E)
>
> Optionally with "!= 0", but often count is used directly as a boolean test,
> especially when it's a single set/map (not a multi set/map).
>
> Changed.
> This comment didn't quite make sense to me:
>
>
> + // If corrections for the first TypoExpr have been exhausted, retry
> them
> + // against the next combination of substitutions for all of the other
> + // TypoExprs.
>
> When you say "retry them" which them do you mean? The corrections for the
> first TypoExpr? But what does it mean for them to be exhausted then, if they
> can still be considered?
>
> Maybe I need a bit of help with the higher level algorithm here - I assume
> the idea is:
>
> Call transform.
> As it visits TypoExpr nodes, it tries their first typo candidate available.
> If we get to the end and have invalid code, reset the state of all the typo
> corrections except the first one (on its next query, it'll give back its
> second alternative).
>
> Here is where the confusion is at. As the transform visits each TypoExpr
node it tries the first candidate available. If the first candidates of all
the TypoExprs don't work, it then tries the next (and subsequent)
candidates of only the first TypoExpr that was seen. When it exhausts the
candidates from the first TypoExpr, it then resets the stream of candidates
for the first TypoExpr and fetches the next candidate of the TypoExpr,
continuing in this way until all combinations of candidates of all
TypoExprs have been tried. So for 3 TypoExprs... let's say 1 has the
candidates (A, B, C), 2 has the candidates (D, E, F, G) and 3 has the
candidates (H, I) the iterations would test the combinations in order:
A D H
B D H
C D H
A E H
B E H
C E H
A F H
B F H
C F H
A G H
B G H
C G H
A D I
B D I
C D I
A E I
B E I
C E I
A F I
B F I
C F I
A G I
B G I
C G I
> If we get to the end, have invalid code, and the first typo has no
> alternatives left to try - I guess we just want to print errors for
> everything, without typo corrections?
>
>
If all of the combinations of typo corrections failed to create an
acceptable Expr, then the current correction for all of TypoExprs would be
an empty TypoCorrection which would cause the corresponding diagnostic
handlers to print out errors without suggestions.
> It might be helpful to split out some of these chunks (the state resetting
> code and maybe the diagnostic printing at the end) into separate functions to
> give them logical names & make the higher level algorithm more clear?
>
> I've split the diagnostics emission and the state handling into separate
helpers, and expanded their description comments. I'm not keen on the name
for the state handler but it is the best name I could come up with that was
even remotely descriptive.
> The loop at the end ("for (auto E : TypoExprs) {") is over a hashing data
> structure, right? Is that going to cause unstable output of diagnostics? (eg:
> different machines, different memory layout, the same program might get the
> typos printed in a different order) or is there something else that will fix
> that?
>
>
> If you have to change the set to be a SetVector, or similar (to stabilize
> output) - then you won't need the FirstTypoExpr anymore - it'll just be the
> first one in the set, since you iterate in insertion order at that point.
>
> Switched it from a SmallPtrSet to a SmallSetVector like it should have
been. Doing so also allowed me to simplify the state handling code by being
able to assume a deterministic order, and eliminating FirstTypoExpr.
>
>
> "while (TypoCorrection TC = State.Consumer->getNextCorrection()) {" doesn't
> seem to be a loop - it has an unconditional return at the end of the body.
> Should it just be an 'if'?
>
> There was actually a missing check on whether the result of
Sema::BuildDeclarationNameExpr was valid.
> "+ (FullExpr.get()->isTypeDependent() ||
>
> + FullExpr.get()->isValueDependent() ||
> + FullExpr.get()->isInstantiationDependent())) {"
>
> What are those checks for? I don't know what condition we would be in if we
> had typo corrections but it wasn't type, value, or instantiation dependent.
> Perhaps a comment could explain that?
>
> The check is to avoid running the tree transform on Expr trees that are
known to not have a TypoExpr in them, regardless of whether the current
expression evaluation context still indicates there are uncorrected typos.
I've added a comment to that effect.
>
> To simplify Sema::clearDelayedTypo you could add MapVector::erase(const
> KeyType&) (given that std::map has such an operation, it seems like a
> reasonable one for MapVector to have).
>
> Done (simple patch attached).
> Sema::getTypoExprState looks like it could be a const function (it looks
> like it's never meant to be called when the element isn't already in the
> map) - using 'lookup' instead of operator[] will be a const operation on
> the map (& assert if the element doesn't exist in the map).
>
Sadly I cannot do that because the operator[] cannot be used if the method
is made const, and .lookup() can't be used because it returns by-value
instead of by-reference and TypoExprState is not copyable:
../tools/clang/lib/Sema/SemaLookup.cpp:4600:10: warning: returning
reference to local temporary object [-Wreturn-stack-address]
return DelayedTypos.lookup(TE);
../include/llvm/ADT/MapVector.h:88:30: error: call to implicitly-deleted
copy constructor of 'const clang::Sema::TypoExprState'
return Pos == Map.end()? ValueT() : Vector[Pos->second].second;
> Otherwise this all looks like rather neat stuff - though I'm not as able
> to review in detail some of the refactoring of existing typo correction
> stuff. It looks plausible.
>
I've attached the updated patch. Thanks for reviewing this, David!
>
>
On Thu, Sep 25, 2014 at 2:00 PM, Kaelyn Takata <[email protected]> wrote:
>
>> ping
>>
>> On Tue, Aug 26, 2014 at 11:05 AM, Kaelyn Takata <[email protected]> wrote:
>>
>>>
>>> +dblaikie
>>>
>>>
>>> On Thu, Jul 31, 2014 at 1:20 PM, Kaelyn Takata <[email protected]> wrote:
>>>
>>>>
>>>> Part of the infrastructure is a map from a TypoExpr to the Sema-specific
>>>> state needed to correct it, along with helpers to ease dealing with the
>>>> state.
>>>>
>>>> The the typo count is propagated up the stack of
>>>> ExpressionEvaluationContextRecords when one is popped off of to
>>>> avoid accidentally dropping TypoExprs on the floor. For example,
>>>> the attempted correction of g() in test/CXX/class/class.mem/p5-0x.cpp
>>>> happens with an ExpressionEvaluationContextRecord that is popped off
>>>> the stack prior to ActOnFinishFullExpr being called and the tree
>>>> transform for TypoExprs being run.
>>>> ---
>>>> include/clang/Sema/Sema.h | 44 +++++
>>>> include/clang/Sema/SemaInternal.h | 15 +-
>>>> include/clang/Sema/TypoCorrection.h | 2 +-
>>>> lib/Sema/SemaExpr.cpp | 7 +
>>>> lib/Sema/SemaExprCXX.cpp | 108 ++++++++++++
>>>> lib/Sema/SemaLookup.cpp | 316
>>>> ++++++++++++++++++++++++------------
>>>> 6 files changed, 384 insertions(+), 108 deletions(-)
>>>>
>>>>
>>>
>>
>
diff --git a/include/llvm/ADT/MapVector.h b/include/llvm/ADT/MapVector.h
index 4e1fc15..d82c579 100644
--- a/include/llvm/ADT/MapVector.h
+++ b/include/llvm/ADT/MapVector.h
@@ -147,6 +147,17 @@ public:
return Next;
}
+ /// \brief Remove all elements with the key value Key.
+ ///
+ /// Returns the number of elements removed.
+ size_type erase(const KeyT &Key) {
+ auto Iterator = find(Key);
+ if (Iterator == end())
+ return 0;
+ erase(Iterator);
+ return 1;
+ }
+
/// \brief Remove the elements that match the predicate.
///
/// Erase all elements that match \c Pred in a single pass. Takes linear
diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h
index d94403d..5bd7e7f 100644
--- a/include/clang/Sema/Sema.h
+++ b/include/clang/Sema/Sema.h
@@ -168,6 +168,8 @@ namespace clang {
class TypedefDecl;
class TypedefNameDecl;
class TypeLoc;
+ class TypoCorrectionConsumer;
+ class TypoDiagnosticGenerator;
class UnqualifiedId;
class UnresolvedLookupExpr;
class UnresolvedMemberExpr;
@@ -763,6 +765,10 @@ public:
/// this expression evaluation context.
unsigned NumCleanupObjects;
+ /// \brief The number of typos encountered during this expression evaluation
+ /// context (i.e. the number of TypoExprs created).
+ unsigned NumTypos;
+
llvm::SmallPtrSet<Expr*, 2> SavedMaybeODRUseExprs;
/// \brief The lambdas that are present within this context, if it
@@ -796,6 +802,7 @@ public:
bool IsDecltype)
: Context(Context), ParentNeedsCleanups(ParentNeedsCleanups),
IsDecltype(IsDecltype), NumCleanupObjects(NumCleanupObjects),
+ NumTypos(0),
ManglingContextDecl(ManglingContextDecl), MangleNumbering() { }
/// \brief Retrieve the mangling numbering context, used to consistently
@@ -2583,6 +2590,18 @@ public:
private:
bool CppLookupName(LookupResult &R, Scope *S);
+ struct TypoExprState {
+ std::unique_ptr<TypoCorrectionConsumer> Consumer;
+ std::unique_ptr<TypoDiagnosticGenerator> DiagHandler;
+ };
+
+ /// \brief The set of unhandled TypoExprs and their associated state.
+ llvm::MapVector<TypoExpr *, TypoExprState> DelayedTypos;
+
+ /// \brief Creates a new TypoExpr AST node.
+ TypoExpr *createDelayedTypo(std::unique_ptr<TypoCorrectionConsumer> TCC,
+ std::unique_ptr<TypoDiagnosticGenerator> TDG);
+
// \brief The set of known/encountered (unique, canonicalized) NamespaceDecls.
//
// The boolean value will be true to indicate that the namespace was loaded
@@ -2593,7 +2612,24 @@ private:
/// source.
bool LoadedExternalKnownNamespaces;
+ /// \brief Helper for CorrectTypo and CorrectTypoDelayed used to create and
+ /// populate a new TypoCorrectionConsumer. Returns nullptr if typo correction
+ /// should be skipped entirely.
+ std::unique_ptr<TypoCorrectionConsumer>
+ makeTypoCorrectionConsumer(const DeclarationNameInfo &Typo,
+ Sema::LookupNameKind LookupKind, Scope *S,
+ CXXScopeSpec *SS,
+ std::unique_ptr<CorrectionCandidateCallback> CCC,
+ DeclContext *MemberContext, bool EnteringContext,
+ const ObjCObjectPointerType *OPT,
+ bool ErrorRecovery, bool &IsUnqualifiedLookup);
+
public:
+ const TypoExprState &getTypoExprState(TypoExpr *TE);
+
+ /// \brief Clears the state of the given TypoExpr.
+ void clearDelayedTypo(TypoExpr *TE);
+
/// \brief Look up a name, looking for a single declaration. Return
/// null if the results were absent, ambiguous, or overloaded.
///
@@ -2671,6 +2707,14 @@ public:
const ObjCObjectPointerType *OPT = nullptr,
bool RecordFailure = true);
+ TypoExpr *CorrectTypoDelayed(
+ const DeclarationNameInfo &Typo, Sema::LookupNameKind LookupKind,
+ Scope *S, CXXScopeSpec *SS,
+ std::unique_ptr<CorrectionCandidateCallback> CCC,
+ std::unique_ptr<TypoDiagnosticGenerator> TDG, CorrectTypoKind Mode,
+ DeclContext *MemberContext = nullptr, bool EnteringContext = false,
+ const ObjCObjectPointerType *OPT = nullptr);
+
void diagnoseTypo(const TypoCorrection &Correction,
const PartialDiagnostic &TypoDiag,
bool ErrorRecovery = true);
diff --git a/include/clang/Sema/SemaInternal.h b/include/clang/Sema/SemaInternal.h
index 64d9994..76bbd66 100644
--- a/include/clang/Sema/SemaInternal.h
+++ b/include/clang/Sema/SemaInternal.h
@@ -101,8 +101,10 @@ public:
DeclContext *MemberContext,
bool EnteringContext)
: Typo(TypoName.getName().getAsIdentifierInfo()), CurrentTCIndex(0),
- SemaRef(SemaRef), S(S), SS(SS), CorrectionValidator(std::move(CCC)),
- MemberContext(MemberContext), Result(SemaRef, TypoName, LookupKind),
+ SemaRef(SemaRef), S(S),
+ SS(SS ? llvm::make_unique<CXXScopeSpec>(*SS) : nullptr),
+ CorrectionValidator(std::move(CCC)), MemberContext(MemberContext),
+ Result(SemaRef, TypoName, LookupKind),
Namespaces(SemaRef.Context, SemaRef.CurContext, SS),
EnteringContext(EnteringContext), SearchNamespaces(false) {
Result.suppressDiagnostics();
@@ -167,6 +169,16 @@ public:
CurrentTCIndex = 0;
}
+ /// \brief Return whether the end of the stream of corrections has been
+ /// reached.
+ bool finished() {
+ return CorrectionResults.empty() &&
+ CurrentTCIndex >= ValidatedCorrections.size();
+ }
+
+ ASTContext &getContext() const { return SemaRef.Context; }
+ const LookupResult &getLookupResult() const { return Result; }
+
private:
class NamespaceSpecifierSet {
struct SpecifierInfo {
@@ -243,7 +255,7 @@ private:
Sema &SemaRef;
Scope *S;
- CXXScopeSpec *SS;
+ std::unique_ptr<CXXScopeSpec> SS;
std::unique_ptr<CorrectionCandidateCallback> CorrectionValidator;
DeclContext *MemberContext;
LookupResult Result;
diff --git a/include/clang/Sema/TypoCorrection.h b/include/clang/Sema/TypoCorrection.h
index 26bf0ac..a69fc56 100644
--- a/include/clang/Sema/TypoCorrection.h
+++ b/include/clang/Sema/TypoCorrection.h
@@ -336,6 +336,12 @@ public:
}
};
+class TypoDiagnosticGenerator {
+ public:
+ virtual void operator()(const TypoCorrection &TC) = 0;
+ virtual ~TypoDiagnosticGenerator() = default;
+};
+
}
#endif
diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp
index 84caf01..87d0d00 100644
--- a/lib/Sema/SemaExpr.cpp
+++ b/lib/Sema/SemaExpr.cpp
@@ -11286,6 +11286,7 @@ Sema::PushExpressionEvaluationContext(ExpressionEvaluationContext NewContext,
void Sema::PopExpressionEvaluationContext() {
ExpressionEvaluationContextRecord& Rec = ExprEvalContexts.back();
+ unsigned NumTypos = Rec.NumTypos;
if (!Rec.Lambdas.empty()) {
if (Rec.isUnevaluated() || Rec.Context == ConstantEvaluated) {
@@ -11338,6 +11339,12 @@ void Sema::PopExpressionEvaluationContext() {
// Pop the current expression evaluation context off the stack.
ExprEvalContexts.pop_back();
+
+ if (!ExprEvalContexts.empty())
+ ExprEvalContexts.back().NumTypos += NumTypos;
+ else
+ assert(NumTypos == 0 && "There are outstanding typos after popping the "
+ "last ExpressionEvaluationContextRecord");
}
void Sema::DiscardCleanupsInEvaluationContext() {
diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp
index d79dcca..bc54bf0 100644
--- a/lib/Sema/SemaExprCXX.cpp
+++ b/lib/Sema/SemaExprCXX.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "clang/Sema/SemaInternal.h"
+#include "TreeTransform.h"
#include "TypeLocBuilder.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h"
@@ -5914,6 +5915,107 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
CurrentLSI->clearPotentialCaptures();
}
+namespace {
+class TransformTypos : public TreeTransform<TransformTypos> {
+ typedef TreeTransform<TransformTypos> BaseTransform;
+
+ llvm::SmallSetVector<TypoExpr *, 2> TypoExprs;
+ llvm::SmallDenseMap<TypoExpr *, ExprResult, 2> TransformCache;
+
+ /// \brief Emit diagnostics for all of the TypoExprs encountered.
+ /// If the TypoExprs were successfully corrected, then the diagnostics should
+ /// suggest the corrections. Otherwise the diagnostics will not suggest
+ /// anything (having been passed an empty TypoCorrection).
+ void EmitAllDiagnostics() {
+ for (auto E : TypoExprs) {
+ TypoExpr *TE = cast<TypoExpr>(E);
+ auto &State = SemaRef.getTypoExprState(TE);
+ if (State.DiagHandler)
+ (*State.DiagHandler)(State.Consumer->getCurrentCorrection());
+ SemaRef.clearDelayedTypo(TE);
+ }
+ }
+
+ /// \brief If corrections for the first TypoExpr have been exhausted for a
+ /// given combination of the other TypoExprs, retry those corrections against
+ /// the next combination of substitutions for the other TypoExprs by advancing
+ /// to the next potential correction of the second TypoExpr. For the second
+ /// and subsequent TypoExprs, if its stream of corrections has been exhausted,
+ /// the stream is reset and the next TypoExpr's stream is advanced by one (a
+ /// TypoExpr's correction stream is advanced by removing the TypoExpr from the
+ /// TransformCache). Returns true if there is still any untried combinations
+ /// of corrections.
+ bool CheckAndAdvanceTypoExprCorrectionStreams() {
+ if (TypoExprs.empty())
+ return false;
+
+ unsigned numFinished = 0;
+ for (auto TE : TypoExprs) {
+ auto &State = SemaRef.getTypoExprState(TE);
+ TransformCache.erase(TE);
+ if (State.Consumer->finished()) {
+ ++numFinished;
+ State.Consumer->resetCorrectionStream();
+ } else {
+ break;
+ }
+ }
+ return numFinished < TypoExprs.size();
+ }
+
+public:
+ TransformTypos(Sema &SemaRef) : BaseTransform(SemaRef) {}
+
+ ExprResult TransformLambdaExpr(LambdaExpr *E) { return Owned(E); }
+
+ ExprResult Transform(Expr *E) {
+ ExprResult res;
+ bool error = false;
+ while (true) {
+ Sema::SFINAETrap Trap(SemaRef);
+ res = TransformExpr(E);
+ error = Trap.hasErrorOccurred();
+
+ // Exit if either the transform was valid or if there were no TypoExprs
+ // to transform that still have any untried correction candidates..
+ if (!(error || res.isInvalid()) ||
+ !CheckAndAdvanceTypoExprCorrectionStreams())
+ break;
+ }
+
+ EmitAllDiagnostics();
+
+ return res;
+ }
+
+ ExprResult TransformTypoExpr(TypoExpr *E) {
+ // If the TypoExpr hasn't been seen before, record it. Otherwise, return the
+ // cached transformation result if there is one and the TypoExpr isn't the
+ // first one that was encountered.
+ if (!TypoExprs.insert(E) && E != TypoExprs[0] && TransformCache.count(E)) {
+ return TransformCache[E];
+ }
+
+ auto &State = SemaRef.getTypoExprState(E);
+ assert(State.Consumer && "Cannot transform a cleared TypoExpr");
+
+ // For the first TypoExpr and an uncached TypoExpr, find the next likely
+ // typo correction and return it.
+ while (TypoCorrection TC = State.Consumer->getNextCorrection()) {
+ LookupResult R(SemaRef,
+ State.Consumer->getLookupResult().getLookupNameInfo(),
+ State.Consumer->getLookupResult().getLookupKind());
+ if (!TC.isKeyword())
+ R.addDecl(TC.getCorrectionDecl());
+ ExprResult NE =
+ SemaRef.BuildDeclarationNameExpr(CXXScopeSpec(), R, false);
+ if (!NE.isInvalid())
+ return TransformCache[E] = NE;
+ }
+ return TransformCache[E] = ExprError();
+ }
+};
+}
ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC,
bool DiscardedValue,
@@ -5961,6 +6063,22 @@ ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC,
return ExprError();
}
+ // If the current evaluation context indicates there are uncorrected typos
+ // and the current expression isn't guaranteed to not have typos, try to
+ // resolve any TypoExpr nodes that might be in the expression.
+ if (ExprEvalContexts.back().NumTypos &&
+ (FullExpr.get()->isTypeDependent() ||
+ FullExpr.get()->isValueDependent() ||
+ FullExpr.get()->isInstantiationDependent())) {
+ auto TyposResolved = DelayedTypos.size();
+ FullExpr = TransformTypos(*this).Transform(FullExpr.get());
+ TyposResolved -= DelayedTypos.size();
+ if (TyposResolved)
+ ExprEvalContexts.back().NumTypos -= TyposResolved;
+ if (FullExpr.isInvalid())
+ return ExprError();
+ }
+
CheckCompletedExpr(FullExpr.get(), CC, IsConstexpr);
// At the end of this full expression (which could be a deeply nested
diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp
index 2e19cef..1010500 100644
--- a/lib/Sema/SemaLookup.cpp
+++ b/lib/Sema/SemaLookup.cpp
@@ -3488,7 +3488,7 @@ const TypoCorrection &TypoCorrectionConsumer::getNextCorrection() {
bool TypoCorrectionConsumer::resolveCorrection(TypoCorrection &Candidate) {
IdentifierInfo *Name = Candidate.getCorrectionAsIdentifierInfo();
DeclContext *TempMemberContext = MemberContext;
- CXXScopeSpec *TempSS = SS;
+ CXXScopeSpec *TempSS = SS.get();
if (Candidate.getCorrectionRange().isInvalid())
Candidate.setCorrectionRange(TempSS, Result.getLookupNameInfo());
retry_lookup:
@@ -3508,7 +3508,7 @@ retry_lookup:
}
if (TempMemberContext) {
if (SS && !TempSS)
- TempSS = SS;
+ TempSS = SS.get();
TempMemberContext = nullptr;
goto retry_lookup;
}
@@ -4001,101 +4001,59 @@ static void checkCorrectionVisibility(Sema &SemaRef, TypoCorrection &TC) {
}
}
-/// \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 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.
-///
-/// \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 OPT when non-NULL, the search for visible declarations will
-/// also walk the protocols in the qualified interfaces of \p 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,
- std::unique_ptr<CorrectionCandidateCallback> CCC,
- CorrectTypoKind Mode,
- DeclContext *MemberContext,
- bool EnteringContext,
- const ObjCObjectPointerType *OPT,
- bool RecordFailure) {
- assert(CCC && "CorrectTypo requires a CorrectionCandidateCallback");
-
- // Always let the ExternalSource have the first chance at correction, even
- // if we would otherwise have given up.
- if (ExternalSource) {
- if (TypoCorrection Correction = ExternalSource->CorrectTypo(
- TypoName, LookupKind, S, SS, *CCC, MemberContext, EnteringContext, OPT))
- return Correction;
- }
+std::unique_ptr<TypoCorrectionConsumer> Sema::makeTypoCorrectionConsumer(
+ const DeclarationNameInfo &TypoName, Sema::LookupNameKind LookupKind,
+ Scope *S, CXXScopeSpec *SS,
+ std::unique_ptr<CorrectionCandidateCallback> CCC,
+ DeclContext *MemberContext, bool EnteringContext,
+ const ObjCObjectPointerType *OPT, bool ErrorRecovery,
+ bool &IsUnqualifiedLookup) {
if (Diags.hasFatalErrorOccurred() || !getLangOpts().SpellChecking ||
DisableTypoCorrection)
- return TypoCorrection();
+ return nullptr;
// In Microsoft mode, don't perform typo correction in a template member
// function dependent context because it interferes with the "lookup into
// dependent bases of class templates" feature.
if (getLangOpts().MSVCCompat && CurContext->isDependentContext() &&
isa<CXXMethodDecl>(CurContext))
- return TypoCorrection();
+ return nullptr;
// We only attempt to correct typos for identifiers.
IdentifierInfo *Typo = TypoName.getName().getAsIdentifierInfo();
if (!Typo)
- return TypoCorrection();
+ return nullptr;
// If the scope specifier itself was invalid, don't try to correct
// typos.
if (SS && SS->isInvalid())
- return TypoCorrection();
+ return nullptr;
// Never try to correct typos during template deduction or
// instantiation.
if (!ActiveTemplateInstantiations.empty())
- return TypoCorrection();
+ return nullptr;
// Don't try to correct 'super'.
if (S && S->isInObjcMethodScope() && Typo == getSuperIdentifier())
- return TypoCorrection();
+ return nullptr;
// Abort if typo correction already failed for this specific typo.
IdentifierSourceLocations::iterator locs = TypoCorrectionFailures.find(Typo);
if (locs != TypoCorrectionFailures.end() &&
locs->second.count(TypoName.getLoc()))
- return TypoCorrection();
+ return nullptr;
// Don't try to correct the identifier "vector" when in AltiVec mode.
// TODO: Figure out why typo correction misbehaves in this case, fix it, and
// remove this workaround.
if (getLangOpts().AltiVec && Typo->isStr("vector"))
- return TypoCorrection();
+ return nullptr;
// If we're handling a missing symbol error, using modules, and the
// special search all modules option is used, look for a missing import.
- if ((Mode == CTK_ErrorRecovery) && getLangOpts().Modules &&
+ if (ErrorRecovery && getLangOpts().Modules &&
getLangOpts().ModulesSearchAll) {
// The following has the side effect of loading the missing module.
getModuleLoader().lookupMissingImports(Typo->getName(),
@@ -4103,9 +4061,9 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
}
CorrectionCandidateCallback &CCCRef = *CCC;
- TypoCorrectionConsumer Consumer(*this, TypoName, LookupKind, S, SS,
- std::move(CCC), MemberContext,
- EnteringContext);
+ auto Consumer = llvm::make_unique<TypoCorrectionConsumer>(
+ *this, TypoName, LookupKind, S, SS, std::move(CCC), MemberContext,
+ EnteringContext);
// If a callback object considers an empty typo correction candidate to be
// viable, assume it does not do any actual validation of the candidates.
@@ -4113,29 +4071,29 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
bool ValidatingCallback = !isCandidateViable(CCCRef, EmptyCorrection);
// Perform name lookup to find visible, similarly-named entities.
- bool IsUnqualifiedLookup = false;
+ IsUnqualifiedLookup = false;
DeclContext *QualifiedDC = MemberContext;
if (MemberContext) {
- LookupVisibleDecls(MemberContext, LookupKind, Consumer);
+ LookupVisibleDecls(MemberContext, LookupKind, *Consumer);
// Look in qualified interfaces.
if (OPT) {
for (auto *I : OPT->quals())
- LookupVisibleDecls(I, LookupKind, Consumer);
+ LookupVisibleDecls(I, LookupKind, *Consumer);
}
} else if (SS && SS->isSet()) {
QualifiedDC = computeDeclContext(*SS, EnteringContext);
if (!QualifiedDC)
- return TypoCorrection();
+ return nullptr;
// 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 TypoCorrection();
+ return nullptr;
++TyposCorrected;
- LookupVisibleDecls(QualifiedDC, LookupKind, Consumer);
+ LookupVisibleDecls(QualifiedDC, LookupKind, *Consumer);
} else {
IsUnqualifiedLookup = true;
UnqualifiedTyposCorrectedMap::iterator Cached
@@ -4152,13 +4110,13 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
CorrectionDecl->getLocation());
LookupResult R(*this, NameInfo, LookupOrdinaryName);
if (LookupName(R, S))
- Consumer.addCorrection(Cached->second);
+ Consumer->addCorrection(Cached->second);
}
} else {
// Only honor no-correction cache hits when a callback that will validate
// correction candidates is not being used.
if (!ValidatingCallback)
- return TypoCorrection();
+ return nullptr;
}
}
if (Cached == UnqualifiedTyposCorrected.end()) {
@@ -4166,7 +4124,7 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
// 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 TypoCorrection();
+ return nullptr;
}
}
@@ -4175,17 +4133,13 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
bool SearchNamespaces
= getLangOpts().CPlusPlus &&
(IsUnqualifiedLookup || (SS && SS->isSet()));
- // In a few cases we *only* want to search for corrections based on just
- // adding or changing the nested name specifier.
- unsigned TypoLen = Typo->getName().size();
- bool AllowOnlyNNSChanges = TypoLen < 3;
if (IsUnqualifiedLookup || SearchNamespaces) {
// For unqualified lookup, look through all of the names that we have
// seen in this translation unit.
// FIXME: Re-add the ability to skip very unlikely potential corrections.
for (const auto &I : Context.Idents)
- Consumer.FoundName(I.getKey());
+ Consumer->FoundName(I.getKey());
// Walk through identifiers in external identifier sources.
// FIXME: Re-add the ability to skip very unlikely potential corrections.
@@ -4197,24 +4151,12 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
if (Name.empty())
break;
- Consumer.FoundName(Name);
+ Consumer->FoundName(Name);
} while (true);
}
}
- AddKeywordsToConsumer(*this, Consumer, S, CCCRef, SS && SS->isNotEmpty());
-
- // If we haven't found anything, we're done.
- if (Consumer.empty())
- return FailedCorrection(Typo, TypoName.getLoc(), RecordFailure,
- IsUnqualifiedLookup);
-
- // Make sure the best edit distance (prior to adding any namespace qualifiers)
- // is not more that about a third of the length of the typo's identifier.
- unsigned ED = Consumer.getBestEditDistance(true);
- if (ED > 0 && TypoLen / ED < 3)
- return FailedCorrection(Typo, TypoName.getLoc(), RecordFailure,
- IsUnqualifiedLookup);
+ AddKeywordsToConsumer(*this, *Consumer, S, CCCRef, SS && SS->isNotEmpty());
// Build the NestedNameSpecifiers for the KnownNamespaces, if we're going
// to search those namespaces.
@@ -4228,17 +4170,101 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
KnownNamespaces[N] = true;
}
- Consumer.addNamespaces(KnownNamespaces);
+ Consumer->addNamespaces(KnownNamespaces);
+ }
+
+ return Consumer;
+}
+
+/// \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 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.
+///
+/// \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 OPT when non-NULL, the search for visible declarations will
+/// also walk the protocols in the qualified interfaces of \p 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,
+ std::unique_ptr<CorrectionCandidateCallback> CCC,
+ CorrectTypoKind Mode,
+ DeclContext *MemberContext,
+ bool EnteringContext,
+ const ObjCObjectPointerType *OPT,
+ bool RecordFailure) {
+ assert(CCC && "CorrectTypo requires a CorrectionCandidateCallback");
+
+ // Always let the ExternalSource have the first chance at correction, even
+ // if we would otherwise have given up.
+ if (ExternalSource) {
+ if (TypoCorrection Correction = ExternalSource->CorrectTypo(
+ TypoName, LookupKind, S, SS, *CCC, MemberContext, EnteringContext, OPT))
+ return Correction;
}
- TypoCorrection BestTC = Consumer.getNextCorrection();
- TypoCorrection SecondBestTC = Consumer.getNextCorrection();
+ // 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.
+ bool ObjCMessageReceiver = CCC->WantObjCSuper && !CCC->WantRemainingKeywords;
+
+ TypoCorrection EmptyCorrection;
+ bool ValidatingCallback = !isCandidateViable(*CCC, EmptyCorrection);
+
+ IdentifierInfo *Typo = TypoName.getName().getAsIdentifierInfo();
+ bool IsUnqualifiedLookup = false;
+ auto Consumer = makeTypoCorrectionConsumer(
+ TypoName, LookupKind, S, SS, std::move(CCC), MemberContext,
+ EnteringContext, OPT, Mode == CTK_ErrorRecovery, IsUnqualifiedLookup);
+
+ if (!Consumer)
+ return TypoCorrection();
+
+ // If we haven't found anything, we're done.
+ if (Consumer->empty())
+ return FailedCorrection(Typo, TypoName.getLoc(), RecordFailure,
+ IsUnqualifiedLookup);
+
+ // Make sure the best edit distance (prior to adding any namespace qualifiers)
+ // is not more that about a third of the length of the typo's identifier.
+ unsigned ED = Consumer->getBestEditDistance(true);
+ unsigned TypoLen = Typo->getName().size();
+ if (ED > 0 && TypoLen / ED < 3)
+ return FailedCorrection(Typo, TypoName.getLoc(), RecordFailure,
+ IsUnqualifiedLookup);
+
+ TypoCorrection BestTC = Consumer->getNextCorrection();
+ TypoCorrection SecondBestTC = Consumer->getNextCorrection();
if (!BestTC)
return FailedCorrection(Typo, TypoName.getLoc(), RecordFailure);
ED = BestTC.getEditDistance();
- if (!AllowOnlyNNSChanges && ED > 0 && TypoLen / ED < 3) {
+ if (TypoLen >= 3 && ED > 0 && TypoLen / ED < 3) {
// If this was an unqualified lookup and we believe the callback
// object wouldn't have filtered out possible corrections, note
// that no correction was found.
@@ -4264,21 +4290,15 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
TC.setCorrectionRange(SS, TypoName);
checkCorrectionVisibility(*this, TC);
return TC;
- }
- // 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.
- else if (SecondBestTC && CCCRef.WantObjCSuper &&
- !CCCRef.WantRemainingKeywords) {
+ } else if (SecondBestTC && ObjCMessageReceiver) {
// Prefer 'super' when we're completing in a message-receiver
// context.
if (BestTC.getCorrection().getAsString() != "super") {
if (SecondBestTC.getCorrection().getAsString() == "super")
BestTC = SecondBestTC;
- else if (Consumer["super"].front().isKeyword())
- BestTC = Consumer["super"].front();
+ else if ((*Consumer)["super"].front().isKeyword())
+ BestTC = (*Consumer)["super"].front();
}
// Don't correct to a keyword that's the same as the typo; the keyword
// wasn't actually in scope.
@@ -4301,6 +4321,73 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
IsUnqualifiedLookup && !ValidatingCallback);
}
+/// \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 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.
+///
+/// \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 TDG A TypoDiagnosticGenerator object that will be used to print
+/// diagnostics when the actual typo correction is attempted.
+///
+/// \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 OPT when non-NULL, the search for visible declarations will
+/// also walk the protocols in the qualified interfaces of \p OPT.
+///
+/// \returns a new \c TypoExpr that will later be replaced in the AST with an
+/// Expr representing the result of performing typo correction, or nullptr if
+/// typo correction is not possible. If nullptr is returned, no diagnostics will
+/// be emitted and it is the responsibility of the caller to emit any that are
+/// needed.
+TypoExpr *Sema::CorrectTypoDelayed(
+ const DeclarationNameInfo &TypoName, Sema::LookupNameKind LookupKind,
+ Scope *S, CXXScopeSpec *SS,
+ std::unique_ptr<CorrectionCandidateCallback> CCC,
+ std::unique_ptr<TypoDiagnosticGenerator> TDG, CorrectTypoKind Mode,
+ DeclContext *MemberContext, bool EnteringContext,
+ const ObjCObjectPointerType *OPT) {
+ assert(CCC && "CorrectTypoDelayed requires a CorrectionCandidateCallback");
+
+ TypoCorrection Empty;
+ bool IsUnqualifiedLookup = false;
+ auto Consumer = makeTypoCorrectionConsumer(
+ TypoName, LookupKind, S, SS, std::move(CCC), MemberContext,
+ EnteringContext, OPT,
+ /*SearchModules=*/(Mode == CTK_ErrorRecovery) && getLangOpts().Modules &&
+ getLangOpts().ModulesSearchAll,
+ IsUnqualifiedLookup);
+
+ if (!Consumer || Consumer->empty())
+ return nullptr;
+
+ // Make sure the best edit distance (prior to adding any namespace qualifiers)
+ // is not more that about a third of the length of the typo's identifier.
+ unsigned ED = Consumer->getBestEditDistance(true);
+ IdentifierInfo *Typo = TypoName.getName().getAsIdentifierInfo();
+ if (ED > 0 && Typo->getName().size() / ED < 3)
+ return nullptr;
+
+ ExprEvalContexts.back().NumTypos++;
+ return createDelayedTypo(std::move(Consumer), std::move(TDG));
+}
+
void TypoCorrection::addCorrectionDecl(NamedDecl *CDecl) {
if (!CDecl) return;
@@ -4497,3 +4584,22 @@ void Sema::diagnoseTypo(const TypoCorrection &Correction,
Diag(ChosenDecl->getLocation(), PrevNote)
<< CorrectedQuotedStr << (ErrorRecovery ? FixItHint() : FixTypo);
}
+
+TypoExpr *
+Sema::createDelayedTypo(std::unique_ptr<TypoCorrectionConsumer> TCC,
+ std::unique_ptr<TypoDiagnosticGenerator> TDG) {
+ assert(TCC && "createDelayedTypo requires a valid TypoCorrectionConsumer");
+ auto TE = new (Context) TypoExpr(Context.DependentTy);
+ auto &State = DelayedTypos[TE];
+ State.Consumer = std::move(TCC);
+ State.DiagHandler = std::move(TDG);
+ return TE;
+}
+
+const Sema::TypoExprState &Sema::getTypoExprState(TypoExpr *TE) {
+ return DelayedTypos[TE];
+}
+
+void Sema::clearDelayedTypo(TypoExpr *TE) {
+ DelayedTypos.erase(TE);
+}
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits