Repository: mesonbuild/meson

Issue:
Python error on introspect on meson.build file
Some introspect methods work on unconfigured builds, but for the other I gets a Python exception.
```
$ meson introspect --dependencies meson.build 

Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
    return options.run_func(options)
  File "/usr/lib/python3.7/site-packages/mesonbuild/mintro.py", line 405, in run
    return print_results(options, results, indent)
  File "/usr/lib/python3.7/site-packages/mesonbuild/mintro.py", line 374, in print_results
    print(json.dumps(results[0][1], indent=indent))
  File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib64/python3.7/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/usr/lib64/python3.7/json/encoder.py", line 429, in _iterencode
    yield from _iterencode_list(o, _current_indent_level)
  File "/usr/lib64/python3.7/json/encoder.py", line 325, in _iterencode_list
    yield from chunks
  File "/usr/lib64/python3.7/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/lib64/python3.7/json/encoder.py", line 438, in _iterencode
    o = _default(o)
  File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type FunctionNode is not JSON serializable
```


Discussion:
Do you have the `meson.build` publically available? Unfortunately, the stack trace alone isn't enough to find the underlying bug in this case.

Resolving patch:
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -153,8 +153,20 @@ def method_call(self, node):
         return True
 
     def evaluate_arithmeticstatement(self, cur):
+        self.evaluate_statement(cur.left)
+        self.evaluate_statement(cur.right)
         return 0
 
+    def evaluate_uminusstatement(self, cur):
+        self.evaluate_statement(cur.value)
+        return 0
+
+    def evaluate_ternary(self, node):
+        assert(isinstance(node, mparser.TernaryNode))
+        self.evaluate_statement(node.condition)
+        self.evaluate_statement(node.trueblock)
+        self.evaluate_statement(node.falseblock)
+
     def evaluate_plusassign(self, node):
         assert(isinstance(node, mparser.PlusAssignmentNode))
         if node.var_name not in self.assignments:
@@ -177,6 +189,18 @@ def reduce_arguments(self, args):
         return args.arguments, args.kwargs
 
     def evaluate_comparison(self, node):
+        self.evaluate_statement(node.left)
+        self.evaluate_statement(node.right)
+        return False
+
+    def evaluate_andstatement(self, cur):
+        self.evaluate_statement(cur.left)
+        self.evaluate_statement(cur.right)
+        return False
+
+    def evaluate_orstatement(self, cur):
+        self.evaluate_statement(cur.left)
+        self.evaluate_statement(cur.right)
         return False
 
     def evaluate_foreach(self, node):
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -20,7 +20,7 @@
 from .. import coredata as cdata
 from ..interpreterbase import InvalidArguments
 from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary
-from ..mparser import ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
+from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
 import os
 
 build_target_functions = ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries']
@@ -142,6 +142,8 @@ def func_dependency(self, node, args, kwargs):
         condition_level = node.condition_level if hasattr(node, 'condition_level') else 0
         if isinstance(required, ElementaryNode):
             required = required.value
+        if not isinstance(required, bool):
+            required = False
         self.dependencies += [{
             'name': name,
             'required': required,
@@ -189,6 +191,8 @@ def build_target(self, node, args, kwargs, targetclass):
 
         # Make sure nothing can crash when creating the build class
         kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in ['install', 'build_by_default', 'build_always']}
+        kwargs_reduced = {k: v.value if isinstance(v, ElementaryNode) else v for k, v in kwargs_reduced.items()}
+        kwargs_reduced = {k: v for k, v in kwargs_reduced.items() if not isinstance(v, BaseNode)}
         is_cross = False
         objects = []
         empty_sources = [] # Passing the unresolved sources list causes errors
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -3408,7 +3408,7 @@ def assertKeyTypes(key_type_list, obj):
         self.assertDictEqual(buildopts_to_find, {})
 
         # Check buildsystem_files
-        bs_files = ['meson.build', 'sharedlib/meson.build', 'staticlib/meson.build']
+        bs_files = ['meson.build', 'meson_options.txt', 'sharedlib/meson.build', 'staticlib/meson.build']
         bs_files = [os.path.join(testdir, x) for x in bs_files]
         self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files)))
 
@@ -3559,6 +3559,12 @@ def test_introspect_dependencies_from_source(self):
                 'has_fallback': False,
                 'conditional': False
             },
+            {
+                'name': 'bugDep1',
+                'required': False,
+                'has_fallback': False,
+                'conditional': False
+            },
             {
                 'name': 'somethingthatdoesnotexist',
                 'required': True,


Test patch:
diff --git a/test cases/unit/55 introspection/meson.build b/test cases/unit/55 introspection/meson.build
--- a/test cases/unit/55 introspection/meson.build	
+++ b/test cases/unit/55 introspection/meson.build	
@@ -2,6 +2,12 @@ project('introspection', ['c', 'cpp'], version: '1.2.3', default_options: ['cpp_
 
 dep1 = dependency('threads')
 dep2 = dependency('zlib', required: false)
+dep3 = dependency('bugDep1', required: get_option('test_opt1'))
+
+b1 = get_option('test_opt1')
+b2 = get_option('test_opt2')
+test_bool = b1 or b2
+test_bool = b1 and b2
 
 if false
   dependency('somethingthatdoesnotexist', required: true)
@@ -11,7 +17,7 @@ endif
 subdir('sharedlib')
 subdir('staticlib')
 
-t1 = executable('test1', 't1.cpp', link_with: [sharedlib], install: true)
+t1 = executable('test1', 't1.cpp', link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2'))
 t2 = executable('test2', 't2.cpp', link_with: [staticlib])
 t3 = executable('test3', 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1])
 
diff --git a/test cases/unit/55 introspection/meson_options.txt b/test cases/unit/55 introspection/meson_options.txt
new file mode 100644
--- /dev/null
+++ b/test cases/unit/55 introspection/meson_options.txt	
@@ -0,0 +1,2 @@
+option('test_opt1', type: 'boolean', value: false, description: 'simple boolean flag')
+option('test_opt2', type: 'boolean', value: true,  description: 'simple boolean flag')