Repository: hsutter/cppfront (C++)

Issue:
[BUG] No support for multi-argument indexing
<a id="title"/>

**[T](#title)itle**: No support for multi-argument indexing.

<a id="desc"/>

**[D](#desc)escription**:

An expression like `x[y,z]`
lowers to `cpp2::assert_in_bounds(x, y, z)`,
but has an arity of 2.

<a id="rep"/>
<a id="cpp2"/>

**[M](#rep)inimal reproducer** (<https://cpp2.godbolt.org/z/TPoGe1czc>):

```Cpp2
t: type = {
  operator[]: (inout this, x: _, y: _) -> i32 = 42;
}
main: () = {
  [[assert: t()[1, 2] == 42]]
}
```

<a id="cmd"/>
<details><summary>
<b><a href="#cmd">C</a>ommands</b>:
</summary>

```bash
cppfront -clean-cpp1 main.cpp2
clang++17 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -I . main.cpp
```
</details>

<a id="expected"/>

**[E](#expected)xpected result**:

It doesn't seem trivial to support multi-argument indexing in `cpp2::assert_in_bounds`.
See <https://en.cppreference.com/w/cpp/container/mdspan/operator_at>.

```C++
  cpp2::Default.expects(t()[1, 2]==42, "");
```

<a id="actual"/>

**[A](#actual)ctual result and error**:

```C++
  cpp2::Default.expects(cpp2::assert_in_bounds(t(), 1, 2)==42, "");
```

<a id="cpp1"/>
<details><summary>
<a href="#cpp1">C</a>pp2 lowered to Cpp1.
</summary>

```C++


#include "cpp2util.h"

class t;
  
class t {
  public: [[nodiscard]] auto operator[](auto const& x, auto const& y) -> cpp2::i32;

  public: t() = default;
  public: t(t const&) = delete; /* No 'that' constructor, suppress copy */
  public: auto operator=(t const&) -> void = delete;
};
auto main() -> int;
  

  [[nodiscard]] auto t::operator[](auto const& x, auto const& y) -> cpp2::i32 { return 42;  }

auto main() -> int{
  cpp2::Default.expects(cpp2::assert_in_bounds(t(), 1, 2)==42, "");
}
```
</details>

<a id="out"/>
<details><summary>
<a href="#out">O</a>utput.
</summary>

```output
build/_cppfront/main.cpp:20:25: error: no matching function for call to 'assert_in_bounds'
  cpp2::Default.expects(cpp2::assert_in_bounds(t(), 1, 2)==42, "");
                        ^~~~~~~~~~~~~~~~~~~~~~
raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:399:6: note: candidate function template not viable: requires 2 arguments, but 3 were provided
auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
     ^
raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:407:6: note: candidate function template not viable: requires 2 arguments, but 3 were provided
auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
     ^
```
</details>




Fix (patch):
diff --git a/source/cppfront.cpp b/source/cppfront.cpp
index 1bad85a53..0c12673e5 100644
--- a/source/cppfront.cpp
+++ b/source/cppfront.cpp
@@ -3029,6 +3029,7 @@ class cppfront
                 if (
                     flag_safe_subscripts
                     && i->op->type() == lexeme::LeftBracket
+                    && std::ssize(i->expr_list->expressions) == 1
                     )
                 {
                     suffix.emplace_back( ")", i->op->position() );
@@ -3065,6 +3066,7 @@ class cppfront
                 if (
                     flag_safe_subscripts
                     && i->op->type() == lexeme::LeftBracket
+                    && std::ssize(i->expr_list->expressions) == 1
                     )
                 {
                     prefix.emplace_back( "cpp2::assert_in_bounds(", i->op->position() );
diff --git a/source/parse.h b/source/parse.h
index 3a4e1ae3b..66a730de7 100644
--- a/source/parse.h
+++ b/source/parse.h
@@ -4084,7 +4084,7 @@ class parser
     //G postfix-expression:
     //G     primary-expression
     //G     postfix-expression postfix-operator     [Note: without whitespace before the operator]
-    //G     postfix-expression '[' expression-list ']'
+    //G     postfix-expression '[' expression-list? ']'
     //G     postfix-expression '(' expression-list? ')'
     //G     postfix-expression '.' id-expression
     //G
@@ -4161,13 +4161,9 @@ class parser
             if (term.op->type() == lexeme::LeftBracket)
             {
                 term.expr_list = expression_list(term.op);
-                if (
-                    !term.expr_list
-                    || term.expr_list->expressions.empty()
-                    )
+                if (!term.expr_list)
                 {
-                    error("subscript expression [ ] must not be empty (if you were trying to name a C-style array type, use 'std::array' instead)");
-                    next();
+                    error("[ is not followed by a valid expression list");
                     return {};
                 }
                 if (curr().type() != lexeme::RightBracket)