Fix typo correction of one qualified name to another.

When suggesting "foo::bar" as a correction for "fob::bar" we mistakenly
replaced only "bar" with "foo::bar" producing "fob::foo::bar" which was broken.

This corrects that replacement in as many places as I could find & provides
test cases for all those cases I could find a test case for. There are a couple
that don't seem to be reachable (one looks entirely dead, the other just
doesn't seem to ever get called with a namespace to namespace change).

Review by Richard Smith ( http://llvm-reviews.chandlerc.com/D57 ).

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@165817 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang/Sema/TypoCorrection.h b/include/clang/Sema/TypoCorrection.h
index a8f6e11..2b4a9e6 100644
--- a/include/clang/Sema/TypoCorrection.h
+++ b/include/clang/Sema/TypoCorrection.h
@@ -170,6 +170,17 @@
     return CorrectionDecls.size() > 1;
   }
 
+  void setCorrectionRange(CXXScopeSpec* SS,
+                          const DeclarationNameInfo &TypoName) {
+    CorrectionRange.setBegin(CorrectionNameSpec && SS ? SS->getBeginLoc()
+                                                      : TypoName.getLoc());
+    CorrectionRange.setEnd(TypoName.getLoc());
+  }
+
+  SourceRange getCorrectionRange() const {
+    return CorrectionRange;
+  }
+
   typedef llvm::SmallVector<NamedDecl*, 1>::iterator decl_iterator;
   decl_iterator begin() {
     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
@@ -193,6 +204,7 @@
   unsigned CharDistance;
   unsigned QualifierDistance;
   unsigned CallbackDistance;
+  SourceRange CorrectionRange;
 };
 
 /// @brief Base class for callback objects used by Sema::CorrectTypo to check
diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp
index 0de9dd5..15bfd1c 100644
--- a/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/lib/Sema/SemaCXXScopeSpec.cpp
@@ -520,7 +520,8 @@
       if (LookupCtx)
         Diag(Found.getNameLoc(), diag::err_no_member_suggest)
           << Name << LookupCtx << CorrectedQuotedStr << SS.getRange()
-          << FixItHint::CreateReplacement(Found.getNameLoc(), CorrectedStr);
+          << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                          CorrectedStr);
       else
         Diag(Found.getNameLoc(), diag::err_undeclared_var_use_suggest)
           << Name << CorrectedQuotedStr
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index aec5f01..ee246b8 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -435,7 +435,8 @@
       else if (DeclContext *DC = computeDeclContext(*SS, false))
         Diag(IILoc, diag::err_unknown_nested_typename_suggest)
           << II << DC << CorrectedQuotedStr << SS->getRange()
-          << FixItHint::CreateReplacement(SourceRange(IILoc), CorrectedStr);
+          << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                          CorrectedStr);
       else
         llvm_unreachable("could not have corrected a typo here");
 
@@ -684,11 +685,12 @@
           Diag(NameLoc, UnqualifiedDiag)
             << Name << CorrectedQuotedStr
             << FixItHint::CreateReplacement(NameLoc, CorrectedStr);
-        else
+        else // FIXME: is this even reachable? Test it.
           Diag(NameLoc, QualifiedDiag)
             << Name << computeDeclContext(SS, false) << CorrectedQuotedStr
             << SS.getRange()
-            << FixItHint::CreateReplacement(NameLoc, CorrectedStr);
+            << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                            CorrectedStr);
 
         // Update the name, so that the caller has the new name.
         Name = Corrected.getCorrectionAsIdentifierInfo();
@@ -4894,6 +4896,10 @@
   }
 
   if (Correction) {
+    // FIXME: use Correction.getCorrectionRange() instead of computing the range
+    // here. This requires passing in the CXXScopeSpec to CorrectTypo which in
+    // turn causes the correction to fully qualify the name. If we fix
+    // CorrectTypo to minimally qualify then this change should be good.
     SourceRange FixItLoc(NewFD->getLocation());
     CXXScopeSpec &SS = ExtraArgs.D.getCXXScopeSpec();
     if (Correction.getCorrectionSpecifier() && SS.isValid())
diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp
index 12452b2..8d891a3 100644
--- a/lib/Sema/SemaDeclCXX.cpp
+++ b/lib/Sema/SemaDeclCXX.cpp
@@ -5811,7 +5811,8 @@
     if (DeclContext *DC = S.computeDeclContext(SS, false))
       S.Diag(IdentLoc, diag::err_using_directive_member_suggest)
         << Ident << DC << CorrectedQuotedStr << SS.getRange()
-        << FixItHint::CreateReplacement(IdentLoc, CorrectedStr);
+        << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                        CorrectedStr);
     else
       S.Diag(IdentLoc, diag::err_using_directive_suggest)
         << Ident << CorrectedQuotedStr
diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp
index 74ee870..3d66baa 100644
--- a/lib/Sema/SemaExpr.cpp
+++ b/lib/Sema/SemaExpr.cpp
@@ -1640,7 +1640,8 @@
           Diag(R.getNameLoc(), diag::err_no_member_suggest)
             << Name << computeDeclContext(SS, false) << CorrectedQuotedStr
             << SS.getRange()
-            << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr);
+            << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                            CorrectedStr);
         if (ND)
           Diag(ND->getLocation(), diag::note_previous_decl)
             << CorrectedQuotedStr;
