Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -997,7 +997,11 @@ "dependent nested name specifier '%0' for friend template declaration is " "not supported; ignoring this friend declaration">, InGroup; - +def ext_friend_tag_redecl_outside_namespace : ExtWarn< + "unqualified friend declarations referring to types outside of the nearest " + "enclosing namespace is a Microsoft extension; add a nested name specifier to fix">, + InGroup; + 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: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -10910,6 +10910,10 @@ // 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) { @@ -10920,13 +10924,22 @@ DeclContext *DC = ND->getDeclContext()->getRedeclContext(); if (DC->isFileContext() && !EnclosingNS->Encloses(ND->getDeclContext())) { - F.erase(); + if (!getLangOpts().MSVCCompat) + F.erase(); FriendSawTagOutsideEnclosingNamespace = true; } } F.done(); + + if (FriendSawTagOutsideEnclosingNamespace && getLangOpts().MSVCCompat) { + Diag(KWLoc, diag::ext_friend_tag_redecl_outside_namespace); + // Set this back to false so that we don't inject a forward declaration + // into the enclosing namespace. We already found a forward declaration + // in another namespace. + FriendSawTagOutsideEnclosingNamespace = false; + } } - + // Note: there used to be some attempt at recovery here. if (Previous.isAmbiguous()) return nullptr; Index: test/SemaCXX/MicrosoftCompatibility.cpp =================================================================== --- test/SemaCXX/MicrosoftCompatibility.cpp +++ test/SemaCXX/MicrosoftCompatibility.cpp @@ -225,3 +225,58 @@ template int *get_n() { return N; } // expected-warning {{expression which evaluates to zero treated as a null pointer constant}} int *g_nullptr = get_n<0>(); // expected-note {{in instantiation of function template specialization}} } + +namespace enclosing_friend_decl { +struct B; +namespace ns { +struct A { + friend struct B; // expected-warning-re {{unqualified friend declarations {{.*}} is a Microsoft extension}} +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'}} +}