Skip to content

[Clang] Implement CWG2598: Union of non-literal types #78195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 17, 2024

Conversation

cor3ntin
Copy link
Contributor

A union is considered a literal type unless it has no non-literal member.

This resolves CWG2096 (which makes unions with literal members literal) and CWG2598 (empty unions are literal types).

Fixes #77924

A union is considered a literal type unless it has no
non-literal member.

This resolves CWG2096 (which makes unions with literal members literal)
and CWG2598 (empty unions are literal types).

Fixes llvm#77924
@cor3ntin cor3ntin added c++ clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 15, 2024
@cor3ntin cor3ntin requested a review from Endilll as a code owner January 15, 2024 17:25
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Jan 15, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 15, 2024

@llvm/pr-subscribers-clang

Author: cor3ntin (cor3ntin)

Changes

A union is considered a literal type unless it has no non-literal member.

This resolves CWG2096 (which makes unions with literal members literal) and CWG2598 (empty unions are literal types).

Fixes #77924


Full diff: https://github.com/llvm/llvm-project/pull/78195.diff

7 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/AST/DeclCXX.h (+13-24)
  • (modified) clang/lib/AST/DeclCXX.cpp (+28)
  • (modified) clang/test/CXX/drs/dr20xx.cpp (+2)
  • (modified) clang/test/CXX/drs/dr25xx.cpp (+47)
  • (modified) clang/test/SemaCXX/literal-type.cpp (+33)
  • (modified) clang/www/cxx_dr_status.html (+2-2)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7e130304c5c08f..d967d904ba3fce 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -227,6 +227,11 @@ C++2c Feature Support
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+- Implemented `CWG2598 <https://wg21.link/CWG2598>`_ and `CWG2096 <https://wg21.link/CWG2096>`_,
+  making unions (that have either no members or at least one literal member) literal types.
+  (`#77924: <https://github.com/llvm/llvm-project/issues/77924>`_).
+
+
 C Language Changes
 ------------------
 - ``structs``, ``unions``, and ``arrays`` that are const may now be used as
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 648f5f94640870..75b73700c44d67 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1439,31 +1439,20 @@ class CXXRecordDecl : public RecordDecl {
 
   /// Determine whether this class is a literal type.
   ///
-  /// C++11 [basic.types]p10:
+  /// C++20 [basic.types]p10:
   ///   A class type that has all the following properties:
-  ///     - it has a trivial destructor
-  ///     - every constructor call and full-expression in the
-  ///       brace-or-equal-intializers for non-static data members (if any) is
-  ///       a constant expression.
-  ///     - it is an aggregate type or has at least one constexpr constructor
-  ///       or constructor template that is not a copy or move constructor, and
-  ///     - all of its non-static data members and base classes are of literal
-  ///       types
-  ///
-  /// We resolve DR1361 by ignoring the second bullet. We resolve DR1452 by
-  /// treating types with trivial default constructors as literal types.
-  ///
-  /// Only in C++17 and beyond, are lambdas literal types.
-  bool isLiteral() const {
-    const LangOptions &LangOpts = getLangOpts();
-    return (LangOpts.CPlusPlus20 ? hasConstexprDestructor()
-                                          : hasTrivialDestructor()) &&
-           (!isLambda() || LangOpts.CPlusPlus17) &&
-           !hasNonLiteralTypeFieldsOrBases() &&
-           (isAggregate() || isLambda() ||
-            hasConstexprNonCopyMoveConstructor() ||
-            hasTrivialDefaultConstructor());
-  }
+  ///     - it has a constexpr destructor
+  ///     - all of its non-static non-variant data members and base classes
+  ///       are of non-volatile literal types, and it:
+  ///        - is a closure type
+  ///        - is an aggregate union type that has either no variant members
+  ///          or at least one variant member of non-volatile literal type
+  ///        - is a non-union aggregate type for which each of its anonymous
+  ///          union members satisfies the above requirements for an aggregate
+  ///          union type, or
+  ///        - has at least one constexpr constructor or constructor template
+  ///          that is not a copy or move constructor.
+  bool isLiteral() const;
 
   /// Determine whether this is a structural type.
   bool isStructural() const {
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 98b0a6dc28ea2f..0d49d54fd64197 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1383,6 +1383,34 @@ void CXXRecordDecl::addedMember(Decl *D) {
   }
 }
 
+bool CXXRecordDecl::isLiteral() const {
+  const LangOptions &LangOpts = getLangOpts();
+  if (!(LangOpts.CPlusPlus20 ? hasConstexprDestructor()
+                             : hasTrivialDestructor()))
+    return false;
+
+  if (isLambda() && !LangOpts.CPlusPlus17)
+    return false;
+
+  if (hasNonLiteralTypeFieldsOrBases()) {
+    // CWG2598
+    // is an aggregate union type that has either no variant
+    // members or at least one variant member of non-volatile literal type,
+    if (!isUnion())
+      return false;
+    bool HasAtLeastOneTrivialMember =
+        fields().empty() || any_of(fields(), [this](const FieldDecl *D) {
+          return !D->getType().isVolatileQualified() &&
+                 D->getType().isTrivialType(getASTContext());
+        });
+    if (!HasAtLeastOneTrivialMember)
+      return false;
+  }
+
+  return isAggregate() || isLambda() || hasConstexprNonCopyMoveConstructor() ||
+         hasTrivialDefaultConstructor();
+}
+
 void CXXRecordDecl::addedSelectedDestructor(CXXDestructorDecl *DD) {
   DD->setIneligibleOrNotSelected(false);
   addedEligibleSpecialMemberFunction(DD, SMF_Destructor);
diff --git a/clang/test/CXX/drs/dr20xx.cpp b/clang/test/CXX/drs/dr20xx.cpp
index 60ee7684440f54..f7f37379e61ad1 100644
--- a/clang/test/CXX/drs/dr20xx.cpp
+++ b/clang/test/CXX/drs/dr20xx.cpp
@@ -418,3 +418,5 @@ namespace dr2094 { // dr2094: 5
   static_assert(__is_trivially_assignable(A, const A&), "");
   static_assert(__is_trivially_assignable(B, const B&), "");
 }
+
+// dr2096: dup 2598
diff --git a/clang/test/CXX/drs/dr25xx.cpp b/clang/test/CXX/drs/dr25xx.cpp
index 32bbfc63d0df4f..6f51e9c249040a 100644
--- a/clang/test/CXX/drs/dr25xx.cpp
+++ b/clang/test/CXX/drs/dr25xx.cpp
@@ -208,3 +208,50 @@ namespace dr2565 { // dr2565: 16 open
 
 #endif
 }
+
+
+namespace dr2598 { // dr2598: 18
+#if __cplusplus >= 201103L
+struct NonLiteral {
+    NonLiteral();
+};
+
+struct anonymous1 {
+    union {} a;
+};
+static_assert(__is_literal(anonymous1), "");
+
+struct anonymous2 {
+    union { char c; };
+};
+static_assert(__is_literal(anonymous2), "");
+
+struct anonymous3 {
+    union { char c; NonLiteral NL; };
+};
+static_assert(__is_literal(anonymous3), "");
+
+struct anonymous4 {
+    union { NonLiteral NL; };
+};
+static_assert(!__is_literal(anonymous4), "");
+
+union empty {};
+static_assert(__is_literal(empty), "");
+
+union union1 { char c; };
+static_assert(__is_literal(union1), "");
+
+union union2 { char c; NonLiteral NL;};
+static_assert(__is_literal(union2), "");
+
+union union3 { NonLiteral NL;};
+static_assert(!__is_literal(union3), "");
+
+union union4 { union4(); };
+static_assert(!__is_literal(union4), "");
+
+union union5 { static NonLiteral NL; };
+static_assert(__is_literal(union5), "");
+#endif
+}
diff --git a/clang/test/SemaCXX/literal-type.cpp b/clang/test/SemaCXX/literal-type.cpp
index 14a4094c45a1b8..88535c169fe01c 100644
--- a/clang/test/SemaCXX/literal-type.cpp
+++ b/clang/test/SemaCXX/literal-type.cpp
@@ -1,4 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
+
 
 static_assert(__is_literal(int), "fail");
 static_assert(__is_literal_type(int), "fail"); // alternate spelling for GCC
@@ -75,3 +77,34 @@ template <typename T> class HasConstExprCtorT {
 static_assert(__is_literal(HasConstExprCtor), "fail");
 static_assert(__is_literal(HasConstExprCtorTemplate<int>), "fail");
 static_assert(__is_literal(HasConstExprCtorT<NonLiteral>), "fail");
+
+
+#if __cplusplus >= 202003L
+namespace GH77924 {
+
+struct A { A(); };
+template <class T>
+struct opt {
+  union Data {
+      constexpr Data() : x{} {}
+      constexpr ~Data() {}
+      char x;
+      T data;
+  };
+
+  constexpr opt() : data{} {}
+  constexpr ~opt() { if (engaged) data.data.~T(); }
+  Data data;
+  bool engaged = false;
+};
+
+consteval void foo() {
+  opt<A> a;
+}
+
+void test() {
+  foo();
+}
+
+}
+#endif
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 397bf1357d3cb3..5e7c1a0fa2f246 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -12384,7 +12384,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2096.html">2096</a></td>
     <td>CD4</td>
     <td>Constraints on literal unions</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="unreleased" align="center">Duplicate of <a href="#2598">2598</a></td>
   </tr>
   <tr class="open" id="2097">
     <td><a href="https://cplusplus.github.io/CWG/issues/2097.html">2097</a></td>
@@ -15396,7 +15396,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2598.html">2598</a></td>
     <td>C++23</td>
     <td>Unions should not require a non-static data member of literal type</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="unreleased" align="center">Clang 18</td>
   </tr>
   <tr id="2599">
     <td><a href="https://cplusplus.github.io/CWG/issues/2599.html">2599</a></td>

bool HasAtLeastOneTrivialMember =
fields().empty() || any_of(fields(), [this](const FieldDecl *D) {
return !D->getType().isVolatileQualified() &&
D->getType().isTrivialType(getASTContext());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about literal non-trivial types (e.g. struct Literal { constexpr Literal() {} }; union union6 { NonLiteral NL; Literal L; };?

.isLiteralType should work here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autocompletion blunder, Nice catch!

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a potentially breaking change since we have expanded what types are considered literals and already existing code may observe this fix?

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good but I would like a second set of eyes.

// members or at least one variant member of non-volatile literal type,
if (!isUnion())
return false;
bool HasAtLeastOneTrivialMember =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use trivial here? The DRs don't refer to trivial it refers to non-volatile literal type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes no sense

static_assert(__is_literal(union5), "");

struct Literal { constexpr Literal() {} };
union union6 { NonLiteral NL; Literal L; };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also include the exact example from CWG2598 as well, just to be complete.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also should we have a static_assert after this line?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also noticed that gcc does not seem to support __is_literal only __is_literal_type

Copy link
Contributor Author

@cor3ntin cor3ntin Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, both are synonym in clang

@cor3ntin
Copy link
Contributor Author

Is this a potentially breaking change since we have expanded what types are considered literals and already existing code may observe this fix?

Some expression will become constant expressions and presumably that can be SFINAE upon, but given that this is a DR, I don't think we are affecting conformance and I don't think we make strong sfinae guarantee.

GCC is conforming with that DR in all language mode since 10.0
https://compiler-explorer.com/z/hKnThPx1T

return false;
}

return isAggregate() || isLambda() || hasConstexprNonCopyMoveConstructor() ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these conditions be moved 'above' the union stuff? I would think they are 'cheaper'. Also, I see the 'isLambda' here, despite it only being in C++17 mode on line 1392.

@@ -1389,7 +1389,11 @@ bool CXXRecordDecl::isLiteral() const {
: hasTrivialDestructor()))
return false;

if (isLambda() && !LangOpts.CPlusPlus17)
// Lambdas are literal types since C++17.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why is isAggregate or hasConstexprNonCopyMoveConstructor/etc not just return true here? Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And aggregate can have non literal members

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... this is worse then. Other than the concern about 'isLambda', I think you should revert the suggested change here. Its not worth the awkward inversions here.

@cor3ntin cor3ntin merged commit 4e64159 into llvm:main Jan 17, 2024
@groundswellaudio
Copy link

Thanks for the fix @cor3ntin. There is still a problem however with std::optional, which I think boils down to implicit destructor and anonymous union :

struct A {
  A ();
  ~A();
};

template <class T>
struct opt
{
  union {
    char c;
    T data;
  };
  
  constexpr opt() {}
  
  constexpr ~opt() {
    if (engaged)
      data.~T();
  }
  
  bool engaged = false;
};

consteval void foo()
{
  opt<A> a;
}

This fails with non-literal type 'opt<A>' cannot be used in a constant expression, but compiles if A has no destructor (or if that destructor is constexpr).

I suspect that the culprit is here :

if (!Subobj->hasConstexprDestructor())

The implicit destructor of the anonymous union is not constexpr because one of its subobjects has a non-constexpr destructor.

@cor3ntin
Copy link
Contributor Author

@groundswellaudio Thanks for letting me know. can you create a separate issue ?

@cor3ntin
Copy link
Contributor Author

@groundswellaudio actually, that should be fixed by #77753
@Fznamznon Can you confirm?

@Fznamznon
Copy link
Contributor

@groundswellaudio actually, that should be fixed by #77753
@Fznamznon Can you confirm?

Yes, it does make an error to go away. However the function foo from the code won't be callable. If it is constexpr instead of consteval, then it is callable outside of constexpr context.

@groundswellaudio
Copy link

groundswellaudio commented Jan 18, 2024

@Fznamznon
@cor3ntin
So #77753 doesn't solve the problem of the enclosing class of an anonymous union not being literal when it should be, right?

@cor3ntin
Copy link
Contributor Author

cor3ntin commented Jan 18, 2024

Looking at that again, i suspect I did not handle indirect fields properly. I'll try to get to that. Sorry!
(I was right the first time)

@groundswellaudio
Copy link

By looking at your tests on anonymous unions, I'd say that indirect fields are handled correctly (perhaps they're not included in the fields range?, otherwise the union-like class would not be considered literal) and that rather the implicitly generated special member functions of the anonymous union are the problems (and it's unclear to me why these would be needed at all, but perhaps it's simpler to generate them that to make a special case anonymous unions).

@cor3ntin
Copy link
Contributor Author

@Fznamznon @cor3ntin So #77753 doesn't solve the problem of the enclosing class of an anonymous union not being literal when it should be, right?

It should actually, CF comments in that PR (I got your code working locally)

@Fznamznon
Copy link
Contributor

@cor3ntin , perhaps a dumb question, but #77753 should only help in post C++20 modes since only there destructor can be constexpr. How about older versions? Clang will still emit an error that the type from #78195 (comment) is not literal in C++17 for example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang] Union of a non-literal type and a literal type should be literal
7 participants