]> granicus.if.org Git - clang/commitdiff
Fix some interactions between C++11 and C++14 features and using-declarations:
authorRichard Smith <richard-llvm@metafoo.co.uk>
Sun, 18 Dec 2016 21:39:37 +0000 (21:39 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Sun, 18 Dec 2016 21:39:37 +0000 (21:39 +0000)
 * a dependent non-type using-declaration within a function template can be
   valid, as it can refer to an enumerator, so don't reject it in the template
   definition
 * we can partially substitute into a dependent using-declaration if it appears
   within a (local class in a) generic lambda within a function template, which
   means an UnresolvedUsing*Decl doesn't necessarily instantiate to a UsingDecl.

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

include/clang/AST/ASTContext.h
include/clang/Sema/Sema.h
lib/AST/ASTContext.cpp
lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaOverload.cpp
lib/Sema/SemaTemplateInstantiateDecl.cpp
test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p8-cxx0x.cpp

index a07f41268267bd318fdd25999167d389b5087e95..f2aceab41c77803b59680ed552b6ee7a757bb3d0 100644 (file)
@@ -398,11 +398,11 @@ private:
   llvm::DenseMap<const VarDecl *, TemplateOrSpecializationInfo>
   TemplateOrInstantiation;
 
-  /// \brief Keeps track of the declaration from which a UsingDecl was
+  /// \brief Keeps track of the declaration from which a using declaration was
   /// created during instantiation.
   ///
-  /// The source declaration is always a UsingDecl, an UnresolvedUsingValueDecl,
-  /// or an UnresolvedUsingTypenameDecl.
+  /// The source and target declarations are always a UsingDecl, an
+  /// UnresolvedUsingValueDecl, or an UnresolvedUsingTypenameDecl.
   ///
   /// For example:
   /// \code
@@ -421,7 +421,7 @@ private:
   ///
   /// This mapping will contain an entry that maps from the UsingDecl in
   /// B<int> to the UnresolvedUsingDecl in B<T>.
-  llvm::DenseMap<UsingDecl *, NamedDecl *> InstantiatedFromUsingDecl;
+  llvm::DenseMap<NamedDecl *, NamedDecl *> InstantiatedFromUsingDecl;
 
   llvm::DenseMap<UsingShadowDecl*, UsingShadowDecl*>
     InstantiatedFromUsingShadowDecl;
@@ -849,11 +849,11 @@ public:
   /// \brief If the given using decl \p Inst is an instantiation of a
   /// (possibly unresolved) using decl from a template instantiation,
   /// return it.
-  NamedDecl *getInstantiatedFromUsingDecl(UsingDecl *Inst);
+  NamedDecl *getInstantiatedFromUsingDecl(NamedDecl *Inst);
 
   /// \brief Remember that the using decl \p Inst is an instantiation
   /// of the using decl \p Pattern of a class template.
-  void setInstantiatedFromUsingDecl(UsingDecl *Inst, NamedDecl *Pattern);
+  void setInstantiatedFromUsingDecl(NamedDecl *Inst, NamedDecl *Pattern);
 
   void setInstantiatedFromUsingShadowDecl(UsingShadowDecl *Inst,
                                           UsingShadowDecl *Pattern);
index 2b54d5ee142dcc39b715617015243ee4ddbad0e1..136f848338802ae0fd75d31fba7fdc0e763a388b 100644 (file)
@@ -4317,6 +4317,7 @@ public:
                                    SourceLocation NameLoc,
                                    const LookupResult &Previous);
   bool CheckUsingDeclQualifier(SourceLocation UsingLoc,
+                               bool HasTypename,
                                const CXXScopeSpec &SS,
                                const DeclarationNameInfo &NameInfo,
                                SourceLocation NameLoc);
index 7a98ac4c6e97d9f3ff804308ce9727d232b6d90f..0ad663fdf964eac69ab53828f53521a65276917c 100644 (file)
@@ -1270,9 +1270,8 @@ void ASTContext::setClassScopeSpecializationPattern(FunctionDecl *FD,
 }
 
 NamedDecl *
