[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