dgoldman created this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

As Typo Resolution can create new TypoExprs while resolving typos,
it is necessary to recurse through the expression to search for more
typos.

This should fix the assertion failure in `clang::Sema::~Sema()`:

  `DelayedTypos.empty() && "Uncorrected typos!"`

Notes:

- For expressions with multiple typos, we only give suggestions if we are able 
to resolve all typos in the expression
- This patch is similar to D37521 <https://reviews.llvm.org/D37521> except that 
it does not eagerly commit to a correction for the first typo in the 
expression. Instead, it will search for corrections which fix all of the typos 
in the expression.


Repository:
  rC Clang

https://reviews.llvm.org/D62648

Files:
  lib/Sema/SemaExprCXX.cpp
  test/Sema/typo-correction-recursive.cpp

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();
+}
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<TypoExpr *, 2> 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<ExprResult(Expr *)> 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);
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to