Repository: mesonbuild/meson

Issue:
'NoneType' object has no attribute 'current_node'
**Describe the bug**
I was setting up a build of GIMP3 from the master branch in MSYS2/MinGW64, but I'm getting an internal error:

```
Configuring org.gimp.extension.goat-exercises.metainfo.xml.in using configuration
Traceback (most recent call last):
  File "D:/meson/mesonbuild/mesonmain.py", line 193, in run
    return options.run_func(options)
  File "D:/meson/mesonbuild/msetup.py", line 309, in run
    app.generate()
  File "D:/meson/mesonbuild/msetup.py", line 186, in generate
    self._generate(env)
  File "D:/meson/mesonbuild/msetup.py", line 230, in _generate
    intr.run()
  File "D:/meson/mesonbuild/interpreter/interpreter.py", line 2886, in run
    super().run()
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 149, in run
    self.evaluate_codeblock(self.ast, start=1)
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 174, in evaluate_codeblock
    raise e
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 167, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 180, in evaluate_statement
    return self.function_call(cur)
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 462, in function_call
    res = func(node, func_args, kwargs)
  File "D:/meson/mesonbuild/interpreterbase/decorators.py", line 277, in wrapper
    return f(*nargs, **wrapped_kwargs)
  File "D:/meson/mesonbuild/interpreterbase/decorators.py", line 590, in wrapper
    return f(*wrapped_args, **wrapped_kwargs)
  File "D:/meson/mesonbuild/interpreter/interpreter.py", line 2298, in func_subdir
    self.evaluate_codeblock(codeblock)
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 174, in evaluate_codeblock
    raise e
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 167, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 208, in evaluate_statement
    return self.evaluate_uminusstatement(cur)
  File "D:/meson/mesonbuild/interpreterbase/interpreterbase.py", line 339, in evaluate_uminusstatement
    v.current_node = cur
AttributeError: 'NoneType' object has no attribute 'current_node'

../gimp/plug-ins/meson.build:4:1: ERROR: Unhandled python exception

    This is a Meson bug and should be reported!
```

I am launching Meson with the following command-line:

`python /d/meson/meson.py setup ../gimp --prefix=$GIMP_PREFIX --buildtype=debug`

**To Reproduce**

* `git clone --depth=1 https://gitlab.gnome.org/GNOME/gimp.git`
* `mkdir gimp-build`
* `cd gimp-build`
* `meson setup ../gimp --buildtype=debug`

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? 
  `Native`
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.)
  `Win10`
* what Python version are you using e.g. 3.8.0
  `Python 3.10.9`
* what meson --version
  `Tried both 0.64.1 and current master (1.0.99)`
* what ninja --version if it's a Ninja build
  `Ninja 1.11.1`



Discussion:
Looks like I made a mistake: instead of commenting a line:

`#subdir('file-exr')`

I wrote that:

`-subdir('file-exr')`

This confuses the parser.
This is still a legitimate bug, we should not display this exception.

Resolving patch:
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -31,12 +31,13 @@
 )
 
 from .exceptions import (
+    BreakRequest,
+    ContinueRequest,
     InterpreterException,
-    InvalidCode,
     InvalidArguments,
+    InvalidCode,
+    MesonException,
     SubdirDoneRequest,
-    ContinueRequest,
-    BreakRequest
 )
 
 from .decorators import FeatureNew
@@ -71,6 +72,13 @@
         T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var]
     ]
 
+
+class InvalidCodeOnVoid(InvalidCode):
+
+    def __init__(self, op_type: str) -> None:
+        super().__init__(f'Cannot perform {op_type!r} operation on void statement.')
+
+
 class InterpreterBase:
     def __init__(self, source_root: str, subdir: str, subproject: 'SubProject'):
         self.source_root = source_root
@@ -238,7 +246,10 @@ def evaluate_dictstatement(self, cur: mparser.DictNode) -> InterpreterObject:
         def resolve_key(key: mparser.BaseNode) -> str:
             if not isinstance(key, mparser.StringNode):
                 FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject)
-            str_key = _unholder(self.evaluate_statement(key))
+            key_holder = self.evaluate_statement(key)
+            if key_holder is None:
+                raise InvalidArguments('Key cannot be void.')
+            str_key = _unholder(key_holder)
             if not isinstance(str_key, str):
                 raise InvalidArguments('Key must be a string')
             return str_key
@@ -248,6 +259,8 @@ def resolve_key(key: mparser.BaseNode) -> str:
 
     def evaluate_notstatement(self, cur: mparser.NotNode) -> InterpreterObject:
         v = self.evaluate_statement(cur.value)
+        if v is None:
+            raise InvalidCodeOnVoid('not')
         if isinstance(v, Disabler):
             return v
         return self._holderify(v.operator_call(MesonOperator.NOT, None))
@@ -259,10 +272,12 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
             # statement evaluation.
             self.tmp_meson_version = None
             result = self.evaluate_statement(i.condition)