-ASTContext::getInstantiatedFromUsingDecl(UsingDecl *UUD) {
-  llvm::DenseMap<UsingDecl *, NamedDecl *>::const_iterator Pos
-    = InstantiatedFromUsingDecl.find(UUD);
+ASTContext::getInstantiatedFromUsingDecl(NamedDecl *UUD) {
+  auto Pos = InstantiatedFromUsingDecl.find(UUD);
   if (Pos == InstantiatedFromUsingDecl.end())
     return nullptr;
 
@@ -1280,11 +1279,15 @@ ASTContext::getInstantiatedFromUsingDecl(UsingDecl *UUD) {
 }
 
 void
-ASTContext::setInstantiatedFromUsingDecl(UsingDecl *Inst, NamedDecl *Pattern) {
+ASTContext::setInstantiatedFromUsingDecl(NamedDecl *Inst, NamedDecl *Pattern) {
   assert((isa<UsingDecl>(Pattern) ||
           isa<UnresolvedUsingValueDecl>(Pattern) ||
           isa<UnresolvedUsingTypenameDecl>(Pattern)) && 
          "pattern decl is not a using decl");
+  assert((isa<UsingDecl>(Inst) ||
+          isa<UnresolvedUsingValueDecl>(Inst) ||
+          isa<UnresolvedUsingTypenameDecl>(Inst)) && 
+         "instantiation did not produce a using decl");
   assert(!InstantiatedFromUsingDecl[Inst] && "pattern already exists");
   InstantiatedFromUsingDecl[Inst] = Pattern;
 }
index 8b1c2340616d02ee72b5b1b7d852f0f587d1cf47..4742995926fd1f1866627f4223cdb76760666442 100644 (file)
@@ -9010,8 +9010,23 @@ NamedDecl *Sema::BuildUsingDeclaration(Scope *S, AccessSpecifier AS,
     F.done();
   } else {
     assert(IsInstantiation && "no scope in non-instantiation");
-    assert(CurContext->isRecord() && "scope not record in instantiation");
-    LookupQualifiedName(Previous, CurContext);
+    if (CurContext->isRecord())
+      LookupQualifiedName(Previous, CurContext);
+    else {
+      // No redeclaration check is needed here; in non-member contexts we
+      // diagnosed all possible conflicts with other using-declarations when
+      // building the template:
+      //
+      // For a dependent non-type using declaration, the only valid case is
+      // if we instantiate to a single enumerator. We check for conflicts
+      // between shadow declarations we introduce, and we check in the template
+      // definition for conflicts between a non-type using declaration and any
+      // other declaration, which together covers all cases.
+      //
+      // A dependent typename using declaration will never successfully
+      // instantiate, since it will always name a class member, so we reject
+      // that in the template definition.
+    }
   }
 
   // Check for invalid redeclarations.
@@ -9020,7 +9035,8 @@ NamedDecl *Sema::BuildUsingDeclaration(Scope *S, AccessSpecifier AS,
     return nullptr;
 
   // Check for bad qualifiers.
-  if (CheckUsingDeclQualifier(UsingLoc, SS, NameInfo, IdentLoc))
+  if (CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, SS, NameInfo,
+                              IdentLoc))
     return nullptr;
 
   DeclContext *LookupContext = computeDeclContext(SS);
@@ -9259,7 +9275,19 @@ bool Sema::CheckUsingDeclRedeclaration(SourceLocation UsingLoc,
                  = dyn_cast<UnresolvedUsingTypenameDecl>(D)) {
       DTypename = true;
       DQual = UD->getQualifier();
-    } else continue;
+    } else if (!isa<TypeDecl>(D) && Qual->isDependent() &&
+               !HasTypenameKeyword) {
+      // A dependent qualifier outside a class can only ever resolve to an
+      // enumeration type. Therefore it conflicts with any other non-type
+      // declaration in the same scope.
+      // FIXME: How should we check for dependent type-type conflicts at block
+      // scope?
+      Diag(NameLoc, diag::err_redefinition_different_kind)
+          << Prev.getLookupName();
+      Diag(D->getLocation(), diag::note_previous_definition);
+      return true;
+    }
+    else continue;
 
     // using decls differ if one says 'typename' and the other doesn't.
     // FIXME: non-dependent using decls?
@@ -9285,6 +9313,7 @@ bool Sema::CheckUsingDeclRedeclaration(SourceLocation UsingLoc,
 /// in the current context is appropriately related to the current
 /// scope.  If an error is found, diagnoses it and returns true.
 bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
+                                   bool HasTypename,
                                    const CXXScopeSpec &SS,
                                    const DeclarationNameInfo &NameInfo,
                                    SourceLocation NameLoc) {
@@ -9295,9 +9324,11 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
     // C++0x [namespace.udecl]p8:
     //   A using-declaration for a class member shall be a member-declaration.
 
-    // If we weren't able to compute a valid scope, it must be a
-    // dependent class scope.
-    if (!NamedContext || NamedContext->getRedeclContext()->isRecord()) {
+    // If we weren't able to compute a valid scope, it might validly be a
+    // dependent class scope or a dependent enumeration unscoped scope. If
+    // we have a 'typename' keyword, the scope must resolve to a class type.
+    if ((HasTypename && !NamedContext) ||
+        (NamedContext && NamedContext->getRedeclContext()->isRecord())) {
       auto *RD = NamedContext
                      ? cast<CXXRecordDecl>(NamedContext->getRedeclContext())
                      : nullptr;
@@ -9357,7 +9388,8 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
         if (getLangOpts().CPlusPlus11) {
           // Convert 'using X::Y;' to 'auto &Y = X::Y;'.
           FixIt = FixItHint::CreateReplacement(
-              UsingLoc, "constexpr auto " + NameInfo.getName().getAsString() + " = ");
+              UsingLoc,
+              "constexpr auto " + NameInfo.getName().getAsString() + " = ");
         }
 
         Diag(UsingLoc, diag::note_using_decl_class_member_workaround)
@@ -9367,7 +9399,7 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
       return true;
     }
 
-    // Otherwise, everything is known to be fine.
+    // Otherwise, this might be valid.
     return false;
   }
 
index 1899e41ba966ea51725de24394c7e2d9ddd6681b..d49142b8d8532638cc8bab19bf55acd88331ea35 100644 (file)
@@ -987,10 +987,17 @@ Sema::CheckOverload(Scope *S, FunctionDecl *New, const LookupResult &Old,
       assert(Old.getLookupKind() == LookupUsingDeclName);
     } else if (isa<TagDecl>(OldD)) {
       // We can always overload with tags by hiding them.
-    } else if (isa<UnresolvedUsingValueDecl>(OldD)) {
+    } else if (auto *UUD = dyn_cast<UnresolvedUsingValueDecl>(OldD)) {
       // Optimistically assume that an unresolved using decl will
       // overload; if it doesn't, we'll have to diagnose during
       // template instantiation.
+      //
+      // Exception: if the scope is dependent and this is not a class
+      // member, the using declaration can only introduce an enumerator.
+      if (UUD->getQualifier()->isDependent() && !UUD->isCXXClassMember()) {
+        Match = *I;
+        return Ovl_NonFunction;
+      }
     } else {
       // (C++ 13p1):
       //   Only function declarations can be overloaded; object and type
index 6b6abc7af6f2d9c7bbee21dc3fae151e3e833d51..0956a1cc502c94b3e7e6bb39784f919d88606289 100644 (file)
@@ -2430,8 +2430,8 @@ Decl *TemplateDeclInstantiator::VisitUsingDecl(UsingDecl *D) {
   }
 
   if (!NewUD->isInvalidDecl() &&
-      SemaRef.CheckUsingDeclQualifier(D->getUsingLoc(), SS, NameInfo,
-                                      D->getLocation()))
+      SemaRef.CheckUsingDeclQualifier(D->getUsingLoc(), D->hasTypename(),
+                                      SS, NameInfo, D->getLocation()))
     NewUD->setInvalidDecl();
 
   SemaRef.Context.setInstantiatedFromUsingDecl(NewUD, D);
@@ -2515,7 +2515,7 @@ Decl * TemplateDeclInstantiator
                                   /*instantiation*/ true,
                                   /*typename*/ true, D->getTypenameLoc());
   if (UD)
-    SemaRef.Context.setInstantiatedFromUsingDecl(cast<UsingDecl>(UD), D);
+    SemaRef.Context.setInstantiatedFromUsingDecl(UD, D);
 
   return UD;
 }
