Skip to content
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

[C2y] Implement WG14 N3369 and N3469 (_Countof) #133125

Merged
merged 11 commits into from
Mar 27, 2025

Conversation

AaronBallman
Copy link
Collaborator

@AaronBallman AaronBallman commented Mar 26, 2025

C2y adds the _Countof operator which returns the number of elements in an array. As with sizeof, _Countof either accepts a parenthesized type name or an expression. Its operand must be (of) an array type. When passed a constant-size array operand, the operator is a constant expression which is valid for use as an integer constant expression.

This is being exposed as an extension in earlier C language modes, but not in C++. C++ already has std::extent and std::rank to cover these needs, so the operator doesn't seem to get the user enough benefit to warrant carrying this as an extension.

Fixes #102836

C2y adds the _Countof operator which returns the number of elements in
an array. As with sizeof, _Countof either accepts a parenthesized type
name or an expression. Its operand must be (of) an array type. When
passed a constant-size array operand, the operator is a constant
expression which is valid for use as an integer constant expression.

Fixes llvm#102836
@AaronBallman AaronBallman added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" extension:clang c2y labels Mar 26, 2025
@llvmbot llvmbot added the clang:codegen IR generation bugs: mangling, exceptions, etc. label Mar 26, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 26, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Aaron Ballman (AaronBallman)

Changes

C2y adds the _Countof operator which returns the number of elements in an array. As with sizeof, _Countof either accepts a parenthesized type name or an expression. Its operand must be (of) an array type. When passed a constant-size array operand, the operator is a constant expression which is valid for use as an integer constant expression.

This is being exposed as an extension in earlier C language modes, but not in C++. C++ already has std::extent and std::rank to cover these needs, so the operator doesn't seem to get the user enough benefit to warrant carrying this as an extension.

Fixes #102836


Patch is 25.31 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/133125.diff

15 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+1)
  • (modified) clang/docs/ReleaseNotes.rst (+6)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+5)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+2)
  • (modified) clang/lib/AST/ExprConstant.cpp (+18-1)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+10-5)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+17-4)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+32-7)
  • (added) clang/test/C/C2y/n3369.c (+61)
  • (added) clang/test/C/C2y/n3369_1.c (+25)
  • (added) clang/test/C/C2y/n3369_2.c (+92)
  • (added) clang/test/C/C2y/n3469.c (+14)
  • (modified) clang/www/c_status.html (+2-2)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index d4771775c9739..8b5707ce2acac 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1653,6 +1653,7 @@ Array & element qualification (N2607)                                          C
 Attributes (N2335)                                                             C23           C89
 ``#embed`` (N3017)                                                             C23           C89, C++
 Octal literals prefixed with ``0o`` or ``0O``                                  C2y           C89, C++
+``_Countof`` (N3369, N3469)                                                    C2y           C89
 ============================================= ================================ ============= =============
 
 Builtin type aliases
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 04ec2cfef679c..b82e79c092c4e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -141,6 +141,12 @@ C2y Feature Support
   paper also introduced octal and hexadecimal delimited escape sequences (e.g.,
   ``"\x{12}\o{12}"``) which are also supported as an extension in older C
   language modes.
+- Implemented `WG14 N3369 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3369.pdf>`_
+  which introduces the ``_Lengthof`` operator, and `WG14 N3469 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3469.htm>`_
+  which renamed ``_Lengthof`` to ``_Countof``. This feature is implemented as
+  a conforming extension in earlier C language modes, but not in C++ language
+  modes (``std::extent`` and ``std::rank`` already provide the same
+  functionality but with more granularity).
 
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 4dc956f7ae6f7..86c361b4dbcf7 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -171,12 +171,17 @@ def ext_c99_feature : Extension<
   "'%0' is a C99 extension">, InGroup<C99>;
 def ext_c11_feature : Extension<
   "'%0' is a C11 extension">, InGroup<C11>;