diff --git a/lib/Sema/SemaExprMember.cpp b/lib/Sema/SemaExprMember.cpp
index a44fbf2..2b202ed 100644
--- a/lib/Sema/SemaExprMember.cpp
+++ b/lib/Sema/SemaExprMember.cpp
@@ -606,7 +606,8 @@
     R.addDecl(ND);
     SemaRef.Diag(R.getNameLoc(), diag::err_no_member_suggest)
       << Name << DC << CorrectedQuotedStr << SS.getRange()
-      << FixItHint::CreateReplacement(R.getNameLoc(), CorrectedStr);
+      << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                      CorrectedStr);
     SemaRef.Diag(ND->getLocation(), diag::note_previous_decl)
       << ND->getDeclName();
   }
diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp
index d5efd75..e4dab05 100644
--- a/lib/Sema/SemaLookup.cpp
+++ b/lib/Sema/SemaLookup.cpp
@@ -4056,7 +4056,9 @@
     if (IsUnqualifiedLookup)
       UnqualifiedTyposCorrected[Typo] = Result;
 
-    return Result;
+    TypoCorrection TC = Result;
+    TC.setCorrectionRange(SS, TypoName);
+    return TC;
   }
   else if (BestResults.size() > 1
            // Ugly hack equivalent to CTC == CTC_ObjCMessageReceiver;
@@ -4076,7 +4078,9 @@
     if (IsUnqualifiedLookup)
       UnqualifiedTyposCorrected[Typo] = BestResults["super"].front();
 
-    return BestResults["super"].front();
+    TypoCorrection TC = BestResults["super"].front();
+    TC.setCorrectionRange(SS, TypoName);
+    return TC;
   }
 
   // If this was an unqualified lookup and we believe the callback object did
diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp
index 0efe710..f56b054 100644
--- a/lib/Sema/SemaTemplate.cpp
+++ b/lib/Sema/SemaTemplate.cpp
@@ -333,7 +333,8 @@
         if (LookupCtx)
           Diag(Found.getNameLoc(), diag::err_no_member_template_suggest)
             << Name << LookupCtx << CorrectedQuotedStr << SS.getRange()
-            << FixItHint::CreateReplacement(Found.getNameLoc(), CorrectedStr);
+            << FixItHint::CreateReplacement(Corrected.getCorrectionRange(),
+                                            CorrectedStr);
         else
           Diag(Found.getNameLoc(), diag::err_no_template_suggest)
             << Name << CorrectedQuotedStr
diff --git a/test/FixIt/typo.cpp b/test/FixIt/typo.cpp
index 3d40da8..b3568a5 100644
--- a/test/FixIt/typo.cpp
+++ b/test/FixIt/typo.cpp
@@ -5,7 +5,8 @@
 // RUN: grep test_string %t
 
 namespace std {
-  template<typename T> class basic_string { // expected-note 2{{'basic_string' declared here}}
+  template<typename T> class basic_string { // expected-note 2{{'basic_string' declared here}} \
+                                            // expected-note {{'otherstd::basic_string' declared here}}
   public:
     int find(const char *substr); // expected-note{{'find' declared here}}
     static const int npos = -1; // expected-note{{'npos' declared here}}
@@ -76,13 +77,50 @@
 }
 
 namespace nonstd {
-  typedef std::basic_string<char> yarn; // expected-note{{'nonstd::yarn' declared here}}
+  typedef std::basic_string<char> yarn; // expected-note 2 {{'nonstd::yarn' declared here}}
+  int narf; // expected-note{{'nonstd::narf' declared here}}
 }
 
 yarn str4; // expected-error{{unknown type name 'yarn'; did you mean 'nonstd::yarn'?}}
+wibble::yarn str5; // expected-error{{no type named 'yarn' in namespace 'otherstd'; did you mean 'nonstd::yarn'?}}
+
+int poit() {
+  nonstd::basic_string<char> str; // expected-error{{no template named 'basic_string' in namespace 'nonstd'; did you mean 'otherstd::basic_string'?}}
+  return wibble::narf; // expected-error{{no member named 'narf' in namespace 'otherstd'; did you mean 'nonstd::narf'?}}
+}
 
 namespace check_bool {
   void f() {
     Bool b; // expected-error{{use of undeclared identifier 'Bool'; did you mean 'bool'?}}
   }
 }
+
+namespace outr {
+}
+namespace outer {
+  namespace inner { // expected-note{{'outer::inner' declared here}} \
+                    // expected-note{{namespace 'outer::inner' defined here}} \
+                    // expected-note{{'inner' declared here}}
+    int i;
+  }
+}
+
+using namespace outr::inner; // expected-error{{no namespace named 'inner' in namespace 'outr'; did you mean 'outer::inner'?}}
+
+void func() {
+  outr::inner::i = 3; // expected-error{{no member named 'inner' in namespace 'outr'; did you mean 'outer::inner'?}}
+  outer::innr::i = 4; // expected-error{{no member named 'innr' in namespace 'outer'; did you mean 'inner'?}}
+}
+
+struct base {
+};
+struct derived : base {
+  int i;
+};
+
+void func2() {
+  derived d;
+  // FIXME: we should offer a fix here. We do if the 'i' is misspelled, but we don't do name qualification changes
+  //        to replace base::i with derived::i as we would for other qualified name misspellings.
+  // d.base::i = 3;
+}