Skip to content

Commit

Permalink
Start adding the infrastructure for handling TypoExprs.
Browse files Browse the repository at this point in the history
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.

llvm-svn: 220695
  • Loading branch information
Kaelyn Takata committed Oct 27, 2014
1 parent 0d6a3ed commit 6c75951
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 107 deletions.
47 changes: 47 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -168,6 +168,7 @@ namespace clang {
class TypedefDecl; class TypedefDecl;
class TypedefNameDecl; class TypedefNameDecl;
class TypeLoc; class TypeLoc;
class TypoCorrectionConsumer;
class UnqualifiedId; class UnqualifiedId;
class UnresolvedLookupExpr; class UnresolvedLookupExpr;
class UnresolvedMemberExpr; class UnresolvedMemberExpr;
Expand Down Expand Up @@ -768,6 +769,10 @@ class Sema {
/// this expression evaluation context. /// this expression evaluation context.
unsigned NumCleanupObjects; 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; llvm::SmallPtrSet<Expr*, 2> SavedMaybeODRUseExprs;


/// \brief The lambdas that are present within this context, if it /// \brief The lambdas that are present within this context, if it
Expand Down Expand Up @@ -801,6 +806,7 @@ class Sema {
bool IsDecltype) bool IsDecltype)
: Context(Context), ParentNeedsCleanups(ParentNeedsCleanups), : Context(Context), ParentNeedsCleanups(ParentNeedsCleanups),
IsDecltype(IsDecltype), NumCleanupObjects(NumCleanupObjects), IsDecltype(IsDecltype), NumCleanupObjects(NumCleanupObjects),
NumTypos(0),
ManglingContextDecl(ManglingContextDecl), MangleNumbering() { } ManglingContextDecl(ManglingContextDecl), MangleNumbering() { }


/// \brief Retrieve the mangling numbering context, used to consistently /// \brief Retrieve the mangling numbering context, used to consistently
Expand Down Expand Up @@ -2585,9 +2591,23 @@ class Sema {
bool ConstThis, bool ConstThis,
bool VolatileThis); bool VolatileThis);


typedef std::function<void(const TypoCorrection &)> TypoDiagnosticGenerator;

private: private:
bool CppLookupName(LookupResult &R, Scope *S); bool CppLookupName(LookupResult &R, Scope *S);


struct TypoExprState {
std::unique_ptr<TypoCorrectionConsumer> Consumer;
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,
TypoDiagnosticGenerator TDG);

// \brief The set of known/encountered (unique, canonicalized) NamespaceDecls. // \brief The set of known/encountered (unique, canonicalized) NamespaceDecls.
// //
// The boolean value will be true to indicate that the namespace was loaded // The boolean value will be true to indicate that the namespace was loaded
Expand All @@ -2598,7 +2618,24 @@ class Sema {
/// source. /// source.
bool LoadedExternalKnownNamespaces; 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: public:
const TypoExprState &getTypoExprState(TypoExpr *TE) const;

/// \brief Clears the state of the given TypoExpr.
void clearDelayedTypo(TypoExpr *TE);

/// \brief Look up a name, looking for a single declaration. Return /// \brief Look up a name, looking for a single declaration. Return
/// null if the results were absent, ambiguous, or overloaded. /// null if the results were absent, ambiguous, or overloaded.
/// ///
Expand Down Expand Up @@ -2676,6 +2713,16 @@ class Sema {
const ObjCObjectPointerType *OPT = nullptr, const ObjCObjectPointerType *OPT = nullptr,
bool RecordFailure = true); bool RecordFailure = true);


TypoExpr *CorrectTypoDelayed(const DeclarationNameInfo &Typo,
Sema::LookupNameKind LookupKind, Scope *S,
CXXScopeSpec *SS,
std::unique_ptr<CorrectionCandidateCallback> CCC,
TypoDiagnosticGenerator TDG,
CorrectTypoKind Mode,
DeclContext *MemberContext = nullptr,
bool EnteringContext = false,
const ObjCObjectPointerType *OPT = nullptr);

