]> granicus.if.org Git - clang/commitdiff
Finished semantic analysis of anonymous unions in C++.
authorDouglas Gregor <dgregor@apple.com>
Wed, 7 Jan 2009 19:46:03 +0000 (19:46 +0000)
committerDouglas Gregor <dgregor@apple.com>
Wed, 7 Jan 2009 19:46:03 +0000 (19:46 +0000)
Duplicate-member checking within classes is still a little messy, and
anonymous unions are still completely broken in C. We'll need to unify
the handling of fields in C and C++ to make this code applicable in
both languages.

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

include/clang/AST/Decl.h
include/clang/AST/DeclBase.h
include/clang/AST/DeclCXX.h
include/clang/Basic/DiagnosticKinds.def
lib/AST/Decl.cpp
lib/AST/DeclCXX.cpp
lib/Sema/SemaDecl.cpp
lib/Sema/SemaDeclCXX.cpp
test/SemaCXX/anonymous-union.cpp
www/cxx_status.html

index ceea987ce0f68afc5ba48cf30c50655c6f067f15..460e3de2282b70e23a7b45ea18bd755baf848ce0 100644 (file)
@@ -160,6 +160,9 @@ public:
                          const_cast<const ScopedDecl*>(this)->getDeclContext());
   }
 
+  void setAccess(AccessSpecifier AS) { Access = AS; }
+  AccessSpecifier getAccess() const { return AccessSpecifier(Access); }
+
   /// getLexicalDeclContext - The declaration context where this ScopedDecl was
   /// lexically declared (LexicalDC). May be different from
   /// getDeclContext() (SemanticDC).
@@ -360,7 +363,7 @@ public:
   StorageClass getStorageClass() const { return (StorageClass)SClass; }
 
   SourceLocation getTypeSpecStartLoc() const { return TypeSpecStartLoc; }
-  
+
   const Expr *getInit() const { return (const Expr*) Init; }
   Expr *getInit() { return (Expr*) Init; }
   void setInit(Expr *I) { Init = (Stmt*) I; }
@@ -624,7 +627,6 @@ private:
   // NOTE: VC++ treats enums as signed, avoid using the StorageClass enum
   unsigned SClass : 2;
   bool IsInline : 1;
-  bool IsImplicit : 1;
 
   // Move to DeclGroup when it is implemented.
   SourceLocation TypeSpecStartLoc;
@@ -636,7 +638,7 @@ protected:
     : ValueDecl(DK, DC, L, N, T, PrevDecl), 
       DeclContext(DK),
       ParamInfo(0), Body(0), PreviousDeclaration(0),
-      SClass(S), IsInline(isInline), IsImplicit(0), TypeSpecStartLoc(TSSL) {}
+      SClass(S), IsInline(isInline), TypeSpecStartLoc(TSSL) {}
 
   virtual ~FunctionDecl();
   virtual void Destroy(ASTContext& C);
@@ -670,9 +672,6 @@ public:
 
   void setBody(Stmt *B) { Body = B; }
   
