Repository: mesonbuild/meson

Issue:
[regression] String formatting has regressed since 0.60. ERROR: Format placeholder @9@ out of range.
**Describe the bug**
There is a regression of as yet unknown cause in Meson's string formatting, as discovered by @alex3kov and diagnosed by myself at libsixel/libsixel#50. This regression causes previously building package to no longer build.

**To Reproduce**
https://github.com/libsixel/libsixel/blob/v1.10.3/meson.build

**Expected behavior**
Good build

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)?
    Plain build.
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.)
    Arch Linux
* what Python version are you using e.g. 3.8.0
    Python 3.9.7
* what `meson --version`
    Versions above 0.59.2 I confirm are affected.
* what `ninja --version` if it's a Ninja build
    We do not get to Ninja step, Meson is the cause.


Discussion:
0.59.3 works for me. 0.60.0 and 0.60.1 do not.
https://github.com/libsixel/libsixel/commit/803951016a3193160ddc1f12a3c15fe13e0728fb

pkg_config_path is a null list, and should never have been there to begin with (it makes no sense).

In this case it seems that it's being flattened?

Somewhat predictably, this bisects to commit d93d01b6c5c98a77e057b914e8d66b01d2a10771 (#9207).

Simple test case:

```meson
project('issue 9530')

message('foo list: @0@'.format([]))
```

In meson 0.59.4, this prints:

```
Message: foo list: []
```

It is of debatable usefulness, but what about this?

```meson
project('issue 9530')

message('foo list: @0@, bar list: @1@'.format(['--foo', 'barvalue'], ['--bar', 'barvalue']))
```

0.59.4 output:
```
Message: foo list: ['--foo', 'foovalue'], bar list: ['--bar', 'barvalue']
```

0.60.1 output:
```
Message: foo list: --foo, bar list: foovalue
```
IMHO the absolute last place we should *ever* be doing list flattening is in .format() and message().

Resolving patch:
diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py
--- a/mesonbuild/interpreter/primitives/string.py
+++ b/mesonbuild/interpreter/primitives/string.py
@@ -13,6 +13,7 @@
     MesonOperator,
     FeatureNew,
     typed_operator,
+    noArgsFlattening,
     noKwargs,
     noPosargs,
     typed_pos_args,
@@ -85,6 +86,7 @@ def startswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
     def endswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
         return self.held_object.endswith(args[0])
 
+    @noArgsFlattening
     @noKwargs
     @typed_pos_args('str.format', varargs=object)
     def format_method(self, args: T.Tuple[T.List[object]], kwargs: TYPE_kwargs) -> str:


Test patch:
diff --git a/test cases/common/35 string operations/meson.build b/test cases/common/35 string operations/meson.build
--- a/test cases/common/35 string operations/meson.build	
+++ b/test cases/common/35 string operations/meson.build	
@@ -60,6 +60,7 @@ assert(false.to_string() == 'false', 'bool string conversion failed')
 assert(true.to_string('yes', 'no') == 'yes', 'bool string conversion with args failed')
 assert(false.to_string('yes', 'no') == 'no', 'bool string conversion with args failed')
 assert('@0@'.format(true) == 'true', 'bool string formatting failed')
+assert('@0@'.format(['one', 'two']) == '[\'one\', \'two\']', 'list string formatting failed')
 
 assert(' '.join(['a', 'b', 'c']) == 'a b c', 'join() array broken')
 assert(''.join(['a', 'b', 'c']) == 'abc', 'empty join() broken')