@@ -2539,7 +2539,7 @@ Decl * TemplateDeclInstantiator
                                   /*instantiation*/ true,
                                   /*typename*/ false, SourceLocation());
   if (UD)
-    SemaRef.Context.setInstantiatedFromUsingDecl(cast<UsingDecl>(UD), D);
+    SemaRef.Context.setInstantiatedFromUsingDecl(UD, D);
 
   return UD;
 }
@@ -4520,13 +4520,13 @@ static bool isInstantiationOf(UsingDecl *Pattern,
 }
 
 static bool isInstantiationOf(UnresolvedUsingValueDecl *Pattern,
-                              UsingDecl *Instance,
+                              NamedDecl *Instance,
                               ASTContext &C) {
   return declaresSameEntity(C.getInstantiatedFromUsingDecl(Instance), Pattern);
 }
 
 static bool isInstantiationOf(UnresolvedUsingTypenameDecl *Pattern,
-                              UsingDecl *Instance,
+                              NamedDecl *Instance,
                               ASTContext &C) {
   return declaresSameEntity(C.getInstantiatedFromUsingDecl(Instance), Pattern);
 }
@@ -4550,15 +4550,13 @@ static bool isInstantiationOfStaticDataMember(VarDecl *Pattern,
 // D is the prospective pattern
 static bool isInstantiationOf(ASTContext &Ctx, NamedDecl *D, Decl *Other) {
   if (D->getKind() != Other->getKind()) {
-    if (UnresolvedUsingTypenameDecl *UUD
-          = dyn_cast<UnresolvedUsingTypenameDecl>(D)) {
+    if (auto *UUD = dyn_cast<UnresolvedUsingTypenameDecl>(D)) {
       if (UsingDecl *UD = dyn_cast<UsingDecl>(Other)) {
         return isInstantiationOf(UUD, UD, Ctx);
       }
     }
 
-    if (UnresolvedUsingValueDecl *UUD
-          = dyn_cast<UnresolvedUsingValueDecl>(D)) {
+    if (auto *UUD = dyn_cast<UnresolvedUsingValueDecl>(D)) {
       if (UsingDecl *UD = dyn_cast<UsingDecl>(Other)) {
         return isInstantiationOf(UUD, UD, Ctx);
       }
@@ -4567,31 +4565,31 @@ static bool isInstantiationOf(ASTContext &Ctx, NamedDecl *D, Decl *Other) {
     return false;
   }
 
-  if (CXXRecordDecl *Record = dyn_cast<CXXRecordDecl>(Other))
+  if (auto *Record = dyn_cast<CXXRecordDecl>(Other))
     return isInstantiationOf(cast<CXXRecordDecl>(D), Record);
 
-  if (FunctionDecl *Function = dyn_cast<FunctionDecl>(Other))
+  if (auto *Function = dyn_cast<FunctionDecl>(Other))
     return isInstantiationOf(cast<FunctionDecl>(D), Function);
 
-  if (EnumDecl *Enum = dyn_cast<EnumDecl>(Other))
+  if (auto *Enum = dyn_cast<EnumDecl>(Other))
     return isInstantiationOf(cast<EnumDecl>(D), Enum);
 
-  if (VarDecl *Var = dyn_cast<VarDecl>(Other))
+  if (auto *Var = dyn_cast<VarDecl>(Other))
     if (Var->isStaticDataMember())
       return isInstantiationOfStaticDataMember(cast<VarDecl>(D), Var);
 
-  if (ClassTemplateDecl *Temp = dyn_cast<ClassTemplateDecl>(Other))
+  if (auto *Temp = dyn_cast<ClassTemplateDecl>(Other))
     return isInstantiationOf(cast<ClassTemplateDecl>(D), Temp);
 
-  if (FunctionTemplateDecl *Temp = dyn_cast<FunctionTemplateDecl>(Other))
+  if (auto *Temp = dyn_cast<FunctionTemplateDecl>(Other))
     return isInstantiationOf(cast<FunctionTemplateDecl>(D), Temp);
 
-  if (ClassTemplatePartialSpecializationDecl *PartialSpec
-        = dyn_cast<ClassTemplatePartialSpecializationDecl>(Other))
+  if (auto *PartialSpec =
+          dyn_cast<ClassTemplatePartialSpecializationDecl>(Other))
     return isInstantiationOf(cast<ClassTemplatePartialSpecializationDecl>(D),
                              PartialSpec);
 
-  if (FieldDecl *Field = dyn_cast<FieldDecl>(Other)) {
+  if (auto *Field = dyn_cast<FieldDecl>(Other)) {
     if (!Field->getDeclName()) {
       // This is an unnamed field.
       return declaresSameEntity(Ctx.getInstantiatedFromUnnamedFieldDecl(Field),
@@ -4599,14 +4597,20 @@ static bool isInstantiationOf(ASTContext &Ctx, NamedDecl *D, Decl *Other) {
     }
   }
 
-  if (UsingDecl *Using = dyn_cast<UsingDecl>(Other))
+  if (auto *Using = dyn_cast<UsingDecl>(Other))
     return isInstantiationOf(cast<UsingDecl>(D), Using, Ctx);
 
-  if (UsingShadowDecl *Shadow = dyn_cast<UsingShadowDecl>(Other))
+  if (auto *Using = dyn_cast<UnresolvedUsingValueDecl>(Other))
+    return isInstantiationOf(cast<UnresolvedUsingValueDecl>(D), Using, Ctx);
+
+  if (auto *Using = dyn_cast<UnresolvedUsingTypenameDecl>(Other))
+    return isInstantiationOf(cast<UnresolvedUsingTypenameDecl>(D), Using, Ctx);
+
+  if (auto *Shadow = dyn_cast<UsingShadowDecl>(Other))
     return isInstantiationOf(cast<UsingShadowDecl>(D), Shadow, Ctx);
 
-  return D->getDeclName() && isa<NamedDecl>(Other) &&
-    D->getDeclName() == cast<NamedDecl>(Other)->getDeclName();
+  return D->getDeclName() &&
+         D->getDeclName() == cast<NamedDecl>(Other)->getDeclName();
 }
 
 template<typename ForwardIterator>
index 6c63f061ab0f1106e7c007548f3b81ae91c10f38..8f6638e69438ce26791abe1fe05e3955080ac6c8 100644 (file)
@@ -1,5 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -std=c++98 -verify %s
 // RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++14 -verify %s
 // RUN: not %clang_cc1 -fsyntax-only -std=c++98 -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck --check-prefix=CXX98 %s
 // RUN: not %clang_cc1 -fsyntax-only -std=c++11 -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck --check-prefix=CXX11 %s
 // C++0x N2914.
@@ -44,10 +45,159 @@ void f() {
 #endif
 }
 
-template <typename T>
-struct PR21933 : T {
-  static void StaticFun() { using T::member; } // expected-error{{using declaration cannot refer to class member}}
-};
+namespace PR21933 {
+  struct A { int member; };
+  struct B { static int member; };
+  enum C { member };
+
+  template <typename T>
+  struct X {
+    static void StaticFun() {
+      using T::member; // expected-error 2{{class member}} expected-note {{use a reference instead}}
+#if __cplusplus < 201103L
+    // expected-error@-2 {{cannot be used prior to '::'}}
+#endif
+      (void)member;
+    }
+  };
+  template<typename T>
+  struct Y : T { 
+    static void StaticFun() {
+      using T::member; // expected-error 2{{class member}} expected-note {{use a reference instead}}
+      (void)member;
+    }
+  };
+
+  void f() { 
+    X<A>::StaticFun(); // expected-note {{instantiation of}}
+    X<B>::StaticFun(); // expected-note {{instantiation of}}
+    X<C>::StaticFun();
+#if __cplusplus < 201103L
+    // expected-note@-2 {{instantiation of}}
+#endif
+    Y<A>::StaticFun(); // expected-note {{instantiation of}}
+    Y<B>::StaticFun(); // expected-note {{instantiation of}}
+  }
+
+  template<typename T, typename U> void value_vs_value() {
+    using T::a; // expected-note {{previous}}
+#if __cplusplus < 201103L
+    // expected-error@-2 {{cannot be used prior to '::'}}
+#endif
+    extern int a(); // expected-error {{different kind of symbol}}
+    a();
+
+    extern int b();
+    using T::b;
+    b();
+
+    using T::c;
+    using U::c;
+    c();
+  }
+
+  template<typename T, typename U> void value_vs_type() {
+    using T::Xt; // expected-note {{previous}}
+    typedef struct {} Xt; // expected-error {{different kind of symbol}}
+    (void)Xt;
+
+    using T::Xs; // expected-note {{candidate}}
+    struct Xs {}; // expected-note {{candidate}}
+    // FIXME: This is wrong, the using declaration hides the type.
+    Xs xs; // expected-error {{ambiguous}}
+
+    using T::Xe; // expected-note {{candidate}}
+    enum Xe {}; // expected-note {{candidate}}
+    // FIXME: This is wrong, the using declaration hides the type.
+    Xe xe; // expected-error {{ambiguous}}
+
+    typedef struct {} Yt; // expected-note {{candidate}}
+    using T::Yt; // eypected-error {{different kind of symbol}} expected-note {{candidate}}
+    Yt yt; // expected-error {{ambiguous}}
+
+    struct Ys {}; // expected-note {{candidate}}
+    using T::Ys; // expected-note {{candidate}}
+    // FIXME: This is wrong, the using declaration hides the type.
+    Ys ys; // expected-error {{ambiguous}}
+
+    enum Ye {}; // expected-note {{candidate}}
+    using T::Ye; // expected-note {{candidate}}
+    // FIXME: This is wrong, the using declaration hides the type.
+    Ye ye; // expected-error {{ambiguous}}
+  }
+
+  template<typename T> void type() {
+    // Must be a class member because T:: can only name a class or enum,
+    // and an enum cannot have a type member.
+    using typename T::X; // expected-error {{cannot refer to class member}}
+  }
+
+  namespace N1 { enum E { a, b, c }; }
+  namespace N2 { enum E { a, b, c }; }
+  void g() { value_vs_value<N1::E, N2::E>(); }
+#if __cplusplus < 201103L
+    // expected-note@-2 {{in instantiation of}}
+#endif
+
+#if __cplusplus >= 201402L
+  namespace partial_substitute {
+    template<typename T> auto f() {
+      return [](auto x) {
+        using A = typename T::template U<decltype(x)>;
+        using A::E::e;
+        struct S : A {
+          using A::f;
+          using typename A::type;
+          type f(int) { return e; }
+        };
+        return S();
+      };
+    }
+    enum Enum { e };
+    struct X {
+      template<typename T> struct U {
+        int f(int, int);
+        using type = int;
+        using E = Enum;
+      };
+    };
+    int test() {
+      auto s = f<X>()(0);
+      return s.f(0) + s.f(0, 0);
+    }
+
+    template<typename T, typename U> auto g() {
+      return [](auto x) {
+        using X = decltype(x);
+        struct S : T::template Q<X>, U::template Q<X> {
+          using T::template Q<X>::f;
+          using U::template Q<X>::f;
+          void h() { f(); }
+          void h(int n) { f(n); }
+        };
+        return S();
+      };
+    }
+    struct A { template<typename> struct Q { int f(); }; };
+    struct B { template<typename> struct Q { int f(int); }; };
+    int test2() {
+      auto s = g<A, B>()(0);
+      s.f();
+      s.f(0);
+      s.h();
+      s.h(0);
+    }
+  }
+#endif
+
+  template<typename T, typename U> struct RepeatedMember : T, U {
+    // FIXME: This is the wrong error: we should complain that a member type
+    // cannot be redeclared at class scope.
+    using typename T::type; // expected-note {{candidate}}
+    using typename U::type; // expected-note {{candidate}}
+    type x; // expected-error {{ambiguous}}
+  };
+}
 
 struct S {
   static int n;