+def ext_c2y_feature : Extension<
+  "'%0' is a C2y extension">, InGroup<C2y>;
 def warn_c11_compat_keyword : Warning<
   "'%0' is incompatible with C standards before C11">,
   InGroup<CPre11Compat>, DefaultIgnore;
 def warn_c23_compat_keyword : Warning<
  "'%0' is incompatible with C standards before C23">,
  InGroup<CPre23Compat>, DefaultIgnore;
+def warn_c2y_compat_keyword : Warning<
+  "'%0' is incompatible with C standards before C2y">,
+  InGroup<CPre2yCompat>, DefaultIgnore;
 
 def err_c11_noreturn_misplaced : Error<
   "'_Noreturn' keyword must precede function declarator">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c77cde297dc32..1e900437d41ce 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7022,6 +7022,8 @@ def err_sizeof_alignof_typeof_bitfield : Error<
   "bit-field">;
 def err_alignof_member_of_incomplete_type : Error<
   "invalid application of 'alignof' to a field of a class still being defined">;
+def err_countof_arg_not_array_type : Error<
+  "'_Countof' requires an argument of array type; %0 invalid">;
 def err_vecstep_non_scalar_vector_type : Error<
   "'vec_step' requires built-in scalar or vector type, %0 invalid">;
 def err_offsetof_incomplete_type : Error<
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 1bf9f43f80986..880928ae0447d 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -349,6 +349,8 @@ KEYWORD(__func__                    , KEYALL)
 KEYWORD(__objc_yes                  , KEYALL)
 KEYWORD(__objc_no                   , KEYALL)
 
+// C2y
+UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX)
 
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 95da7b067b459..92b1f41bf2fab 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -14926,6 +14926,23 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
 
     return false;
   }
+  case UETT_CountOf: {
+    QualType Ty = E->getTypeOfArgument();
+    assert(Ty->isArrayType());
+
+    // We don't need to worry about array element qualifiers, so getting the
+    // unsafe array type is fine.
+    if (const auto *CAT =
+            dyn_cast<ConstantArrayType>(Ty->getAsArrayTypeUnsafe())) {
+      return Success(CAT->getSize(), E);
+    }
+
+    // If it wasn't a constant array, it's not a valid constant expression.
+    assert(!Ty->isConstantSizeType());
+    // FIXME: Better diagnostic.
+    Info.FFDiag(E->getBeginLoc());
+    return false;
+  }
   }
 
   llvm_unreachable("unknown expr/type trait");
