]> granicus.if.org Git - clang/commitdiff
MSVC compat: Allow lookup of friend types in enclosing namespaces
authorReid Kleckner <reid@kleckner.net>
Thu, 10 Jul 2014 23:44:52 +0000 (23:44 +0000)
committerReid Kleckner <reid@kleckner.net>
Thu, 10 Jul 2014 23:44:52 +0000 (23:44 +0000)
The relevant portion of C++ standard says [namespace.memdef]p3:

  If the name in a friend declaration is neither qualified nor a
  template-id and the declaration is a function or an
  elaborated-type-specifier, the lookup to determine whether the entity
  has been previously declared shall not consider any scopes outside the
  innermost enclosing namespace.

MSVC does not implement that rule for types.  If there is a type in an
enclosing namespace, they consider an unqualified tag declaration with
the same name to be a redeclaration of the type from another namespace.

Implementing compatibility is a simple matter of disabling our
implementation of this rule for types, which was added in r177473.

Reviewers: rsmith

Differential Revision: http://reviews.llvm.org/D4443

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@212784 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Basic/DiagnosticSemaKinds.td
lib/Sema/SemaDecl.cpp
test/SemaCXX/MicrosoftExtensions.cpp
test/SemaCXX/ms-friend-lookup.cpp [new file with mode: 0644]

index 5ab9aef94e417e2e1a0c8b962d3c886ab889c138..e48da1035031de78f926d8277bc9cc34efa6adcc 100644 (file)
@@ -997,7 +997,11 @@ def warn_template_qualified_friend_ignored : Warning<
   "dependent nested name specifier '%0' for friend template declaration is "
   "not supported; ignoring this friend declaration">,
   InGroup<UnsupportedFriend>;
-  
+def ext_friend_tag_redecl_outside_namespace : ExtWarn<
+  "unqualified friend declaration referring to type outside of the nearest "
+  "enclosing namespace is a Microsoft extension; add a nested name specifier">,
+  InGroup<Microsoft>;
+
 def err_invalid_member_in_interface : Error<
   "%select{data member |non-public member function |static member function |"
           "user-declared constructor|user-declared destructor|operator |"
index 13a77f15eb20032a2861fce7af2e1c8f25f9562c..51967627b0c7ce7ebd09c2a21386c37f24bc7169 100644 (file)
@@ -10718,6 +10718,50 @@ bool Sema::isAcceptableTagRedeclaration(const TagDecl *Previous,
   return false;
 }
 
+/// Add a minimal nested name specifier fixit hint to allow lookup of a tag name
+/// from an outer enclosing namespace or file scope inside a friend declaration.
+/// This should provide the commented out code in the following snippet:
+///   namespace N {
+///     struct X;
+///     namespace M {
+///       struct Y { friend struct /*N::*/ X; };
+///     }
+///   }
+static void addFriendTagNNSFixIt(Sema &SemaRef, Sema::SemaDiagnosticBuilder &D,
+                                 NamedDecl *ND, Scope *S,
+                                 SourceLocation NameLoc) {
+  // While the decl is in a namespace, do repeated lookup of that name and see
+  // if we get the same namespace back.  If we do not, continue until
+  // translation unit scope, at which point we have a fully qualified NNS.
+  SmallVector<IdentifierInfo *, 4> Namespaces;
+  DeclContext *DC = ND->getDeclContext()->getRedeclContext();
+  for (; !DC->isTranslationUnit(); DC = DC->getParent()) {
+    // This tag should be declared in a namespace, which can only be enclosed by
+    // other namespaces.  Bail if there's an anonymous namespace in the chain.
+    NamespaceDecl *Namespace = dyn_cast<NamespaceDecl>(DC);
+    if (!Namespace || Namespace->isAnonymousNamespace())
+      return;
+    IdentifierInfo *II = Namespace->getIdentifier();
+    Namespaces.push_back(II);
+    NamedDecl *Lookup = SemaRef.LookupSingleName(
+        S, II, NameLoc, Sema::LookupNestedNameSpecifierName);
+    if (Lookup == Namespace)
+      break;
+  }
+
+  // Once we have all the namespaces, reverse them to go outermost first, and
+  // build an NNS.
+  SmallString<64> Insertion;
+  llvm::raw_svector_ostream OS(Insertion);
+  if (DC->isTranslationUnit())
+    OS << "::";
+  std::reverse(Namespaces.begin(), Namespaces.end());
+  for (auto *II : Namespaces)
+    OS << II->getName() << "::";
+  OS.flush();
+  D << FixItHint::CreateInsertion(NameLoc, Insertion);
+}
+
 /// ActOnTag - This is invoked when we see 'struct foo' or 'struct {'.  In the
 /// former case, Name will be non-null.  In the later case, Name will be null.
 /// TagSpec indicates what kind of tag this is. TUK indicates whether this is a
@@ -10827,7 +10871,6 @@ Decl *Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
     Redecl = NotForRedeclaration;
 
   LookupResult Previous(*this, Name, NameLoc, LookupTagName, Redecl);
