summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang/include/clang/Basic/DiagnosticSemaKinds.td6
-rw-r--r--clang/lib/Sema/SemaDecl.cpp69
-rw-r--r--clang/test/SemaCXX/MicrosoftExtensions.cpp23
-rw-r--r--clang/test/SemaCXX/ms-friend-lookup.cpp104
4 files changed, 172 insertions, 30 deletions
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5ab9aef94e4..e48da103503 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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 |"
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 13a77f15eb2..51967627b0c 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -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())
diff --git a/clang/test/SemaCXX/MicrosoftExtensions.cpp b/clang/test/SemaCXX/MicrosoftExtensions.cpp
index 3b54c281afe..6d221a409e7 100644
--- a/clang/test/SemaCXX/MicrosoftExtensions.cpp
+++ b/clang/test/SemaCXX/MicrosoftExtensions.cpp
@@ -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/clang/test/SemaCXX/ms-friend-lookup.cpp b/clang/test/SemaCXX/ms-friend-lookup.cpp
new file mode 100644
index 00000000000..c63160f5b9f
--- /dev/null
+++ b/clang/test/SemaCXX/ms-friend-lookup.cpp
@@ -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;
+}
+
+}
OpenPOWER on IntegriCloud