Repository: mesonbuild/meson

Issue:
Missing feature warning < 0.60: concatenating list with custom target
Hi,

in <0.60 it was not possible to do [list, of, targets] + custom_target. But in 0.60 that is possible. 

Repro:
```
project('repro', ['c'],
  version : '0.1'
)

foo = custom_target('foo',
  output: 'foo',
  command: ['touch', '@OUTPUT@']
)

sometargets = [] + foo
```
HEAD:
```
$ ~/src/meson/meson.py setup build-master --reconfigure
The Meson build system
Version: 0.60.0.rc1
Source dir: /tmp/repro-meson-src
Build dir: /tmp/repro-meson-src/build-master
Build type: native build
Project name: repro
Project version: 0.1
C compiler for the host machine: ccache cc (gcc 11.2.0 "cc (Debian 11.2.0-9) 11.2.0")
C linker for the host machine: cc ld.bfd 2.37
Host machine cpu family: x86_64
Host machine cpu: x86_64
Program touch found: YES (/usr/bin/touch)
Build targets in project: 1

Found ninja-1.10.1 at /usr/bin/ninja
```

0.59.2:
```
~/src/meson-0.59/meson.py setup build-0.59
The Meson build system
Version: 0.59.2
Source dir: /tmp/repro-meson-src
Build dir: /tmp/repro-meson-src/build-0.59
Build type: native build
Project name: repro
Project version: 0.1
C compiler for the host machine: ccache cc (gcc 11.2.0 "cc (Debian 11.2.0-10) 11.2.0")
C linker for the host machine: cc ld.bfd 2.37
Host machine cpu family: x86_64
Host machine cpu: x86_64
Program touch found: YES (/usr/bin/touch)

meson.build:10:0: ERROR: Invalid use of addition: can only concatenate list (not "CustomTargetHolder") to list

```

Regards,

Andres


Discussion:
Bisected to af0587cb490dfa2c2a00c2426c10d1de11d34fdd, and I'm not sure this makes sense to support -- it certainly wasn't announced as a feature.
Seems consistent with
```
a = []
a += custom_target()
```
that has always been supported AFAIK.
For consistency I would prefere to add a FeatueNew warning. And yes, I wasn't aware that this introduced another behavior change.

On 22 October 2021 02:05:11 CEST, Xavier Claessens ***@***.***> wrote:
>Seems consistent with
>```
>a = []
>a += custom_target()
>```
>that has always been supported AFAIK.
>
>-- 
>You are receiving this because you were assigned.
>Reply to this email directly or view it on GitHub:
>https://github.com/mesonbuild/meson/issues/9425#issuecomment-949089693
-- 
Sent from my Android device with K-9 Mail. Please excuse my brevity.
-- 
Sent from my Android device with K-9 Mail. Please excuse my brevity.
@mensinda do you have an idea where we can detect that case? Would be nice to add FeatureNew before 0.60 release.
I am working on it, but it is likely going to be a bad hack if I can can get the warning to work :(

Resolving patch:
diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py
--- a/mesonbuild/interpreter/primitives/array.py
+++ b/mesonbuild/interpreter/primitives/array.py
@@ -12,12 +12,14 @@
     noPosargs,
     noArgsFlattening,
     typed_pos_args,
+    FeatureNew,
 
     TYPE_var,
     TYPE_kwargs,
 
     InvalidArguments,
 )
+from ...mparser import PlusAssignmentNode
 
 if T.TYPE_CHECKING:
     # Object holders need the actual interpreter
@@ -92,6 +94,8 @@ def get_method(self, args: T.Tuple[int, T.Optional[TYPE_var]], kwargs: TYPE_kwar
     @typed_operator(MesonOperator.PLUS, object)
     def op_plus(self, other: TYPE_var) -> T.List[TYPE_var]:
         if not isinstance(other, list):
+            if not isinstance(self.current_node, PlusAssignmentNode):
+                FeatureNew.single_use('list.<plus>', '0.60.0', self.subproject, 'The right hand operand was not a list.')
             other = [other]
         return self.held_object + other
 
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -303,6 +303,7 @@ def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject
         if operator in (MesonOperator.IN, MesonOperator.NOT_IN):
             val1, val2 = val2, val1
 
+        val1.current_node = node
         return self._holderify(val1.operator_call(operator, _unholder(val2)))
 
     def evaluate_andstatement(self, cur: mparser.AndNode) -> InterpreterObject:
@@ -333,6 +334,7 @@ def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> InterpreterObject
         v = self.evaluate_statement(cur.value)
         if isinstance(v, Disabler):
             return v
+        v.current_node = cur
         return self._holderify(v.operator_call(MesonOperator.UMINUS, None))
 
     def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> InterpreterObject:
@@ -350,6 +352,7 @@ def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> Interpret
             'div': MesonOperator.DIV,
             'mod': MesonOperator.MOD,
         }
+        l.current_node = cur
         res = l.operator_call(mapping[cur.operation], _unholder(r))
         return self._holderify(res)
 
@@ -358,6 +361,7 @@ def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterO
         result = self.evaluate_statement(node.condition)
         if isinstance(result, Disabler):
             return result
+        result.current_node = node
         result_bool = result.operator_call(MesonOperator.BOOL, None)
         if result_bool:
             return self.evaluate_statement(node.trueblock)
@@ -420,6 +424,7 @@ def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None:
         # Remember that all variables are immutable. We must always create a
         # full new variable and then assign it.
         old_variable = self.get_variable(varname)
+        old_variable.current_node = node
         new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition)))
         self.set_variable(varname, new_value)
 
@@ -432,6 +437,7 @@ def evaluate_indexing(self, node: mparser.IndexNode) -> InterpreterObject:
 
         if iobject is None:
             raise InterpreterException('Tried to evaluate indexing on None')
+        iobject.current_node = node
         return self._holderify(iobject.operator_call(MesonOperator.INDEX, index))
 
     def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObject]:


Test patch:
diff --git a/test cases/warning/6 list add/meson.build b/test cases/warning/6 list add/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/warning/6 list add/meson.build	
@@ -0,0 +1,7 @@
+project('test list add', meson_version: '>=0.59.0')
+
+l1 = [1, 2, 3]
+l2 = l1 + 4
+l2 += 5
+
+message(l2)
diff --git a/test cases/warning/6 list add/test.json b/test cases/warning/6 list add/test.json
new file mode 100644
--- /dev/null
+++ b/test cases/warning/6 list add/test.json	
@@ -0,0 +1,7 @@
+{
+  "stdout": [
+    {
+      "line": "WARNING: Project targeting '>=0.59.0' but tried to use feature introduced in '0.60.0': list.<plus>. The right hand operand was not a list."
+    }
+  ]
+}