r212784 - MSVC compat: Allow lookup of friend types in enclosing namespaces

Reid Kleckner reid at kleckner.net
Thu Jul 10 16:44:52 PDT 2014


Author: rnk
Date: Thu Jul 10 18:44:52 2014
New Revision: 212784

URL: http://llvm.org/viewvc/llvm-project?rev=212784&view=rev
Log:
MSVC compat: Allow lookup of friend types in enclosing namespaces

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

Added:
    cfe/trunk/test/SemaCXX/ms-friend-lookup.cpp
Modified:
    cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
    cfe/trunk/lib/Sema/SemaDecl.cpp
    cfe/trunk/test/SemaCXX/MicrosoftExtensions.cpp

Modified: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td?rev=212784&r1=212783&r2=212784&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td Thu Jul 10 18:44:52 2014
@@ -997,7 +997,11 @@ def warn_template_qualified_friend_ignor
   "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 |"

Modified: cfe/trunk/lib/Sema/SemaDecl.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaDecl.cpp?rev=212784&r1=212783&r2=212784&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaDecl.cpp (original)
+++ cfe/trunk/lib/Sema/SemaDecl.cpp Thu Jul 10 18:44:52 2014
@@ -10718,6 +10718,50 @@ bool Sema::isAcceptableTagRedeclaration(
   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
     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
     //   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())

Modified: cfe/trunk/test/SemaCXX/MicrosoftExtensions.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/MicrosoftExtensions.cpp?rev=212784&r1=212783&r2=212784&view=diff
==============================================================================
--- cfe/trunk/test/SemaCXX/MicrosoftExtensions.cpp (original)
+++ cfe/trunk/test/SemaCXX/MicrosoftExtensions.cpp Thu Jul 10 18:44:52 2014
@@ -176,29 +176,6 @@ void pointer_to_integral_type_conv(char*
    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;

Added: cfe/trunk/test/SemaCXX/ms-friend-lookup.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/ms-friend-lookup.cpp?rev=212784&view=auto
==============================================================================
--- cfe/trunk/test/SemaCXX/ms-friend-lookup.cpp (added)
+++ cfe/trunk/test/SemaCXX/ms-friend-lookup.cpp Thu Jul 10 18:44:52 2014
@@ -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;
+}
+
+}





More information about the cfe-commits mailing list