@@ -17425,7 +17442,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   }
   case Expr::UnaryExprOrTypeTraitExprClass: {
     const UnaryExprOrTypeTraitExpr *Exp = cast<UnaryExprOrTypeTraitExpr>(E);
-    if ((Exp->getKind() ==  UETT_SizeOf) &&
+    if ((Exp->getKind() ==  UETT_SizeOf || Exp->getKind() == UETT_CountOf) &&
         Exp->getTypeOfArgument()->isVariableArrayType())
       return ICEDiag(IK_NotICE, E->getBeginLoc());
     return NoDiag();
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 917402544d4f6..981cdb3c806b1 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5367,6 +5367,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
       MangleAlignofSizeofArg();
       break;
 
+    case UETT_CountOf:
     case UETT_VectorElements:
     case UETT_OpenMPRequiredSimdAlign:
     case UETT_VecStep:
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index eccdcdb497f84..e858de7a4f6d2 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -3477,7 +3477,7 @@ ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr(
                               const UnaryExprOrTypeTraitExpr *E) {
   QualType TypeToSize = E->getTypeOfArgument();
   if (auto Kind = E->getKind();
-      Kind == UETT_SizeOf || Kind == UETT_DataSizeOf) {
+      Kind == UETT_SizeOf || Kind == UETT_DataSizeOf || Kind == UETT_CountOf) {
     if (const VariableArrayType *VAT =
             CGF.getContext().getAsVariableArrayType(TypeToSize)) {
       if (E->isArgumentType()) {
@@ -3492,10 +3492,15 @@ ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr(
       auto VlaSize = CGF.getVLASize(VAT);
       llvm::Value *size = VlaSize.NumElts;
 
-      // Scale the number of non-VLA elements by the non-VLA element size.
-      CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type);
-      if (!eltSize.isOne())
-        size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size);
+      // For sizeof and __datasizeof, we need to scale the number of elements
+      // by the size of the array element type. For _Countof, we just want to
+      // return the size directly.
+      if (Kind != UETT_CountOf) {
+        // Scale the number of non-VLA elements by the non-VLA element size.
+        CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type);
+        if (!eltSize.isOne())
+          size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size);
+      }
 
       return size;
     }
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 0c28972d6ed8f..0a22f7372a9f9 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -904,6 +904,8 @@ ExprResult Parser::ParseBuiltinPtrauthTypeDiscriminator() {
 /// [GNU]   '__alignof' '(' type-name ')'
 /// [C11]   '_Alignof' '(' type-name ')'
 /// [C++11] 'alignof' '(' type-id ')'
+/// [C2y]   '_Countof' unary-expression
+/// [C2y]   '_Countof' '(' type-name ')'
 /// [GNU]   '&&' identifier
 /// [C++11] 'noexcept' '(' expression ')' [C++11 5.3.7]
 /// [C++]   new-expression
@@ -1544,6 +1546,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
   // unary-expression: '__builtin_omp_required_simd_align' '(' type-name ')'
   case tok::kw___builtin_omp_required_simd_align:
   case tok::kw___builtin_vectorelements:
+  case tok::kw__Countof:
     if (NotPrimaryExpression)
       *NotPrimaryExpression = true;
     AllowSuffix = false;
@@ -2463,7 +2466,7 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
                        tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof,
                        tok::kw__Alignof, tok::kw_vec_step,
                        tok::kw___builtin_omp_required_simd_align,
-                       tok::kw___builtin_vectorelements) &&
+                       tok::kw___builtin_vectorelements, tok::kw__Countof) &&
          "Not a typeof/sizeof/alignof/vec_step expression!");
 
   ExprResult Operand;
@@ -2510,9 +2513,9 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
     // is not going to help when the nesting is too deep. In this corner case
     // we continue to parse with sufficient stack space to avoid crashing.
     if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
-                      tok::kw_alignof, tok::kw__Alignof) &&
+                      tok::kw_alignof, tok::kw__Alignof, tok::kw__Countof) &&
         Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
-                    tok::kw_alignof, tok::kw__Alignof))
+                    tok::kw_alignof, tok::kw__Alignof, tok::kw__Countof))
       Actions.runWithSufficientStackSpace(Tok.getLocation(), [&] {
         Operand = ParseCastExpression(UnaryExprOnly);
       });
@@ -2594,12 +2597,14 @@ ExprResult Parser::ParseSYCLUniqueStableNameExpression() {
 /// [GNU]   '__alignof' '(' type-name ')'
 /// [C11]   '_Alignof' '(' type-name ')'
 /// [C++11] 'alignof' '(' type-id ')'
+/// [C2y]   '_Countof' unary-expression
+/// [C2y]   '_Countof' '(' type-name ')'
 /// \endverbatim
 ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
   assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
                      tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step,
                      tok::kw___builtin_omp_required_simd_align,
-                     tok::kw___builtin_vectorelements) &&
+                     tok::kw___builtin_vectorelements, tok::kw__Countof) &&
          "Not a sizeof/alignof/vec_step expression!");
   Token OpTok = Tok;
   ConsumeToken();
@@ -2656,6 +2661,8 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
     Diag(OpTok, diag::warn_cxx98_compat_alignof);
   else if (getLangOpts().C23 && OpTok.is(tok::kw_alignof))
     Diag(OpTok, diag::warn_c23_compat_keyword) << OpTok.getName();
