Index: lib/Sema/SemaExprCXX.cpp =================================================================== --- lib/Sema/SemaExprCXX.cpp +++ lib/Sema/SemaExprCXX.cpp @@ -7680,6 +7680,60 @@ return ExprFilter(Res.get()); } + // Try to transform the given expression, looping through the correction + // candidates with `CheckAndAdvanceTypoExprCorrectionStreams`. + // + // Since correcting typos may intoduce new TypoExprs, this function + // checks for new TypoExprs and recurses if it finds any. Note that it will + // only succeed if it is able to correct all typos in the given expression. + ExprResult RecursiveTransformLoop(Expr *E) { + ExprResult Res; + while (true) { + Res = TryTransform(E); + + // The transform was valid: check if there any new TypoExprs were created. + // If so, we need to recurse to check their validity. + if (!Res.isInvalid() && !TypoExprs.empty()) { + Expr *FixedExpr = Res.get(); + auto SavedTypoExprs = TypoExprs; + llvm::SmallSetVector RecursiveTypoExprs; + TypoExprs = RecursiveTypoExprs; + FindTypoExprs(TypoExprs).TraverseStmt(FixedExpr); + + // Recurse to handle newly created TypoExprs. If we're not able to + // handle them, discard these TypoExprs. + ExprResult RecurResult = RecursiveTransformLoop(FixedExpr); + if (RecurResult.isInvalid()) { + Res = ExprError(); + // Recursive corrections didn't work, wipe them away and don't add + // them to the TypoExprs set. + for (auto TE : TypoExprs) { + auto &State = SemaRef.getTypoExprState(TE); + TransformCache.erase(TE); + SemaRef.clearDelayedTypo(TE); + } + } else { + // TypoExpr is valid: add newly created TypoExprs since we were + // able to correct them. + Res = RecurResult; + SavedTypoExprs.set_union(TypoExprs); + } + + TypoExprs = SavedTypoExprs; + } + // If the transform is still valid after checking for any new typos, + // it's good to go. + if (!Res.isInvalid()) + break; + + // The transform was invalid, see if we have any TypoExprs with untried + // correction candidates. + if (!CheckAndAdvanceTypoExprCorrectionStreams()) + break; + } + return Res; + } + public: TransformTypos(Sema &SemaRef, VarDecl *InitDecl, llvm::function_ref Filter) : BaseTransform(SemaRef), InitDecl(InitDecl), ExprFilter(Filter) {} @@ -7707,16 +7761,7 @@ ExprResult TransformBlockExpr(BlockExpr *E) { return Owned(E); } ExprResult Transform(Expr *E) { - ExprResult Res; - while (true) { - Res = TryTransform(E); - - // Exit if either the transform was valid or if there were no TypoExprs - // to transform that still have any untried correction candidates.. - if (!Res.isInvalid() || - !CheckAndAdvanceTypoExprCorrectionStreams()) - break; - } + ExprResult Res = RecursiveTransformLoop(E); // Ensure none of the TypoExprs have multiple typo correction candidates // with the same edit length that pass all the checks and filters. @@ -7745,7 +7790,8 @@ } SemaRef.DisableTypoCorrection = false; - // Ensure that all of the TypoExprs within the current Expr have been found. + // Ensure that all of the TypoExprs within the current invalid Expr have + // been found. if (!Res.isUsable()) FindTypoExprs(TypoExprs).TraverseStmt(E); Index: test/Sema/typo-correction-recursive.cpp =================================================================== --- /dev/null +++ test/Sema/typo-correction-recursive.cpp @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// Check the following typo correction behavior: +// - multiple typos in a single member call chain are all diagnosed +// - no typos are diagnosed for multiple typos in an expression when not all +// typos can be corrected + +class DeepClass +{ +public: + void trigger() const; // expected-note {{'trigger' declared here}} +}; + +class Y +{ +public: + const DeepClass& getX() const { return m_deepInstance; } // expected-note {{'getX' declared here}} + int getN() const { return m_n; } +private: + DeepClass m_deepInstance; + int m_n; +}; + +class Z +{ +public: + const Y& getY0() const { return m_y0; } // expected-note {{'getY0' declared here}} + const Y& getY1() const { return m_y1; } + const Y& getActiveY() const { return m_y0; } + +private: + Y m_y0; + Y m_y1; +}; + +Z z_obj; + +void testMultipleCorrections() +{ + z_obj.getY2(). // expected-error {{no member named 'getY2' in 'Z'; did you mean 'getY0'}} + getM(). // expected-error {{no member named 'getM' in 'Y'; did you mean 'getX'}} + triggee(); // expected-error {{no member named 'triggee' in 'DeepClass'; did you mean 'trigger'}} +} + +void testNoCorrections() +{ + z_obj.getY2(). // expected-error {{no member named 'getY2' in 'Z'}} + getM(). + thisDoesntSeemToMakeSense(); +} + +struct C {}; +struct D { int value; }; +struct A { + C get_me_a_C(); +}; +struct B { + D get_me_a_D(); +}; +A make_an_A(); +B make_an_B(); +int testDiscardedCorrections() { + return make_an_E(). // expected-error {{use of undeclared identifier 'make_an_E'}} + get_me_a_Z().value; +}