Skip to content
Browse files

Start adding the infrastructure for handling TypoExprs.

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

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
Kaelyn Takata committed Oct 27, 2014
1 parent 0d6a3ed commit 6c759519bb374cbe9866d602665f4048b597a7e6
@@ -168,6 +168,7 @@ namespace clang {
class TypedefDecl;
class TypedefNameDecl;
class TypeLoc;
class TypoCorrectionConsumer;
class UnqualifiedId;
class UnresolvedLookupExpr;
class UnresolvedMemberExpr;
@@ -768,6 +769,10 @@ class Sema {
/// 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
@@ -801,6 +806,7 @@ class Sema {
bool IsDecltype)
: Context(Context), ParentNeedsCleanups(ParentNeedsCleanups),
IsDecltype(IsDecltype), NumCleanupObjects(NumCleanupObjects),
ManglingContextDecl(ManglingContextDecl), MangleNumbering() { }

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

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

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.
// The boolean value will be true to indicate that the namespace was loaded
@@ -2598,7 +2618,24 @@ class Sema {
/// 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.
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);

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
/// null if the results were absent, ambiguous, or overloaded.
@@ -2676,6 +2713,16 @@ class Sema {
const ObjCObjectPointerType *OPT = nullptr,
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,
const PartialDiagnostic &TypoDiag,
bool ErrorRecovery = true);
@@ -101,8 +101,10 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer {
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) {
@@ -167,6 +169,13 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer {
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; }

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

Sema &SemaRef;
Scope *S;
CXXScopeSpec *SS;
std::unique_ptr<CXXScopeSpec> SS;
std::unique_ptr<CorrectionCandidateCallback> CorrectionValidator;
DeclContext *MemberContext;
LookupResult Result;
@@ -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) {
@@ -11333,6 +11334,12 @@ void Sema::PopExpressionEvaluationContext() {

// Pop the current expression evaluation context off the stack.

if (!ExprEvalContexts.empty())
ExprEvalContexts.back().NumTypos += NumTypos;
assert(NumTypos == 0 && "There are outstanding typos after popping the "
"last ExpressionEvaluationContextRecord");

void Sema::DiscardCleanupsInEvaluationContext() {
@@ -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,103 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(

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)

/// \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);
if (!State.Consumer->finished())
return true;
return false;

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()) ||


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,
if (!TC.isKeyword())
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,
bool DiscardedValue,
@@ -5961,6 +6059,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

0 comments on commit 6c75951

Please sign in to comment.
You can’t perform that action at this time.