-  bool isImplicit() { return IsImplicit; }
-  void setImplicit() { IsImplicit = true; }
-
   /// getPreviousDeclaration - Return the previous declaration of this
   /// function.
   const FunctionDecl *getPreviousDeclaration() const {
@@ -776,6 +775,12 @@ public:
   /// isBitfield - Determines whether this field is a bitfield.
   bool isBitField() const { return BitWidth != NULL; }
 
+  /// isAnonymousStructOrUnion - Determines whether this field is a
+  /// representative for an anonymous struct or union. Such fields are
+  /// unnamed and are implicitly generated by the implementation to
+  /// store the data for the anonymous union or struct.
+  bool isAnonymousStructOrUnion() const;
+
   Expr *getBitWidth() const { return BitWidth; }
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) {
@@ -852,10 +857,8 @@ protected:
   TypeDecl(Kind DK, DeclContext *DC, SourceLocation L,
            IdentifierInfo *Id, ScopedDecl *PrevDecl)
     : ScopedDecl(DK, DC, L, Id, PrevDecl), TypeForDecl(0) {}
-public:
-  void setAccess(AccessSpecifier AS) { Access = AS; }
-  AccessSpecifier getAccess() const { return AccessSpecifier(Access); }
 
+public:
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) {
     return D->getKind() >= TypeFirst && D->getKind() <= TypeLast;
index cc60d624f69cf1f0a981b75accb1cee182ea49a6..482bf12b469fc040393a684ab08e17e4c3e9e137 100644 (file)
@@ -141,6 +141,10 @@ private:
   /// HasAttrs - This indicates whether the decl has attributes or not.
   unsigned int HasAttrs : 1;
 
+  /// Implicit - Whether this declaration was implicitly generated by
+  /// the implementation rather than explicitly written by the user.
+  bool Implicit : 1;
+
  protected:
   /// Access - Used by C++ decls for the access specifier.
   // NOTE: VC++ treats enums as signed, avoid using the AccessSpecifier enum
@@ -178,6 +182,12 @@ public:
   /// allows for graceful error recovery.
   void setInvalidDecl() { InvalidDecl = 1; }
   bool isInvalidDecl() const { return (bool) InvalidDecl; }
+
+  /// isImplicit - Indicates whether the declaration was implicitly
+  /// generated by the implementation. If false, this declaration
+  /// was written explicitly in the source code.
+  bool isImplicit() const { return Implicit; }
+  void setImplicit(bool I = true) { Implicit = I; }
   
   IdentifierNamespace getIdentifierNamespace() const {
     switch (DeclKind) {
index 583f57854a2da009bc7f1e73ca1b383ee038c2de..567751d7107731fbd16e85fa191f782b987e9808 100644 (file)
@@ -463,9 +463,6 @@ public:
     return getLexicalDeclContext() != getDeclContext();
   }
 
-  void setAccess(AccessSpecifier AS) { Access = AS; }
-  AccessSpecifier getAccess() const { return AccessSpecifier(Access); }
-
   /// getParent - Returns the parent of this method declaration, which
   /// is the class in which this method is defined.
   const CXXRecordDecl *getParent() const { 
@@ -617,16 +614,12 @@ class CXXConstructorDecl : public CXXMethodDecl {
   /// Explicit - Whether this constructor is explicit.
   bool Explicit : 1;
 
-  /// ImplicitlyDeclared - Whether this constructor was implicitly
-  /// declared. When false, the constructor was declared by the user.
-  bool ImplicitlyDeclared : 1;
-
   /// ImplicitlyDefined - Whether this constructor was implicitly
   /// defined by the compiler. When false, the constructor was defined
   /// by the user. In C++03, this flag will have the same value as
-  /// ImplicitlyDeclared. In C++0x, however, a constructor that is
+  /// Implicit. In C++0x, however, a constructor that is
   /// explicitly defaulted (i.e., defined with " = default") will have
-  /// @c !ImplicitlyDeclared && ImplicitlyDefined.
+  /// @c !Implicit && ImplicitlyDefined.
   bool ImplicitlyDefined : 1;
 
   /// FIXME: Add support for base and member initializers.
@@ -636,8 +629,9 @@ class CXXConstructorDecl : public CXXMethodDecl {
                      bool isExplicit, bool isInline, bool isImplicitlyDeclared)
     : CXXMethodDecl(CXXConstructor, RD, L, N, T, false, isInline, 
                     /*PrevDecl=*/0),
-      Explicit(isExplicit), ImplicitlyDeclared(isImplicitlyDeclared),
-      ImplicitlyDefined(false) { }
+      Explicit(isExplicit), ImplicitlyDefined(false) { 
+    setImplicit(isImplicitlyDeclared);
+  }
 
 public:
   static CXXConstructorDecl *Create(ASTContext &C, CXXRecordDecl *RD,
@@ -648,11 +642,6 @@ public:
   /// isExplicit - Whether this constructor was marked "explicit" or not.  
   bool isExplicit() const { return Explicit; }
 
-  /// isImplicitlyDeclared - Whether this constructor was implicitly
-  /// declared. If false, then this constructor was explicitly
-  /// declared by the user.
-  bool isImplicitlyDeclared() const { return ImplicitlyDeclared; }
-
   /// isImplicitlyDefined - Whether this constructor was implicitly
   /// defined. If false, then this constructor was defined by the
   /// user. This operation can only be invoked if the constructor has
@@ -728,16 +717,12 @@ public:
 /// };
 /// @endcode
 class CXXDestructorDecl : public CXXMethodDecl {
-  /// ImplicitlyDeclared - Whether this destructor was implicitly
-  /// declared. When false, the destructor was declared by the user.
-  bool ImplicitlyDeclared : 1;
-
   /// ImplicitlyDefined - Whether this destructor was implicitly
   /// defined by the compiler. When false, the destructor was defined
   /// by the user. In C++03, this flag will have the same value as
-  /// ImplicitlyDeclared. In C++0x, however, a destructor that is
+  /// Implicit. In C++0x, however, a destructor that is
   /// explicitly defaulted (i.e., defined with " = default") will have
-  /// @c !ImplicitlyDeclared && ImplicitlyDefined.
+  /// @c !Implicit && ImplicitlyDefined.
   bool ImplicitlyDefined : 1;
 
   CXXDestructorDecl(CXXRecordDecl *RD, SourceLocation L,
@@ -745,8 +730,9 @@ class CXXDestructorDecl : public CXXMethodDecl {
                     bool isInline, bool isImplicitlyDeclared)
     : CXXMethodDecl(CXXDestructor, RD, L, N, T, false, isInline, 
                     /*PrevDecl=*/0),
-      ImplicitlyDeclared(isImplicitlyDeclared),
-      ImplicitlyDefined(false) { }
+      ImplicitlyDefined(false) { 
+    setImplicit(isImplicitlyDeclared);
+  }
 
 public:
   static CXXDestructorDecl *Create(ASTContext &C, CXXRecordDecl *RD,
@@ -754,11 +740,6 @@ public:
                                    QualType T, bool isInline, 
                                    bool isImplicitlyDeclared);
 
-  /// isImplicitlyDeclared - Whether this destructor was implicitly
-  /// declared. If false, then this destructor was explicitly
-  /// declared by the user.
-  bool isImplicitlyDeclared() const { return ImplicitlyDeclared; }
-
   /// isImplicitlyDefined - Whether this destructor was implicitly
   /// defined. If false, then this destructor was defined by the
   /// user. This operation can only be invoked if the destructor has
@@ -857,9 +838,6 @@ public:
                              SourceLocation L,IdentifierInfo *Id,
                              QualType T, ScopedDecl *PrevDecl);
   
-  void setAccess(AccessSpecifier AS) { Access = AS; }
-  AccessSpecifier getAccess() const { return AccessSpecifier(Access); }
-
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return D->getKind() == CXXClassVar; }
   static bool classof(const CXXClassVarDecl *D) { return true; }
index 13a3a93d625f508e74b3a46145bd3a80c3cedfa2..a29f43f1dd5ced0850fa7a284eb94ce428b52430 100644 (file)
@@ -1476,7 +1476,7 @@ DIAG(err_base_init_direct_and_virtual, ERROR,
      "base class initializer %0 names both a direct base class and an"
      " inherited virtual base class")
 
-// C++ anonymous unions and GNU anonymous structs
+// C++ anonymous unions and GNU anonymous structs/unions
 DIAG(ext_anonymous_union, EXTENSION,
      "anonymous unions are a GNU extension in C")
 DIAG(ext_anonymous_struct, EXTENSION,
@@ -1491,6 +1491,16 @@ DIAG(err_anonymous_union_member_redecl, ERROR,
      "member of anonymous union redeclares %0")
 DIAG(err_anonymous_struct_member_redecl, ERROR,
      "member of anonymous struct redeclares %0")
+DIAG(err_anonymous_record_with_type, ERROR,
+     "types cannot be declared in an anonymous %select{struct|union}0")
+DIAG(err_anonymous_record_with_function, ERROR,
+     "functions cannot be declared in an anonymous %select{struct|union}0")
+DIAG(err_anonymous_record_with_static, ERROR,
+     "static members cannot be declared in an anonymous %select{struct|union}0")
+DIAG(err_anonymous_record_bad_member, ERROR,
+     "anonymous %select{struct|union}0 can only contain non-static data members")
+DIAG(err_anonymous_record_nonpublic_member, ERROR,
+     "anonymous %select{struct|union}0 cannot contain a %select{private|protected}1 data member")
 
 // Derived classes.
 DIAG(err_dup_virtual, ERROR,
index bfe8a184e8d51c190964f29be234912973e2755b..b3f19a25b86043b2c18f874a89abb62272b1cb40 100644 (file)
@@ -97,6 +97,15 @@ FieldDecl *FieldDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation L,
   return new (Mem) FieldDecl(Decl::Field, DC, L, Id, T, BW, Mutable, PrevDecl);
 }
 
+bool FieldDecl::isAnonymousStructOrUnion() const {
+  if (!isImplicit() || getDeclName())
+    return false;
+  
+  if (const RecordType *Record = getType()->getAsRecordType())
+    return Record->getDecl()->isAnonymousStructOrUnion();
+
+  return false;
+}
 
 EnumConstantDecl *EnumConstantDecl::Create(ASTContext &C, EnumDecl *CD,
                                            SourceLocation L,
index 545f18ed056bec039f94b51bd20eaedf014a38ad..7f787de75f784271e53ff1ed17db91cea00d1048 100644 (file)
@@ -153,7 +153,7 @@ bool CXXRecordDecl::hasConstCopyAssignment(ASTContext &Context) const {
 void
 CXXRecordDecl::addedConstructor(ASTContext &Context, 
                                 CXXConstructorDecl *ConDecl) {
-  if (!ConDecl->isImplicitlyDeclared()) {
+  if (!ConDecl->isImplicit()) {
     // Note that we have a user-declared constructor.
     UserDeclaredConstructor = true;
 
index e1c9efc326e7d9a4035828408e28e824742bd1fa..49719ab4f524979c414ec53cba9835d3bdf94a5b 100644 (file)
@@ -924,6 +924,48 @@ Sema::DeclTy *Sema::BuildAnonymousStructOrUnion(Scope *S, DeclSpec &DS,
       DS.SetStorageClassSpec(DeclSpec::SCS_unspecified, SourceLocation(),
                              PrevSpec);
     }
+
+    // C++ [class.union]p2: 
+    //   The member-specification of an anonymous union shall only
+    //   define non-static data members. [Note: nested types and
+    //   functions cannot be declared within an anonymous union. ]
+    for (DeclContext::decl_iterator Mem = Record->decls_begin(),
+                                 MemEnd = Record->decls_end();
+         Mem != MemEnd; ++Mem) {
+      if (FieldDecl *FD = dyn_cast<FieldDecl>(*Mem)) {
+        // C++ [class.union]p3:
+        //   An anonymous union shall not have private or protected
+        //   members (clause 11).
+        if (FD->getAccess() == AS_protected || FD->getAccess() == AS_private) {
+          Diag(FD->getLocation(), diag::err_anonymous_record_nonpublic_member)
+            << (int)Record->isUnion() << (int)(FD->getAccess() == AS_protected);
+          Invalid = true;
+        }
+      } else if ((*Mem)->isImplicit()) {
+        // Any implicit members are fine.
+      } else if (RecordDecl *MemRecord = dyn_cast<RecordDecl>(*Mem)) {
+        if (!MemRecord->isAnonymousStructOrUnion() &&
+            MemRecord->getDeclName()) {
+          // This is a nested type declaration.
+          Diag(MemRecord->getLocation(), diag::err_anonymous_record_with_type)
+            << (int)Record->isUnion();
+          Invalid = true;
+        }
+      } else {
+        // We have something that isn't a non-static data
+        // member. Complain about it.
+        unsigned DK = diag::err_anonymous_record_bad_member;
+        if (isa<TypeDecl>(*Mem))
+          DK = diag::err_anonymous_record_with_type;
+        else if (isa<FunctionDecl>(*Mem))
+          DK = diag::err_anonymous_record_with_function;
+        else if (isa<VarDecl>(*Mem))
+          DK = diag::err_anonymous_record_with_static;
+        Diag((*Mem)->getLocation(), DK)
+            << (int)Record->isUnion();
+          Invalid = true;
+      }
+    }
   } else {
     // FIXME: Check GNU C semantics
   }
@@ -941,6 +983,9 @@ Sema::DeclTy *Sema::BuildAnonymousStructOrUnion(Scope *S, DeclSpec &DS,
                              Context.getTypeDeclType(Record),
                              /*BitWidth=*/0, /*Mutable=*/false,
                              /*PrevDecl=*/0);
+    Anon->setAccess(AS_public);
+    if (getLangOptions().CPlusPlus)
+      FieldCollector->Add(cast<FieldDecl>(Anon));
   } else {
     VarDecl::StorageClass SC;
     switch (DS.getStorageClassSpec()) {
@@ -966,6 +1011,7 @@ Sema::DeclTy *Sema::BuildAnonymousStructOrUnion(Scope *S, DeclSpec &DS,
                            SC, /*FIXME:LastDeclarator=*/0,
                            DS.getSourceRange().getBegin());
   }
+  Anon->setImplicit();
 
   // Add the anonymous struct/union object to the current
   // context. We'll be referencing this object when we refer to one of
@@ -3176,18 +3222,52 @@ void Sema::ActOnFields(Scope* S,
   // Verify that all the fields are okay.
   unsigned NumNamedMembers = 0;
   llvm::SmallVector<FieldDecl*, 32> RecFields;
-  llvm::SmallSet<const IdentifierInfo*, 32> FieldIDs;
+
+  // FIXME: Eventually, we'd like to eliminate this in favor of
+  // checking for redeclarations on-the-fly.
+  llvm::DenseMap<const IdentifierInfo*, FieldDecl *> FieldIDs;
   
   for (unsigned i = 0; i != NumFields; ++i) {
-    
     FieldDecl *FD = cast_or_null<FieldDecl>(static_cast<Decl*>(Fields[i]));
     assert(FD && "missing field decl");
     
-    // Remember all fields.
-    RecFields.push_back(FD);
-    
     // Get the type for the field.
     Type *FDTy = FD->getType().getTypePtr();
+
+    if (FD->isAnonymousStructOrUnion()) {
+      // We have found a field that represents an anonymous struct
+      // or union. Introduce all of the inner fields (recursively)
+      // into the list of fields we know about, so that we can produce
+      // an appropriate error message in cases like:
+      //
+      //   struct X {
+      //     union {
+      //       int x;
+      //       float f;
+      //     };
+      //     double x;
+      //   };
+      llvm::SmallVector<FieldDecl *, 4> AnonStructUnionFields;
+      AnonStructUnionFields.push_back(FD);
+      while (!AnonStructUnionFields.empty()) {
+        FieldDecl *AnonField = AnonStructUnionFields.back();
+        AnonStructUnionFields.pop_back();
+        
+        RecordDecl *AnonRecord 
+          = AnonField->getType()->getAsRecordType()->getDecl();
+        for (RecordDecl::field_iterator F = AnonRecord->field_begin(),
+                                     FEnd = AnonRecord->field_end();
+             F != FEnd; ++F) {
+          if ((*F)->isAnonymousStructOrUnion())
+            AnonStructUnionFields.push_back(*F);
+          else if (const IdentifierInfo *II = (*F)->getIdentifier())
+            FieldIDs[II] = *F;
+        }
+      }
+    } else {
+      // Remember all fields written by the user.
+      RecFields.push_back(FD);
+    }
       
     // C99 6.7.2.1p2 - A field may not be a function type.
     if (FDTy->isFunctionType()) {
@@ -3262,23 +3342,16 @@ void Sema::ActOnFields(Scope* S,
     // Keep track of the number of named members.
     if (IdentifierInfo *II = FD->getIdentifier()) {
       // Detect duplicate member names.
-      if (!FieldIDs.insert(II)) {
+      if (FieldIDs[II]) {
         Diag(FD->getLocation(), diag::err_duplicate_member) << II;
         // Find the previous decl.
-        SourceLocation PrevLoc;
-        for (unsigned i = 0; ; ++i) {
-          assert(i != RecFields.size() && "Didn't find previous def!");
-          if (RecFields[i]->getIdentifier() == II) {
-            PrevLoc = RecFields[i]->getLocation();
-            break;
-          }
-        }
-        Diag(PrevLoc, diag::note_previous_definition);
+        Diag(FieldIDs[II]->getLocation(), diag::note_previous_definition);
         FD->setInvalidDecl();
         EnclosingDecl->setInvalidDecl();
         continue;
       }
       ++NumNamedMembers;
+      FieldIDs[II] = FD;
     }
   }
 
index 19582c0d798807431f409325547933d57db4fb01..c5e5aa2f3d266464dc0ecadadbffb9f0976b92b6 100644 (file)
@@ -427,9 +427,12 @@ void Sema::ActOnStartCXXClassDef(Scope *S, DeclTy *D, SourceLocation LBrace) {
     //   class itself; this is known as the injected-class-name. For
     //   purposes of access checking, the injected-class-name is treated
     //   as if it were a public member name.
-    PushOnScopeChains(CXXRecordDecl::Create(Context, Dcl->getTagKind(),
-                                            CurContext, Dcl->getLocation(),
-                                            Dcl->getIdentifier(), Dcl), S);
+    RecordDecl *InjectedClassName
+      = CXXRecordDecl::Create(Context, Dcl->getTagKind(),
+                              CurContext, Dcl->getLocation(),
+                              Dcl->getIdentifier(), Dcl);
+    InjectedClassName->setImplicit();
+    PushOnScopeChains(InjectedClassName, S);
   }
 }
 
@@ -789,6 +792,7 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) {
                                  /*isInline=*/true,
                                  /*isImplicitlyDeclared=*/true);
     DefaultCon->setAccess(AS_public);
+    DefaultCon->setImplicit();
     ClassDecl->addDecl(Context, DefaultCon);
 
     // Notify the class that we've added a constructor.
@@ -860,6 +864,7 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) {
                                    /*isInline=*/true,
                                    /*isImplicitlyDeclared=*/true);
     CopyConstructor->setAccess(AS_public);
+    CopyConstructor->setImplicit();
 
     // Add the parameter to the constructor.
     ParmVarDecl *FromParam = ParmVarDecl::Create(Context, CopyConstructor,
@@ -936,6 +941,7 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) {
                                                     false, 0),
                             /*isStatic=*/false, /*isInline=*/true, 0);
     CopyAssignment->setAccess(AS_public);
+    CopyAssignment->setImplicit();
 
     // Add the parameter to the operator.
     ParmVarDecl *FromParam = ParmVarDecl::Create(Context, CopyAssignment,
@@ -964,6 +970,7 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) {
                                   /*isInline=*/true,
                                   /*isImplicitlyDeclared=*/true);
     Destructor->setAccess(AS_public);
+    Destructor->setImplicit();
     ClassDecl->addDecl(Context, Destructor);
   }
 }
index d078d2ff08e630af82ad4f58fae4c7962a0f99a0..a66745b378481b599d7fdc70ed88cd36d629fee7 100644 (file)
@@ -66,11 +66,11 @@ struct Redecl {
   union {
     int x; // expected-error{{member of anonymous union redeclares 'x'}}
     float y;
-    double z; // FIXME: note here
+    double z; // expected-note{{previous definition is here}}
     double zz; // expected-note{{previous definition is here}}
   };
 
-  int z; // FIXME: should complain here!
+  int z; // expected-error{{duplicate member 'z'}}
   void zz(); // expected-error{{redefinition of 'zz' as different kind of symbol}}
 };
 
@@ -92,8 +92,19 @@ void f() {
 void g() {
   union {
     int i;
-    float f;
+    float f2;
   };
   i = 0;
-  f = 0.0;
+  f2 = 0.0;
 }
+
+struct BadMembers {
+  union {
+    struct X { }; // expected-error {{types cannot be declared in an anonymous union}}
+    struct { int x; int y; } y;
+    
+    void f(); // expected-error{{functions cannot be declared in an anonymous union}}
+  private: int x1; // expected-error{{anonymous union cannot contain a private data member}}
+  protected: float x2; // expected-error{{anonymous union cannot contain a protected data member}}
+  };
+};
index a4a6741b34e65ecd70d3c63cda673aa69705f2d1..96cd287af874d4d407c757e61967733e099611e9 100644 (file)
@@ -1077,10 +1077,10 @@ welcome!</p>
 <tr>\r
   <td>&nbsp;&nbsp;9.5 [class.union]</td>\r
   <td class="complete" align="center">&#x2713;</td>  \r
-  <td></td>\r
-  <td></td>\r
-  <td></td>\r
-  <td></td>\r
+  <td class="complete" align="center">&#x2713;</td>\r
+  <td class="medium"></td>\r
+  <td class="medium"></td>\r
+  <td>Semantic analysis does not yet check all of the requirements placed on the members of unions.</td>\r
 </tr>\r
 <tr>\r
   <td>&nbsp;&nbsp;9.6 [class.bit]</td>\r