void diagnoseTypo(const TypoCorrection &Correction, void diagnoseTypo(const TypoCorrection &Correction,
const PartialDiagnostic &TypoDiag, const PartialDiagnostic &TypoDiag,
bool ErrorRecovery = true); bool ErrorRecovery = true);
Expand Down
15 changes: 12 additions & 3 deletions clang/include/clang/Sema/SemaInternal.h
Expand Up @@ -101,8 +101,10 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer {
DeclContext *MemberContext, DeclContext *MemberContext,
bool EnteringContext) bool EnteringContext)
: Typo(TypoName.getName().getAsIdentifierInfo()), CurrentTCIndex(0), : Typo(TypoName.getName().getAsIdentifierInfo()), CurrentTCIndex(0),
SemaRef(SemaRef), S(S), SS(SS), CorrectionValidator(std::move(CCC)), SemaRef(SemaRef), S(S),
MemberContext(MemberContext), Result(SemaRef, TypoName, LookupKind), SS(SS ? llvm::make_unique<CXXScopeSpec>(*SS) : nullptr),
CorrectionValidator(std::move(CCC)), MemberContext(MemberContext),
Result(SemaRef, TypoName, LookupKind),
Namespaces(SemaRef.Context, SemaRef.CurContext, SS), Namespaces(SemaRef.Context, SemaRef.CurContext, SS),
EnteringContext(EnteringContext), SearchNamespaces(false) { EnteringContext(EnteringContext), SearchNamespaces(false) {
Result.suppressDiagnostics(); Result.suppressDiagnostics();
Expand Down Expand Up @@ -167,6 +169,13 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer {
CurrentTCIndex = 0; 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; } ASTContext &getContext() const { return SemaRef.Context; }
const LookupResult &getLookupResult() const { return Result; } const LookupResult &getLookupResult() const { return Result; }


Expand Down Expand Up @@ -246,7 +255,7 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer {


Sema &SemaRef; Sema &SemaRef;
Scope *S; Scope *S;
CXXScopeSpec *SS; std::unique_ptr<CXXScopeSpec> SS;
std::unique_ptr<CorrectionCandidateCallback> CorrectionValidator; std::unique_ptr<CorrectionCandidateCallback> CorrectionValidator;
DeclContext *MemberContext; DeclContext *MemberContext;
LookupResult Result; LookupResult Result;
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Expand Up @@ -11286,6 +11286,7 @@ Sema::PushExpressionEvaluationContext(ExpressionEvaluationContext NewContext,


void Sema::PopExpressionEvaluationContext() { void Sema::PopExpressionEvaluationContext() {
ExpressionEvaluationContextRecord& Rec = ExprEvalContexts.back(); ExpressionEvaluationContextRecord& Rec = ExprEvalContexts.back();
unsigned NumTypos = Rec.NumTypos;


if (!Rec.Lambdas.empty()) { if (!Rec.Lambdas.empty()) {
if (Rec.isUnevaluated() || Rec.Context == ConstantEvaluated) { if (Rec.isUnevaluated() || Rec.Context == ConstantEvaluated) {
Expand Down Expand Up @@ -11333,6 +11334,12 @@ void Sema::PopExpressionEvaluationContext() {


// Pop the current expression evaluation context off the stack. // Pop the current expression evaluation context off the stack.
ExprEvalContexts.pop_back(); 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() { void Sema::DiscardCleanupsInEvaluationContext() {
Expand Down
114 changes: 114 additions & 0 deletions clang/lib/Sema/SemaExprCXX.cpp
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//


#include "clang/Sema/SemaInternal.h" #include "clang/Sema/SemaInternal.h"
#include "TreeTransform.h"
#include "TypeLocBuilder.h" #include "TypeLocBuilder.h"
#include "clang/AST/ASTContext.h" #include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h" #include "clang/AST/ASTLambda.h"
Expand Down Expand Up @@ -5914,6 +5915,103 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
CurrentLSI->clearPotentialCaptures(); 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() {
for (auto TE : TypoExprs) {
auto &State = SemaRef.getTypoExprState(TE);
TransformCache.erase(TE);
if (!State.Consumer->finished())
return true;
State.Consumer->resetCorrectionStream();
}
return false;
}

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.
auto &CacheEntry = TransformCache[E];
if (!TypoExprs.insert(E) && !CacheEntry.isUnset()) {
return CacheEntry;
}

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);
assert(!NE.isUnset() &&
"Typo was transformed into a valid-but-null ExprResult");
if (!NE.isInvalid())
return CacheEntry = NE;
}
return CacheEntry = ExprError();
}
};
}


ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC, ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC,
bool DiscardedValue, bool DiscardedValue,
Expand Down Expand Up @@ -5961,6 +6059,22 @@ ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC,
return ExprError(); 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); CheckCompletedExpr(FullExpr.get(), CC, IsConstexpr);


// At the end of this full expression (which could be a deeply nested // At the end of this full expression (which could be a deeply nested
Expand Down

0 comments on commit 6c75951

Please sign in to comment.