+  else if (getLangOpts().C2y && OpTok.is(tok::kw__Countof))
+    Diag(OpTok, diag::warn_c2y_compat_keyword) << OpTok.getName();
 
   EnterExpressionEvaluationContext Unevaluated(
       Actions, Sema::ExpressionEvaluationContext::Unevaluated,
@@ -2690,6 +2697,12 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
   case tok::kw___builtin_vectorelements:
     ExprKind = UETT_VectorElements;
     break;
+  case tok::kw__Countof:
+    ExprKind = UETT_CountOf;
+    assert(!getLangOpts().CPlusPlus && "_Countof in C++ mode?");
+    if (!getLangOpts().C2y)
+      Diag(OpTok, diag::ext_c2y_feature) << OpTok.getName();
+    break;
   default:
     break;
   }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 3af6d6c23438f..f7554d90ee4c5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4266,7 +4266,7 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(Expr *E,
   bool IsUnevaluatedOperand =
       (ExprKind == UETT_SizeOf || ExprKind == UETT_DataSizeOf ||
        ExprKind == UETT_AlignOf || ExprKind == UETT_PreferredAlignOf ||
-       ExprKind == UETT_VecStep);
+       ExprKind == UETT_VecStep || ExprKind == UETT_CountOf);
   if (IsUnevaluatedOperand) {
     ExprResult Result = CheckUnevaluatedOperand(E);
     if (Result.isInvalid())
@@ -4338,6 +4338,21 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(Expr *E,
                                        E->getSourceRange(), ExprKind))
     return true;
 
