[clang] [Clang] [Sema] Improve handling of multidimensional subscript operator for builtin types (PR #187828)

via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 25 08:18:50 PDT 2026


https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/187828

>From 906630f5dcaacdcac69d3976018812126a250b1a Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 21 Mar 2026 01:16:51 +0100
Subject: [PATCH 1/3] [Clang] [Sema] Improve handling of multidimensional
 subscript operator for builtin types

---
 clang/docs/ReleaseNotes.rst                   |  1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/lib/Sema/SemaExpr.cpp                   | 40 +++++++---
 clang/test/OpenMP/target_update_messages.cpp  |  4 +-
 .../test/SemaCXX/cxx23-builtin-subscript.cpp  | 77 +++++++++++++++++++
 .../SemaCXX/cxx2b-overloaded-operator.cpp     |  2 +-
 6 files changed, 113 insertions(+), 13 deletions(-)
 create mode 100644 clang/test/SemaCXX/cxx23-builtin-subscript.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b13171f7ecafc..f6bc428d5f30b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -339,6 +339,7 @@ Bug Fixes in This Version
 - Fixed a crash when normalizing constraints involving concept template parameters whose index coincided with non-concept template parameters in the same parameter mapping.
 - Fixed a crash caused by accessing dependent diagnostics of a non-dependent context.
 - Fixed a crash when substituting into a non-type template parameter that has a type containing an undeduced placeholder type.
+- Fixed several crashes and improved diagnostics when a multidimensional subscript operator is applied to a built-in type. (#GH187800)
 
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d4d09a8ecef36..c0527db7da27d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5425,6 +5425,8 @@ def err_ovl_no_viable_subscript :
     Error<"no viable overloaded operator[] for type %0">;
 def err_ovl_no_oper :
     Error<"type %0 does not provide a %select{subscript|call}1 operator">;
+def err_ovl_builtin_subscript_expects_single_arg : Error<
+  "built-in subscript operator for type %0 expects exactly one argument">;
 def err_ovl_unresolvable : Error<
   "reference to %select{overloaded|multiversioned}1 function could not be "
   "resolved; did you mean to call it%select{| with no arguments}0?">;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2b0a786302aab..30fd2d91ca343 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5040,8 +5040,11 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
   //
   // Helper to check for comma expressions, which are not allowed as indices for
   // matrix subscript expressions.
-  auto CheckAndReportCommaError = [this, base, rbLoc](Expr *E) {
-    if (isa<BinaryOperator>(E) && cast<BinaryOperator>(E)->isCommaOp()) {
+  //
+  // In C++23, we get multiple arguments instead of a comma expression.
+  auto CheckAndReportCommaError = [&](Expr *E) {
+    if (ArgExprs.size() > 1 ||
+        (isa<BinaryOperator>(E) && cast<BinaryOperator>(E)->isCommaOp())) {
       Diag(E->getExprLoc(), diag::err_matrix_subscript_comma)
           << SourceRange(base->getBeginLoc(), rbLoc);
       return true;
@@ -5061,7 +5064,6 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
   // MatrixSubscriptExpr.
   auto *matSubscriptE = dyn_cast<MatrixSubscriptExpr>(base);
   if (matSubscriptE) {
-    assert(ArgExprs.size() == 1);
     if (CheckAndReportCommaError(ArgExprs.front()))
       return ExprError();
 
@@ -5098,7 +5100,6 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
 
   // If the base is a matrix type, try to create a new MatrixSubscriptExpr.
   if (base->getType()->isMatrixType()) {
-    assert(ArgExprs.size() == 1);
     if (CheckAndReportCommaError(ArgExprs.front()))
       return ExprError();
 
@@ -5146,13 +5147,20 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
   // The above statement indicates that x[] can be used with one or more array
   // indices. In this case, i=p->x[a][b] will be turned into i=p->GetX(a, b),
   // and p->x[a][b] = i will be turned into p->PutX(a, b, i);
+  //
+  // Since C++23, MSVC accepts multiple arguments; e.g. given
+  //
+  //   struct S {
+  //     int get_x(int, int);
+  //     __declspec(property(get=get_x)) int x[][];
+  //   };
+  //
+  // MSVC accepts both 'S().x[1][2]' and 'S().x[1, 2]'.
   if (IsMSPropertySubscript) {
-    assert(ArgExprs.size() == 1);
-    // Build MS property subscript expression if base is MS property reference
-    // or MS property subscript.
-    return new (Context)
-        MSPropertySubscriptExpr(base, ArgExprs.front(), Context.PseudoObjectTy,
-                                VK_LValue, OK_Ordinary, rbLoc);
+    for (Expr *Arg : ArgExprs)
+      base = new (Context) MSPropertySubscriptExpr(
+          base, Arg, Context.PseudoObjectTy, VK_LValue, OK_Ordinary, rbLoc);
+    return base;
   }
 
   // Use C++ overloaded-operator rules if either operand has record
@@ -5163,6 +5171,18 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
   //
   // ObjC pointers have their own subscripting logic that is not tied
   // to overload resolution and so should not take this path.
+  //
+  // Issue a better diagnostic if we tried to pass multiple arguments to
+  // a builtin subscript operator rather than diagnosing this as a generic
+  // overload resolution failure.
+  if (ArgExprs.size() != 1 &&
+      !base->getType()->isRecordType() &&
+      !base->getType()->isObjCObjectPointerType()) {
+    Diag(base->getExprLoc(), diag::err_ovl_builtin_subscript_expects_single_arg)
+        << base->getType() << base->getSourceRange();
+    return ExprError();
+  }
+
   if (getLangOpts().CPlusPlus && !base->getType()->isObjCObjectPointerType() &&
       ((base->getType()->isRecordType() ||
         (ArgExprs.size() != 1 || isa<PackExpansionExpr>(ArgExprs[0]) ||
diff --git a/clang/test/OpenMP/target_update_messages.cpp b/clang/test/OpenMP/target_update_messages.cpp
index 000cc80e513e6..1f6ecdbf344cb 100644
--- a/clang/test/OpenMP/target_update_messages.cpp
+++ b/clang/test/OpenMP/target_update_messages.cpp
@@ -241,7 +241,7 @@ void f() {
 
 #pragma omp target update to(test[1])
 
-#pragma omp target update to(test[1, 2]) // cxx23-error {{type 'int[10]' does not provide a subscript operator}} \
+#pragma omp target update to(test[1, 2]) // cxx23-error {{built-in subscript operator for type 'int[10]' expects exactly one argument}} \
                                          // cxx23-error {{expected at least one 'to' clause or 'from' clause specified to '#pragma omp target update'}}
 
 #pragma omp target update to(test [1:1:1])
@@ -255,7 +255,7 @@ void f() {
 #pragma omp target update to(test[1, 2 ::]) // cxx23-error {{expected ']'}} // expected-note {{'['}} \
                                             // cxx23-error {{expected at least one 'to' clause or 'from' clause specified to '#pragma omp target update'}}
 
-#pragma omp target update to(test[]) // cxx23-error {{type 'int[10]' does not provide a subscript operator}} \
+#pragma omp target update to(test[]) // cxx23-error {{built-in subscript operator for type 'int[10]' expects exactly one argument}} \
                                             // cxx23-error {{expected at least one 'to' clause or 'from' clause specified to '#pragma omp target update'}}
   S s;
   (void)s[0];
diff --git a/clang/test/SemaCXX/cxx23-builtin-subscript.cpp b/clang/test/SemaCXX/cxx23-builtin-subscript.cpp
new file mode 100644
index 0000000000000..e85e43443f314
--- /dev/null
+++ b/clang/test/SemaCXX/cxx23-builtin-subscript.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -std=c++23 -verify -fenable-matrix -fdeclspec %s
+
+union U {};
+struct S {};
+enum E {};
+enum class EC {};
+
+using vec3 = int __attribute__((ext_vector_type(3)));
+using mat3 = int __attribute__((matrix_type(3, 3)));
+
+void f(int *p) {
+  int a[4]{};
+  vec3 v{};
+  mat3 m;
+  p[1, 2]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  p[1, p]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  1[1, 2]; // expected-error {{built-in subscript operator for type 'int' expects exactly one argument}}
+  1[p, 2]; // expected-error {{built-in subscript operator for type 'int' expects exactly one argument}}
+  1[p, 2]; // expected-error {{built-in subscript operator for type 'int' expects exactly one argument}}
+  p[U{}, U{}]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  p[E{}, 1]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  p[EC{}, 1]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  p[S{}, 1]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  p[1u, 1l]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  p[1, 2, 3]; // expected-error {{built-in subscript operator for type 'int *' expects exactly one argument}}
+  a[1, 2]; // expected-error {{built-in subscript operator for type 'int[4]' expects exactly one argument}}
+  a[1, p]; // expected-error {{built-in subscript operator for type 'int[4]' expects exactly one argument}}
+  a[S{}, p]; // expected-error {{built-in subscript operator for type 'int[4]' expects exactly one argument}}
+  a[1, 2, 3]; // expected-error {{built-in subscript operator for type 'int[4]' expects exactly one argument}}
+  v[1, 2]; // expected-error {{built-in subscript operator for type 'vec3' (vector of 3 'int' values) expects exactly one argument}}
+  v[1, p]; // expected-error {{built-in subscript operator for type 'vec3' (vector of 3 'int' values) expects exactly one argument}}
+  v[S{}, p]; // expected-error {{built-in subscript operator for type 'vec3' (vector of 3 'int' values) expects exactly one argument}}
+  v[1, 2, 3]; // expected-error {{built-in subscript operator for type 'vec3' (vector of 3 'int' values) expects exactly one argument}}
+  E{}[2, 2]; // expected-error {{built-in subscript operator for type 'E' expects exactly one argument}}
+  EC{}[2, 2]; // expected-error {{built-in subscript operator for type 'EC' expects exactly one argument}}
+
+  m[1][3, 4]; // expected-error {{comma expressions are not allowed as indices in matrix subscript}}
+  m[1][2, 3]; // expected-error {{comma expressions are not allowed as indices in matrix subscript}}
+  m[1, 2][3, 4]; // expected-error {{comma expressions are not allowed as indices in matrix subscript}}
+
+  U{}[2, 2]; // expected-error {{type 'U' does not provide a subscript operator}}
+  S{}[2, 2]; // expected-error {{type 'S' does not provide a subscript operator}}
+}
+
+struct Prop {
+  int val = 0;
+
+  constexpr int get_two(int a, int b) { return a + b; }
+  constexpr int get_three(int a, int b, int c) { return a + b + c; }
+
+  constexpr void put_two(int a, int b, int c) { val = a + b + c; }
+  constexpr void put_three(int a, int b, int c, int d) { val = a + b + c + d; }
+
+  __declspec(property(get = get_two, put = put_two)) int two[][];
+  __declspec(property(get = get_three, put = put_three)) int three[][][];
+};
+
+static_assert(Prop().two[1, 2] == 3);
+static_assert(Prop().three[1, 2, 3] == 6);
+static_assert(Prop().three[1, 2][3] == 6);
+static_assert(Prop().three[1][2, 3] == 6);
+
+constexpr int p1() {
+  Prop p;
+  p.two[1, 2] = 3;
+  return p.val;
+}
+
+static_assert(p1() == 6);
+
+constexpr int p2() {
+  Prop p;
+  p.three[1, 2, 3] = 4;
+  return p.val;
+}
+
+static_assert(p2() == 10);
diff --git a/clang/test/SemaCXX/cxx2b-overloaded-operator.cpp b/clang/test/SemaCXX/cxx2b-overloaded-operator.cpp
index c734b82987083..2ca72c8d39cc2 100644
--- a/clang/test/SemaCXX/cxx2b-overloaded-operator.cpp
+++ b/clang/test/SemaCXX/cxx2b-overloaded-operator.cpp
@@ -98,7 +98,7 @@ int cxx_subscript_unexpanded() {
 template<int... Is>
 constexpr int c_array() {
   int arr[] = {1, 2, 3};
-  return arr[Is...]; // expected-error 2{{type 'int[3]' does not provide a subscript operator}}
+  return arr[Is...]; // expected-error 2{{built-in subscript operator for type 'int[3]' expects exactly one argument}}
 }
 
 template<int... Is>

>From 95f9aa96f14ea3b701d7ec979446bff4e785bac8 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 21 Mar 2026 01:28:42 +0100
Subject: [PATCH 2/3] clang-format

---
 clang/lib/Sema/SemaExpr.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 30fd2d91ca343..344c3d5051c8c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5175,8 +5175,7 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
   // Issue a better diagnostic if we tried to pass multiple arguments to
   // a builtin subscript operator rather than diagnosing this as a generic
   // overload resolution failure.
-  if (ArgExprs.size() != 1 &&
-      !base->getType()->isRecordType() &&
+  if (ArgExprs.size() != 1 && !base->getType()->isRecordType() &&
       !base->getType()->isObjCObjectPointerType()) {
     Diag(base->getExprLoc(), diag::err_ovl_builtin_subscript_expects_single_arg)
         << base->getType() << base->getSourceRange();

>From cf3f2582c781ac60bea2ae69a3c70932c5683e3c Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Tue, 24 Mar 2026 19:14:35 +0100
Subject: [PATCH 3/3] Disallow multidimensional property subscripts for now

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/lib/Sema/SemaExpr.cpp                   | 24 ++++++------
 .../test/SemaCXX/cxx23-builtin-subscript.cpp  | 39 +++++++------------
 3 files changed, 26 insertions(+), 39 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c0527db7da27d..41d806f70e534 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5427,6 +5427,8 @@ def err_ovl_no_oper :
     Error<"type %0 does not provide a %select{subscript|call}1 operator">;
 def err_ovl_builtin_subscript_expects_single_arg : Error<
   "built-in subscript operator for type %0 expects exactly one argument">;
+def err_ms_property_subscript_expects_single_arg : Error<
+  "property subscript expects exactly one argument">;
 def err_ovl_unresolvable : Error<
   "reference to %select{overloaded|multiversioned}1 function could not be "
   "resolved; did you mean to call it%select{| with no arguments}0?">;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 344c3d5051c8c..11f0645b74291 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5147,20 +5147,18 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base,
   // The above statement indicates that x[] can be used with one or more array
   // indices. In this case, i=p->x[a][b] will be turned into i=p->GetX(a, b),
   // and p->x[a][b] = i will be turned into p->PutX(a, b, i);
-  //
-  // Since C++23, MSVC accepts multiple arguments; e.g. given
-  //
-  //   struct S {
-  //     int get_x(int, int);
-  //     __declspec(property(get=get_x)) int x[][];
-  //   };
-  //
-  // MSVC accepts both 'S().x[1][2]' and 'S().x[1, 2]'.
   if (IsMSPropertySubscript) {
-    for (Expr *Arg : ArgExprs)
-      base = new (Context) MSPropertySubscriptExpr(
-          base, Arg, Context.PseudoObjectTy, VK_LValue, OK_Ordinary, rbLoc);
-    return base;
+    if (ArgExprs.size() > 1) {
+      Diag(base->getExprLoc(),
+           diag::err_ms_property_subscript_expects_single_arg);
+      return ExprError();
+    }
+
+    // Build MS property subscript expression if base is MS property reference
+    // or MS property subscript.
+    return new (Context)
+        MSPropertySubscriptExpr(base, ArgExprs.front(), Context.PseudoObjectTy,
+                                VK_LValue, OK_Ordinary, rbLoc);
   }
 
   // Use C++ overloaded-operator rules if either operand has record
diff --git a/clang/test/SemaCXX/cxx23-builtin-subscript.cpp b/clang/test/SemaCXX/cxx23-builtin-subscript.cpp
index e85e43443f314..8067663ded676 100644
--- a/clang/test/SemaCXX/cxx23-builtin-subscript.cpp
+++ b/clang/test/SemaCXX/cxx23-builtin-subscript.cpp
@@ -43,35 +43,22 @@ void f(int *p) {
 }
 
 struct Prop {
-  int val = 0;
-
-  constexpr int get_two(int a, int b) { return a + b; }
-  constexpr int get_three(int a, int b, int c) { return a + b + c; }
-
-  constexpr void put_two(int a, int b, int c) { val = a + b + c; }
-  constexpr void put_three(int a, int b, int c, int d) { val = a + b + c + d; }
-
+  constexpr int get_two(int a, int b);
+  constexpr int get_three(int a, int b, int c);
+  constexpr void put_two(int a, int b, int c);
+  constexpr void put_three(int a, int b, int c, int d);
   __declspec(property(get = get_two, put = put_two)) int two[][];
   __declspec(property(get = get_three, put = put_three)) int three[][][];
 };
 
-static_assert(Prop().two[1, 2] == 3);
-static_assert(Prop().three[1, 2, 3] == 6);
-static_assert(Prop().three[1, 2][3] == 6);
-static_assert(Prop().three[1][2, 3] == 6);
-
-constexpr int p1() {
+void f() {
   Prop p;
-  p.two[1, 2] = 3;
-  return p.val;
+  p.two[1, 2]; // expected-error {{property subscript expects exactly one argument}}
+  p.three[1, 2, 3]; // expected-error {{property subscript expects exactly one argument}}
+  p.three[1, 2][3]; // expected-error {{property subscript expects exactly one argument}}
+  p.three[1][2, 3]; // expected-error {{property subscript expects exactly one argument}}
+  p.two[1, 2] = 3; // expected-error {{property subscript expects exactly one argument}}
+  p.three[1, 2, 3] = 4; // expected-error {{property subscript expects exactly one argument}}
+  p.three[1, 2][3] = 4; // expected-error {{property subscript expects exactly one argument}}
+  p.three[1][2, 3] = 4; // expected-error {{property subscript expects exactly one argument}}
 }
-
-static_assert(p1() == 6);
-
-constexpr int p2() {
-  Prop p;
-  p.three[1, 2, 3] = 4;
-  return p.val;
-}
-
-static_assert(p2() == 10);



More information about the cfe-commits mailing list