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 declaration referring to type outside of the nearest " + "enclosing namespace is a Microsoft extension; add a nested name specifier">, + 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 @@ -10718,6 +10718,51 @@ 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 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 something there's an anonymous namespace in + // the chain. + NamespaceDecl *Namespace = dyn_cast(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 +10872,6 @@ 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 +10956,39 @@ // 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(); + Sema::SemaDiagnosticBuilder 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 +11513,7 @@ // 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: test/SemaCXX/ms-friend-lookup.cpp =================================================================== --- /dev/null +++ test/SemaCXX/ms-friend-lookup.cpp @@ -0,0 +1,81 @@ +// 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::" +}; +} +} +} +}