+  if (ExprKind == UETT_CountOf) {
+    // The type has to be an array type. We already checked for incomplete
+    // types above.
+    QualType ExprType = E->IgnoreParens()->getType();
+    if (!ExprType->isArrayType()) {
+      Diag(E->getExprLoc(), diag::err_countof_arg_not_array_type) << ExprType;
+      return true;
+    }
+    // FIXME: warn on _Countof on an array parameter. Not warning on it
+    // currently because there are papers in WG14 about array types which do
+    // not decay that could impact this behavior, so we want to see if anything
+    // changes here before coming up with a warning group for _Countof-related
+    // diagnostics.
+  }
+
   if (ExprKind == UETT_SizeOf) {
     if (const auto *DeclRef = dyn_cast<DeclRefExpr>(E->IgnoreParens())) {
       if (const auto *PVD = dyn_cast<ParmVarDecl>(DeclRef->getFoundDecl())) {
@@ -4608,6 +4623,15 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType ExprType,
     return true;
   }
 
+  if (ExprKind == UETT_CountOf) {
+    // The type has to be an array type. We already checked for incomplete
+    // types above.
+    if (!ExprType->isArrayType()) {
+      Diag(OpLoc, diag::err_countof_arg_not_array_type) << ExprType;
+      return true;
+    }
+  }
+
   // WebAssembly tables are always illegal operands to unary expressions and
   // type traits.
   if (Context.getTargetInfo().getTriple().isWasm() &&
@@ -4666,7 +4690,8 @@ ExprResult Sema::CreateUnaryExprOrTypeTraitExpr(TypeSourceInfo *TInfo,
   // properly deal with VLAs in nested calls of sizeof and typeof.
   if (currentEvaluationContext().isUnevaluated() &&
       currentEvaluationContext().InConditionallyConstantEvaluateContext &&
-      ExprKind == UETT_SizeOf && TInfo->getType()->isVariablyModifiedType())
+      (ExprKind == UETT_SizeOf || ExprKind == UETT_CountOf) &&
+      TInfo->getType()->isVariablyModifiedType())
     TInfo = TransformToPotentiallyEvaluated(TInfo);
 
   // C99 6.5.3.4p4: the type (an unsigned integer type) is size_t.
@@ -4697,16 +4722,16 @@ Sema::CreateUnaryExprOrTypeTraitExpr(Expr *E, SourceLocation OpLoc,
   } else if (E->refersToBitField()) {  // C99 6.5.3.4p1.
     Diag(E->getExprLoc(), diag::err_sizeof_alignof_typeof_bitfield) << 0;
     isInvalid = true;
-  } else if (ExprKind == UETT_VectorElements) {
-    isInvalid = CheckUnaryExprOrTypeTraitOperand(E, UETT_VectorElements);
-  } else {
-    isInvalid = CheckUnaryExprOrTypeTraitOperand(E, UETT_SizeOf);
+  } else if (ExprKind == UETT_VectorElements || ExprKind == UETT_SizeOf ||
+             ExprKind == UETT_CountOf) { // FIXME: __datasizeof?
+    isInvalid = CheckUnaryExprOrTypeTraitOperand(E, ExprKind);
   }
 
   if (isInvalid)
     return ExprError();
 
-  if (ExprKind == UETT_SizeOf && E->getType()->isVariableArrayType()) {
+  if ((ExprKind == UETT_SizeOf || ExprKind == UETT_CountOf) &&
+      E->getType()->isVariableArrayType()) {
     PE = TransformToPotentiallyEvaluated(E);
     if (PE.isInvalid()) return ExprError();
     E = PE.get();
diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c
new file mode 100644
index 0000000000000..c199feb7f9d54
--- /dev/null
+++ b/clang/test/C/C2y/n3369.c
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c2y -pedantic -Wall -Wno-comment -verify %s
+
+/* WG14 N3369: Clang 21
+ * _Lengthof operator
+ *
+ * Adds an operator to get the length of an array. Note that WG14 N3469 renamed
+ * this operator to _Countof.
+ */
+
+int global_array[12];
+
+void test_parsing_failures() {
+  (void)_Countof;     // expected-error {{expected expression}}
+  (void)_Countof(;    // expected-error {{expected expression}}
+  (void)_Countof();   // expected-error {{expected expression}}
+  (void)_Countof int; // expected-error {{expected expression}}
+}
+
+void test_semantic_failures() {
+  (void)_Countof(1);         // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}}
+  int non_array;
+  (void)_Countof non_array;  // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}}  
+  (void)_Countof(int);       // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}}  
+}
+
+void test_constant_expression_behavior(int n) {
+  static_assert(_Countof(global_array) == 12);
+  static_assert(_Countof global_array == 12);  
+  static_assert(_Countof(int[12]) == 12);
+
+  // Use of a VLA makes it not a constant expression, same as with sizeof.
+  int array[n];
+  static_assert(_Countof(array)); // expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(sizeof(array));   // expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(_Countof(int[n]));// expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(sizeof(int[n]));  // expected-error {{static assertion expression is not an integral constant expression}}
+  
+  // Constant folding works the same way as sizeof, too.
+  const int m = 12;
+  int other_array[m];
+  static_assert(sizeof(other_array));   // expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(_Countof(other_array)); // expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(sizeof(int[m]));        // expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(_Countof(int[m]));      // expected-error {{static assertion expression is not an integral constant expression}}
+  
+  // Note that this applies to each array dimension.
+  int another_array[n][7];
+  static_assert(_Countof(another_array)); // expected-error {{static assertion expression is not an integral constant expression}}
+  static_assert(_Countof(*another_array) == 7);
+}
+
+void test_with_function_param(int array[12], int (*array_ptr)[12]) {
+  (void)_Countof(array); // expected-error {{'_Countof' requires an argument of array type; 'int *' invalid}}
+  static_assert(_Countof(*array_ptr) == 12);
+}
+
+void test_multidimensional_arrays() {
+  int array[12][7];
+  static_assert(_Countof(array) == 12);
+  static_assert(_Countof(*array) == 7);
+}
diff --git a/clang/test/C/C2y/n3369_1.c b/clang/test/C/C2y/n3369_1.c
new file mode 100644
index 0000000000000..b4e75151a5404
--- /dev/null
+++ b/clang/test/C/C2y/n3369_1.c
@@ -0,0 +1,25 @@
+/* RUN: %clang_cc1 -fsyntax-only -std=c2y -pedantic -Wpre-c2y-compat -verify=compat %s
+   RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -verify %s
+   RUN: %clang_cc1 -fsyntax-only -std=c89 -pedantic -verify=expected,static-assert %s
+   RUN: %clang_cc1 -fsyntax-only -pedantic -verify=cpp,static-assert -x c++ %s
+ */
+
+/* This tests the extension behavior for _Countof in language modes before C2y.
+ * It also tests the behavior of the precompat warning. And it tests the
+ * behavior in C++ mode where the extension is not supported.
+ */
+int array[12];
+int x = _Countof(array);   /* expected-warning {{'_Countof' is a C2y extension}}
+                              compat-warning {{'_Countof' is incompatible with C standards before C2y}}
+                              cpp-error {{use of undeclared identifier '_Countof'}}
+                            */
+int y = _Countof(int[12]); /* expected-warning {{'_Countof' is a C2y extension}}
+                              compat-warning {{'_Countof' is incompatible with C standards before C2y}}
+                              cpp-error {{expected '(' for function-style cast or type construction}}
+                            */
+
+_Static_assert(_Countof(int[12]) == 12, ""); /* expected-warning {{'_Countof' is a C2y extension}}
+                                                compat-warning {{'_Countof' is incompatible with C standards before C2y}}
+                                                cpp-error {{expected '(' for function-style cast or type construction}}
+                                                static-a...
[truncated]

Copy link

github-actions bot commented Mar 26, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 15f5a7a3ec71c624cea0cbdf02e3c5205ba81d9d b50ac64688338b7c21df9434c3cd5b075c3832ca --extensions cpp,h,c -- clang/test/C/C2y/n3369.c clang/test/C/C2y/n3369_1.c clang/test/C/C2y/n3369_2.c clang/test/C/C2y/n3469.c clang/include/clang/AST/Stmt.h clang/include/clang/AST/Type.h clang/lib/AST/ByteCode/Compiler.cpp clang/lib/AST/ExprConstant.cpp clang/lib/AST/ItaniumMangle.cpp clang/lib/CodeGen/CGExprScalar.cpp clang/lib/Parse/ParseExpr.cpp clang/lib/Sema/SemaExpr.cpp
View the diff from clang-format here.
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 0a22f7372a..14c9b59926 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -953,20 +953,18 @@ ExprResult Parser::ParseBuiltinPtrauthTypeDiscriminator() {
 /// [OBJC]  '\@encode' '(' type-name ')'
 /// [OBJC]  objc-string-literal
 /// [C++]   simple-type-specifier '(' expression-list[opt] ')'      [C++ 5.2.3]
-/// [C++11] simple-type-specifier braced-init-list                  [C++11 5.2.3]
-/// [C++]   typename-specifier '(' expression-list[opt] ')'         [C++ 5.2.3]
-/// [C++11] typename-specifier braced-init-list                     [C++11 5.2.3]
-/// [C++]   'const_cast' '<' type-name '>' '(' expression ')'       [C++ 5.2p1]
-/// [C++]   'dynamic_cast' '<' type-name '>' '(' expression ')'     [C++ 5.2p1]
-/// [C++]   'reinterpret_cast' '<' type-name '>' '(' expression ')' [C++ 5.2p1]
-/// [C++]   'static_cast' '<' type-name '>' '(' expression ')'      [C++ 5.2p1]
-/// [C++]   'typeid' '(' expression ')'                             [C++ 5.2p1]
-/// [C++]   'typeid' '(' type-id ')'                                [C++ 5.2p1]
-/// [C++]   'this'          [C++ 9.3.2]
-/// [G++]   unary-type-trait '(' type-id ')'
-/// [G++]   binary-type-trait '(' type-id ',' type-id ')'           [TODO]
-/// [EMBT]  array-type-trait '(' type-id ',' integer ')'
-/// [clang] '^' block-literal
+/// [C++11] simple-type-specifier braced-init-list [C++11 5.2.3] [C++]
+/// typename-specifier '(' expression-list[opt] ')'         [C++ 5.2.3] [C++11]
+/// typename-specifier braced-init-list                     [C++11 5.2.3] [C++]
+/// 'const_cast' '<' type-name '>' '(' expression ')'       [C++ 5.2p1] [C++]
+/// 'dynamic_cast' '<' type-name '>' '(' expression ')'     [C++ 5.2p1] [C++]
+/// 'reinterpret_cast' '<' type-name '>' '(' expression ')' [C++ 5.2p1] [C++]
+/// 'static_cast' '<' type-name '>' '(' expression ')'      [C++ 5.2p1] [C++]
+/// 'typeid' '(' expression ')'                             [C++ 5.2p1] [C++]
+/// 'typeid' '(' type-id ')'                                [C++ 5.2p1] [C++]
+/// 'this'          [C++ 9.3.2] [G++]   unary-type-trait '(' type-id ')' [G++]
+/// binary-type-trait '(' type-id ',' type-id ')'           [TODO] [EMBT]
+/// array-type-trait '(' type-id ',' integer ')' [clang] '^' block-literal
 ///
 ///       constant: [C99 6.4.4]
 ///         integer-constant

@jrtc27
Copy link
Collaborator

jrtc27 commented Mar 26, 2025

Although C++ might have good alternatives, that's not so helpful for C headers being included from C++ that might end up using this (e.g. some static inline function in a system header). (i.e. if it's not too much effort to do, I would encourage exposing it in C++)

@AaronBallman
Copy link
Collaborator Author

Although C++ might have good alternatives, that's not so helpful for C headers being included from C++ that might end up using this (e.g. some static inline function in a system header). (i.e. if it's not too much effort to do, I would encourage exposing it in C++)

We can contrive reasons to expose this in C++, and it would be a conforming extension there, but I'm still not convinced that's a good idea. Historically, many of the things we expose from C into C++ have some really rough edges we mostly ignore, like VLAs, _Complex vs std::complex, etc and I'd like to break that cycle by not exposing everything to both languages just because we can. I'm not strongly opposed in this case, but I'd still rather wait for a real world use case beyond "it could be used there" before we expose it to C++.

@AaronBallman
Copy link
Collaborator Author

Good catch on the VLA test cases @jyknight! It turns out we model VLAs very oddly in the type system. Given:

extern int n;
int array[7][n];

We make a VariableArrayType whose element type is a VariableArrayType rather than a ConstantArrayType of size 7 whose element type is a VariableArrayType. This made _Countof a little bit more complicated because we need to look through that design choice. I've implement that (in both ExprConstant.cpp and CGExprScalar.cpp), but we may someday want to do a deeper repair. Instead, I added a FIXME and a note in Type.h so others aren't surprised by this as well.

@jyknight
Copy link
Member

We make a VariableArrayType whose element type is a VariableArrayType rather than a ConstantArrayType of size 7 whose element type is a VariableArrayType.

Yea. Up till now I think there was no observable difference between the outer array having a constant or variable bound when the inner array is already a VLA, so it's perhaps not surprising it was modeled that way.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

Did WG14 considered some sort of macros for this feature?
Otherwise I'd be tempted to ask you to add something to __has_feature. I can see user wanting to use _Countof when available and fallback to something else when not.

@AaronBallman
Copy link
Collaborator Author

Did WG14 considered some sort of macros for this feature? Otherwise I'd be tempted to ask you to add something to __has_feature. I can see user wanting to use _Countof when available and fallback to something else when not.

There is weak consensus to add stdcountof.h which supplies a countof macro. However, there's no changes to the standard which add one (yet), so I figured that can be done in a follow-up.

@cor3ntin
Copy link
Contributor

There is weak consensus to add stdcountof.h which supplies a countof macro. However, there's no changes to the standard which add one (yet), so I figured that can be done in a follow-up.

Then I'd really like a feature.
We already have a bunch, It makes sense to add one for that imo

include/clang/Basic/Features.def:FEATURE(c_alignas, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_alignof, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_atomic, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_generic_selections, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_static_assert, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_thread_local, LangOpts.C11 &&PP.getTargetInfo().isTLSSupported())
include/clang/Basic/Features.def:FEATURE(c_fixed_enum, LangOpts.C23)

@AaronBallman
Copy link
Collaborator Author

There is weak consensus to add stdcountof.h which supplies a countof macro. However, there's no changes to the standard which add one (yet), so I figured that can be done in a follow-up.

Then I'd really like a feature. We already have a bunch, It makes sense to add one for that imo

include/clang/Basic/Features.def:FEATURE(c_alignas, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_alignof, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_atomic, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_generic_selections, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_static_assert, LangOpts.C11)
include/clang/Basic/Features.def:FEATURE(c_thread_local, LangOpts.C11 &&PP.getTargetInfo().isTLSSupported())
include/clang/Basic/Features.def:FEATURE(c_fixed_enum, LangOpts.C23)

So you want this to be a FEATURE in C2y and an EXTENSION in older language modes? And when we get the countof macro in a few months, this becomes unnecessary?

@cor3ntin
Copy link
Contributor

So you want this to be a FEATURE in C2y and an EXTENSION in older language modes?

Presumably yes.

And when we get the countof macro in a few months, this becomes unnecessary?

Are most users going to include stdcountof.h ?

My assumption is that they would not, so having the feature thingy is useful (especially in a library that may be concerned about not defining countof)

@AaronBallman
Copy link
Collaborator Author

So you want this to be a FEATURE in C2y and an EXTENSION in older language modes?

Presumably yes.

And when we get the countof macro in a few months, this becomes unnecessary?

Are most users going to include stdcountof.h ?

My assumption is that they would not, so having the feature thingy is useful (especially in a library that may be concerned about not defining countof)

Hmmm okay, that seems reasonable enough.

@AaronBallman
Copy link
Collaborator Author

So you want this to be a FEATURE in C2y and an EXTENSION in older language modes?

Presumably yes.

And when we get the countof macro in a few months, this becomes unnecessary?

Are most users going to include stdcountof.h ?
My assumption is that they would not, so having the feature thingy is useful (especially in a library that may be concerned about not defining countof)

Hmmm okay, that seems reasonable enough.

I've added that now.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

Thanks!

@AaronBallman AaronBallman merged commit 00c43ae into llvm:main Mar 27, 2025
11 of 12 checks passed
@AaronBallman AaronBallman deleted the aballman-wg14-n3369 branch March 27, 2025 17:23
@alejandro-colomar
Copy link

Thanks @AaronBallman !! I am still working on my patch, so just for learning about Clang internals, I'll try to finish mine, and compare it to yours. :-)

Sorry for being so slow!

Cheers,
Alex

@AaronBallman
Copy link
Collaborator Author

Thanks @AaronBallman !! I am still working on my patch, so just for learning about Clang internals, I'll try to finish mine, and compare it to yours. :-)

Sorry for stealing this one out from under you! It turns out it was a bit more involved than I expected because I didn't realize how we represented multidimensional VLAs in the type system.

Sorry for being so slow!

No worries! I wasn't certain if you had gotten busy or not, and I had some spare bandwidth and wanted to write code for a bit.

@alejandro-colomar
Copy link

alejandro-colomar commented Mar 27, 2025

Thanks @AaronBallman !! I am still working on my patch, so just for learning about Clang internals, I'll try to finish mine, and compare it to yours. :-)

Sorry for stealing this one out from under you!

No problem. :-)

It turns out it was a bit more involved than I expected because I didn't realize how we represented multidimensional VLAs in the type system.

Yep, the GCC patches were also non-trivial; at least for some corner cases. I had to wait for Martin to patch something unrelated to make it work.

BTW, I'll check all the tests I wrote for my implementation, and contribute anything that might be missing here (if any).

Sorry for being so slow!

No worries! I wasn't certain if you had gotten busy or not, and I had some spare bandwidth and wanted to write code for a bit.

:)

alejandro-colomar added a commit to alejandro-colomar/llvm-project that referenced this pull request Mar 27, 2025
Link: <llvm#102836>
Link: <llvm#133125>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
@alejandro-colomar

This comment was marked as resolved.

alejandro-colomar added a commit to alejandro-colomar/llvm-project that referenced this pull request Mar 28, 2025
Link: <llvm#102836>
Link: <llvm#133125>
Cc: Aaron Ballman <aaron@aaronballman.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
alejandro-colomar added a commit to alejandro-colomar/llvm-project that referenced this pull request Mar 28, 2025
Link: <llvm#102836>
Link: <llvm#133125>
Cc: Aaron Ballman <aaron@aaronballman.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c2y clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category extension:clang
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement N3369 (_Lengthof)
8 participants