+            if result is None:
+                raise InvalidCodeOnVoid('if')
             if isinstance(result, Disabler):
                 return result
             if not isinstance(result, InterpreterObject):
-                raise mesonlib.MesonBugException(f'Argument to not ({result}) is not an InterpreterObject but {type(result).__name__}.')
+                raise mesonlib.MesonBugException(f'Argument to if ({result}) is not an InterpreterObject but {type(result).__name__}.')
             res = result.operator_call(MesonOperator.BOOL, None)
             if not isinstance(res, bool):
                 raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.')
@@ -281,9 +296,13 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
 
     def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject:
         val1 = self.evaluate_statement(node.left)
+        if val1 is None:
+            raise MesonException('Cannot compare a void statement on the left-hand side')
         if isinstance(val1, Disabler):
             return val1
         val2 = self.evaluate_statement(node.right)
+        if val2 is None:
+            raise MesonException('Cannot compare a void statement on the right-hand side')
         if isinstance(val2, Disabler):
             return val2
 
@@ -308,30 +327,40 @@ def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject
 
     def evaluate_andstatement(self, cur: mparser.AndNode) -> InterpreterObject:
         l = self.evaluate_statement(cur.left)
+        if l is None:
+            raise MesonException('Cannot compare a void statement on the left-hand side')
         if isinstance(l, Disabler):
             return l
         l_bool = l.operator_call(MesonOperator.BOOL, None)
         if not l_bool:
             return self._holderify(l_bool)
         r = self.evaluate_statement(cur.right)
+        if r is None:
+            raise MesonException('Cannot compare a void statement on the right-hand side')
         if isinstance(r, Disabler):
             return r
         return self._holderify(r.operator_call(MesonOperator.BOOL, None))
 
     def evaluate_orstatement(self, cur: mparser.OrNode) -> InterpreterObject:
         l = self.evaluate_statement(cur.left)
+        if l is None:
+            raise MesonException('Cannot compare a void statement on the left-hand side')
         if isinstance(l, Disabler):
             return l
         l_bool = l.operator_call(MesonOperator.BOOL, None)
         if l_bool:
             return self._holderify(l_bool)
         r = self.evaluate_statement(cur.right)
+        if r is None:
+            raise MesonException('Cannot compare a void statement on the right-hand side')
         if isinstance(r, Disabler):
             return r
         return self._holderify(r.operator_call(MesonOperator.BOOL, None))
 
     def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> InterpreterObject:
         v = self.evaluate_statement(cur.value)
+        if v is None:
+            raise InvalidCodeOnVoid('unary minus')
         if isinstance(v, Disabler):
             return v
         v.current_node = cur
@@ -344,6 +373,8 @@ def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> Interpret
         r = self.evaluate_statement(cur.right)
         if isinstance(r, Disabler):
             return r
+        if l is None or r is None:
+            raise InvalidCodeOnVoid(cur.operation)
 
         mapping: T.Dict[str, MesonOperator] = {
             'add': MesonOperator.PLUS,
@@ -359,6 +390,8 @@ def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> Interpret
     def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterObject]:
         assert isinstance(node, mparser.TernaryNode)
         result = self.evaluate_statement(node.condition)
+        if result is None:
+            raise MesonException('Cannot use a void statement as condition for ternary operator.')
         if isinstance(result, Disabler):
             return result
         result.current_node = node
@@ -424,6 +457,8 @@ def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None:
         assert isinstance(node, mparser.PlusAssignmentNode)
         varname = node.var_name
         addition = self.evaluate_statement(node.value)
+        if addition is None:
+            raise InvalidCodeOnVoid('plus assign')
 
         # Remember that all variables are immutable. We must always create a
         # full new variable and then assign it.
@@ -435,12 +470,15 @@ def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None:
     def evaluate_indexing(self, node: mparser.IndexNode) -> InterpreterObject:
         assert isinstance(node, mparser.IndexNode)
         iobject = self.evaluate_statement(node.iobject)
+        if iobject is None:
+            raise InterpreterException('Tried to evaluate indexing on void.')
         if isinstance(iobject, Disabler):
             return iobject
-        index = _unholder(self.evaluate_statement(node.index))
+        index_holder = self.evaluate_statement(node.index)
+        if index_holder is None:
+            raise InvalidArguments('Cannot use void statement as index.')
+        index = _unholder(index_holder)
 
-        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))
 
@@ -578,7 +616,7 @@ def assignment(self, node: mparser.AssignmentNode) -> None:
 
     def set_variable(self, varname: str, variable: T.Union[TYPE_var, InterpreterObject], *, holderify: bool = False) -> None:
         if variable is None:
-            raise InvalidCode('Can not assign None to variable.')
+            raise InvalidCode('Can not assign void to variable.')
         if holderify:
             variable = self._holderify(variable)
         else:


Test patch: