Repository: mesonbuild/meson

Issue:
contains() no longer works with lists of file objects
**Describe the bug**

With 0.59.0, `[].contains()` no longer works as expected with file objects

**To Reproduce**

foo.c:

```
```
meson.build:

```
project('contains-bug', 'c',
  meson_version: '>= 0.59.0',
)

l = [files('foo.c')]

message(l.contains(files('foo.c')))
```

**Expected behavior**

This should print `Message: True`

**system parameters**

* plain native build
* Fedora 33
* python 3.9.2
* meson 0.59.0
* ninja 1.10.2



Discussion:
`l` is empty, so contains() always should return false. Maybe a typo in your issue report, if I change to `f.contains()` then it prints False but I guess it should be True.
hah, my bad, edited
for context, this is in gstreamer's FFmpeg build, where we relied on the feature for poor man's deduplication: https://gitlab.freedesktop.org/gstreamer/meson-ports/ffmpeg/-/blob/meson-4.3/meson.build#L2118
Ok, I can confirm that with list of strings it works, but not with File objects.

The test case in this report could be a bit confusing because `files()` returns itself a list, so there is also the question if `[files()]` should be flattened. Also raise the question if calling `files()` twice for the same string should return the same object.

A simplified test case:
```
l = files('foo.c')
assert(l.contains(l[0]))
```

@mensinda that smell like a regression in holderify stuff you did, is it possible?
@MathieuDuponchelle I guess this is about the duplicate `g722dsp.o` ninja rule issue in FFmpeg we discussed with @tp-m on IRC yesterday?
@xclaesse I didn't see the discussion, but got a private report today about it yes :)
@xclaesse Maybe. The relevant code is [here](https://github.com/mesonbuild/meson/blob/master/mesonbuild/interpreterbase/interpreterbase.py#L786-L798) and from the looks of it flattening `posargs[0]` should fix the issue (and possibly introduce others...). Though, I honestly have no idea how this used to work previeously...

It is also slightly concerning that we use `==` for the check and I am not sure if we implement the `__eq__` function for all interpreter objects...
Putting flattening aside, this also prints False when I would expect it to print True (and that more closely matches the pattern in our FFmpeg meson.build):

```
project('contains-bug', 'c',
  meson_version: '>= 0.59.0',
)

fs = import('fs')

l = []

foreach f: files('foo.c')
  l += [f]
endforeach

foreach f: files('foo.c')
  message(l.contains(f))
endforeach
```
Should be fixed with #9027

Resolving patch:
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -783,7 +783,7 @@ def array_method_call(self,
                           posargs: T.List[TYPE_var],
                           kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]:
         if method_name == 'contains':
-            def check_contains(el: list) -> bool:
+            def check_contains(el: T.List[TYPE_var]) -> bool:
                 if len(posargs) != 1:
                     raise InterpreterException('Contains method takes exactly one argument.')
                 item = posargs[0]
@@ -795,7 +795,7 @@ def check_contains(el: list) -> bool:
                     if element == item:
                         return True
                 return False
-            return check_contains(obj)
+            return check_contains([_unholder(x) for x in obj])
         elif method_name == 'length':
             return len(obj)
         elif method_name == 'get':


Test patch:
diff --git a/test cases/common/56 array methods/a.txt b/test cases/common/56 array methods/a.txt
new file mode 100644
diff --git a/test cases/common/56 array methods/b.txt b/test cases/common/56 array methods/b.txt
new file mode 100644
diff --git a/test cases/common/56 array methods/c.txt b/test cases/common/56 array methods/c.txt
new file mode 100644
diff --git a/test cases/common/56 array methods/meson.build b/test cases/common/56 array methods/meson.build
--- a/test cases/common/56 array methods/meson.build	
+++ b/test cases/common/56 array methods/meson.build	
@@ -5,6 +5,22 @@ one = ['abc']
 two = ['def', 'ghi']
 combined = [empty, one, two]
 
+file_list = files('a.txt', 'b.txt')
+file_a = files('a.txt')
+file_c = files('c.txt')
+
+if file_a[0] != file_list[0]
+  error('Files are not equal')
+endif
+
+if not file_list.contains(file_a[0])
+  error('Contains with ObjectHolder lists does not work')
+endif
+
+if file_list.contains(file_c[0])
+  error('Contains with ObjectHolder lists found non existant object')
+endif
+
 if empty.contains('abc')
   error('Empty is not empty.')
 endif
@@ -41,6 +57,14 @@ if not combined.contains('abc')
   error('Combined claims not to contain abc.')
 endif
 
+if not combined.contains(one)
+  error('Combined claims not to contain [abc].')
+endif
+
+if not combined.contains(two)
+  error('Combined claims not to contain [def, ghi].')
+endif
+
 if not combined.contains('ghi')
   error('Combined claims not to contain ghi.')
 endif