Index: lib/Sema/SemaExprCXX.cpp =================================================================== --- lib/Sema/SemaExprCXX.cpp +++ lib/Sema/SemaExprCXX.cpp @@ -7680,6 +7680,56 @@ 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()) { + Expr *FixedExpr = Res.get(); + auto SavedTypoExprs = TypoExprs; + llvm::SmallSetVector RecursiveTypoExprs; + TypoExprs = RecursiveTypoExprs; + FindTypoExprs(TypoExprs).TraverseStmt(FixedExpr); + + // Check to see if any new TypoExprs were created. If they were, we need + // to recurse in order to handle them. If we're not able to handle them, + // discard this correction. + if (!TypoExprs.empty()) { + ExprResult RecurResult = RecursiveTransformLoop(FixedExpr); + if (RecurResult.isInvalid()) + Res = ExprError(); + else + Res = RecurResult; + // Add the newly created typos to the TypoExprs list, even if they + // failed to apply. This allows them to be reaped although they won't + // emit any diagnostic. + 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 +7757,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 +7786,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,50 @@ +// 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(); +}