-  bool FriendSawTagOutsideEnclosingNamespace = false;
   if (Name && SS.isNotEmpty()) {
     // We have a nested-name tag ('struct foo::bar').
 
@@ -10912,23 +10955,38 @@ Decl *Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
     //   the entity has been previously declared shall not consider
     //   any scopes outside the innermost enclosing namespace.
     //
+    // MSVC doesn't implement the above rule for types, so a friend tag
+    // declaration may be a redeclaration of a type declared in an enclosing
+    // scope.  They do implement this rule for friend functions.
+    //
     // Does it matter that this should be by scope instead of by
     // semantic context?
     if (!Previous.empty() && TUK == TUK_Friend) {
       DeclContext *EnclosingNS = SearchDC->getEnclosingNamespaceContext();
       LookupResult::Filter F = Previous.makeFilter();
+      bool FriendSawTagOutsideEnclosingNamespace = false;
       while (F.hasNext()) {
         NamedDecl *ND = F.next();
         DeclContext *DC = ND->getDeclContext()->getRedeclContext();
         if (DC->isFileContext() &&
             !EnclosingNS->Encloses(ND->getDeclContext())) {
-          F.erase();
-          FriendSawTagOutsideEnclosingNamespace = true;
+          if (getLangOpts().MSVCCompat)
+            FriendSawTagOutsideEnclosingNamespace = true;
+          else
+            F.erase();
         }
       }
       F.done();
+
+      // Diagnose this MSVC extension in the easy case where lookup would have
+      // unambiguously found something outside the enclosing namespace.
+      if (Previous.isSingleResult() && FriendSawTagOutsideEnclosingNamespace) {
+        NamedDecl *ND = Previous.getFoundDecl();
+        auto D = Diag(NameLoc, diag::ext_friend_tag_redecl_outside_namespace);
+        addFriendTagNNSFixIt(*this, D, ND, S, NameLoc);
+      }
     }
-    
+
     // Note:  there used to be some attempt at recovery here.
     if (Previous.isAmbiguous())
       return nullptr;
@@ -11453,8 +11511,7 @@ CreateNewDecl:
   // declaration so we always pass true to setObjectOfFriendDecl to make
   // the tag name visible.
   if (TUK == TUK_Friend)
-    New->setObjectOfFriendDecl(!FriendSawTagOutsideEnclosingNamespace &&
-                               getLangOpts().MicrosoftExt);
+    New->setObjectOfFriendDecl(getLangOpts().MSVCCompat);
 
   // Set the access specifier.
   if (!Invalid && SearchDC->isRecord())
index 3b54c281afe074525876c39419e3118ac4debbd7..6d221a409e7bcf4a0b9e358ed774a64a96510ed9 100644 (file)
@@ -176,29 +176,6 @@ void pointer_to_integral_type_conv(char* ptr) {
    b = reinterpret_cast<bool>(ptr); // expected-error {{cast from pointer to smaller type 'bool' loses information}}
 }
 
-namespace friend_as_a_forward_decl {
-
-class A {
-  class Nested {
-    friend class B;
-    B* b;
-  };
-  B* b;
-};
-B* global_b;
-
-
-void f()
-{
-  class Local {
-    friend class Z;
-    Z* b;
-  };
-  Z* b;
-}
-
-}
-
 struct PR11150 {
   class X {
     virtual void f() = 0;
diff --git a/test/SemaCXX/ms-friend-lookup.cpp b/test/SemaCXX/ms-friend-lookup.cpp
new file mode 100644 (file)
index 0000000..c63160f
--- /dev/null
@@ -0,0 +1,104 @@
+// RUN: %clang_cc1 %s -triple i686-pc-win32 -std=c++11 -Wmicrosoft -fms-compatibility -verify
+// RUN: not %clang_cc1 %s -triple i686-pc-win32 -std=c++11 -Wmicrosoft -fms-compatibility -fdiagnostics-parseable-fixits 2>&1 | FileCheck %s
+
+struct X;
+namespace name_at_tu_scope {
+struct Y {
+  friend struct X; // expected-warning-re {{unqualified friend declaration {{.*}} is a Microsoft extension}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:17-[[@LINE-1]]:17}:"::"
+};
+}
+
+namespace enclosing_friend_decl {
+struct B;
+namespace ns {
+struct A {
+  friend struct B; // expected-warning-re {{unqualified friend declaration {{.*}} is a Microsoft extension}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:17-[[@LINE-1]]:17}:"enclosing_friend_decl::"
+protected:
+  A();
+};
+}
+struct B {
+  static void f() { ns::A x; }
+};
+}
+
+namespace enclosing_friend_qualified {
+struct B;
+namespace ns {
+struct A {
+  friend struct enclosing_friend_qualified::B; // Adding name specifiers fixes it.
+protected:
+  A();
+};
+}
+struct B {
+  static void f() { ns::A x; }
+};
+}
+
+namespace enclosing_friend_no_tag {
+struct B;
+namespace ns {
+struct A {
+  friend B; // Removing the tag decl fixes it.
+protected:
+  A();
+};
+}
+struct B {
+  static void f() { ns::A x; }
+};
+}
+
+namespace enclosing_friend_func {
+void f();
+namespace ns {
+struct A {
+  // Amusingly, in MSVC, this declares ns::f(), and doesn't find the outer f().
+  friend void f();
+protected:
+  A(); // expected-note {{declared protected here}}
+};
+}
+void f() { ns::A x; } // expected-error {{calling a protected constructor of class 'enclosing_friend_func::ns::A'}}
+}
+
+namespace test_nns_fixit_hint {
+namespace name1 {
+namespace name2 {
+struct X;
+struct name2;
+namespace name3 {
+struct Y {
+  friend struct X; // expected-warning-re {{unqualified friend declaration {{.*}} is a Microsoft extension}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:17-[[@LINE-1]]:17}:"name1::name2::"
+};
+}
+}
+}
+}
+
+// A friend declaration injects a forward declaration into the nearest enclosing
+// non-member scope.
+namespace friend_as_a_forward_decl {
+
+class A {
+  class Nested {
+    friend class B;
+    B *b;
+  };
+  B *b;
+};
+B *global_b;
+
+void f() {
+  class Local {
+    friend class Z;
+    Z *b;
+  };
+  Z *b;
+}
+
+}