From 809de599d542c60dc373120d3cbec229b9f8eda8 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Mon, 1 Nov 2021 10:56:57 +0100 Subject: [PATCH 1/2] interpreter: Add a Void holder --- mesonbuild/interpreterbase/__init__.py | 3 +++ mesonbuild/interpreterbase/baseobjects.py | 8 ++++++-- mesonbuild/interpreterbase/interpreterbase.py | 6 ++++++ mesonbuild/interpreterbase/void.py | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 mesonbuild/interpreterbase/void.py diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index 7c4b1db3b1e3..9d24433ad44d 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -24,6 +24,8 @@ 'Disabler', 'is_disabled', + 'VoidObject', + 'InterpreterException', 'InvalidCode', 'InvalidArguments', @@ -129,3 +131,4 @@ from .helpers import default_resolve_key, flatten, resolve_second_level_holders from .interpreterbase import InterpreterBase from .operator import MesonOperator +from .void import VoidObject diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index ca17481a1469..fdca815ead7f 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -32,7 +32,7 @@ TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) -TYPE_elementary = T.Union[str, int, bool, T.List[T.Any], T.Dict[str, T.Any]] +TYPE_elementary = T.Union[str, int, bool, T.List[T.Any], T.Dict[str, T.Any], None] TYPE_var = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject'] TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] TYPE_kwargs = T.Dict[str, TYPE_var] @@ -75,6 +75,10 @@ def __init__(self, *, subproject: T.Optional[str] = None) -> None: def display_name(self) -> str: return type(self).__name__ + @property + def is_assignable(self) -> bool: + return True + def method_call( self, method_name: str, @@ -130,7 +134,7 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' -HoldableTypes = (HoldableObject, int, bool, str, list, dict) +HoldableTypes = (HoldableObject, int, bool, str, list, dict, type(None)) TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject] InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index 326204da3cea..de337995f4f1 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -43,6 +43,7 @@ from .decorators import FeatureNew from .disabler import Disabler, is_disabled +from .void import VoidObject from .helpers import default_resolve_key, flatten, resolve_second_level_holders from .operator import MesonOperator from ._unholder import _unholder @@ -62,6 +63,7 @@ T.Type[str], T.Type[list], T.Type[dict], + T.Type[None], ], # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar] T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]] @@ -95,6 +97,10 @@ def __init__(self, source_root: str, subdir: str, subproject: str): # current meson version target within that if-block. self.tmp_meson_version = None # type: T.Optional[str] + self.holder_map.update({ + type(None): VoidObject, + }) + def load_root_meson_file(self) -> None: mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) if not os.path.isfile(mesonfile): diff --git a/mesonbuild/interpreterbase/void.py b/mesonbuild/interpreterbase/void.py new file mode 100644 index 000000000000..741f421e1991 --- /dev/null +++ b/mesonbuild/interpreterbase/void.py @@ -0,0 +1,16 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 + +from .baseobjects import ObjectHolder, TYPE_var, TYPE_kwargs, InvalidCode, MesonOperator +import typing as T + +class VoidObject(ObjectHolder[None]): + @property + def is_assignable(self) -> bool: + return False + + def method_call(self, method_name: str, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var: + raise InvalidCode(f'Tried to call method {method_name} on void.') + + def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var: + raise InvalidCode(f'Tried use operator {operator.value} on void.') From 47ad7fb86b826aaf6737275400a4bc7e2503a32e Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Mon, 1 Nov 2021 10:59:33 +0100 Subject: [PATCH 2/2] interpretrer: Use the new Void holder to simplyfy logic --- mesonbuild/interpreter/interpreter.py | 2 +- mesonbuild/interpreterbase/baseobjects.py | 1 + mesonbuild/interpreterbase/interpreterbase.py | 49 +++++++++---------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 1aa7829f1b0b..16043505102f 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -2753,7 +2753,7 @@ def is_subproject(self) -> bool: @noSecondLevelHolderResolving def func_set_variable(self, node: mparser.BaseNode, args: T.Tuple[str, object], kwargs: 'TYPE_kwargs') -> None: varname, value = args - self.set_variable(varname, value, holderify=True) + self.set_variable(varname, self._holderify(value)) @typed_pos_args('get_variable', (str, Disabler), optargs=[object]) @noKwargs diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index fdca815ead7f..56bb26d9e8d8 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -77,6 +77,7 @@ def display_name(self) -> str: @property def is_assignable(self) -> bool: + ''' Property used to indicate whether an object can be used at all ''' return True def method_call( diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index de337995f4f1..61fae0401cd4 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -179,7 +179,7 @@ def evaluate_codeblock(self, node: mparser.CodeBlockNode, start: int = 0, end: T raise e i += 1 # In THE FUTURE jump over blocks and stuff. - def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[InterpreterObject]: + def evaluate_statement(self, cur: mparser.BaseNode) -> InterpreterObject: self.current_node = cur if isinstance(cur, mparser.FunctionNode): return self.function_call(cur) @@ -192,7 +192,7 @@ def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[InterpreterObj elif isinstance(cur, mparser.BooleanNode): return self._holderify(cur.value) elif isinstance(cur, mparser.IfClauseNode): - return self.evaluate_if(cur) + self.evaluate_if(cur) elif isinstance(cur, mparser.IdNode): return self.get_variable(cur.value) elif isinstance(cur, mparser.ComparisonNode): @@ -229,7 +229,7 @@ def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[InterpreterObj raise BreakRequest() else: raise InvalidCode("Unknown statement.") - return None + return self._holderify(None) def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> InterpreterObject: (arguments, kwargs) = self.reduce_arguments(cur.args) @@ -256,7 +256,7 @@ def evaluate_notstatement(self, cur: mparser.NotNode) -> InterpreterObject: return v return self._holderify(v.operator_call(MesonOperator.NOT, None)) - def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]: + def evaluate_if(self, node: mparser.IfClauseNode) -> None: assert isinstance(node, mparser.IfClauseNode) for i in node.ifs: # Reset self.tmp_meson_version to know if it gets set during this @@ -264,7 +264,7 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]: self.tmp_meson_version = None result = self.evaluate_statement(i.condition) if isinstance(result, Disabler): - return result + return None if not isinstance(result, InterpreterObject): raise mesonlib.MesonBugException(f'Argument to not ({result}) is not an InterpreterObject but {type(result).__name__}.') res = result.operator_call(MesonOperator.BOOL, None) @@ -281,7 +281,6 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]: return None if not isinstance(node.elseblock, mparser.EmptyNode): self.evaluate_codeblock(node.elseblock) - return None def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject: val1 = self.evaluate_statement(node.left) @@ -360,7 +359,7 @@ def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> Interpret res = l.operator_call(mapping[cur.operation], _unholder(r)) return self._holderify(res) - def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterObject]: + def evaluate_ternary(self, node: mparser.TernaryNode) -> InterpreterObject: assert isinstance(node, mparser.TernaryNode) result = self.evaluate_statement(node.condition) if isinstance(result, Disabler): @@ -444,7 +443,7 @@ def evaluate_indexing(self, node: mparser.IndexNode) -> InterpreterObject: iobject.current_node = node return self._holderify(iobject.operator_call(MesonOperator.INDEX, index)) - def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObject]: + def function_call(self, node: mparser.FunctionNode) -> InterpreterObject: func_name = node.func_name (h_posargs, h_kwargs) = self.reduce_arguments(node.args) (posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs) @@ -458,12 +457,12 @@ def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObj if not getattr(func, 'no-second-level-holder-flattening', False): func_args, kwargs = resolve_second_level_holders(func_args, kwargs) res = func(node, func_args, kwargs) - return self._holderify(res) if res is not None else None + return self._holderify(res) else: self.unknown_function_called(func_name) - return None + return self._holderify(None) - def method_call(self, node: mparser.MethodNode) -> T.Optional[InterpreterObject]: + def method_call(self, node: mparser.MethodNode) -> InterpreterObject: invokable = node.source_object obj: T.Optional[InterpreterObject] if isinstance(invokable, mparser.IdNode): @@ -486,7 +485,7 @@ def method_call(self, node: mparser.MethodNode) -> T.Optional[InterpreterObject] raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}" of type {type(obj).__name__}') obj.current_node = node res = obj.method_call(method_name, args, kwargs) - return self._holderify(res) if res is not None else None + return self._holderify(res) def _holderify(self, res: T.Union[TYPE_var, InterpreterObject]) -> InterpreterObject: if isinstance(res, HoldableTypes): @@ -529,15 +528,15 @@ def reduce_arguments( raise InvalidArguments('All keyword arguments must be after positional arguments.') self.argument_depth += 1 reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] - if any(x is None for x in reduced_pos): - raise InvalidArguments(f'At least one value in the arguments is void.') + if any(not x.is_assignable for x in reduced_pos): + raise InvalidArguments(f'At least one value in the arguments is not assignable.') reduced_kw: T.Dict[str, InterpreterObject] = {} for key, val in args.kwargs.items(): reduced_key = key_resolver(key) assert isinstance(val, mparser.BaseNode) reduced_val = self.evaluate_statement(val) - if reduced_val is None: - raise InvalidArguments(f'Value of key {reduced_key} is void.') + if not reduced_val.is_assignable: + raise InvalidArguments(f'Value of key {reduced_key} is not assignable.') if duplicate_key_error and reduced_key in reduced_kw: raise InvalidArguments(duplicate_key_error.format(reduced_key)) reduced_kw[reduced_key] = reduced_val @@ -545,7 +544,7 @@ def reduce_arguments( final_kw = self.expand_default_kwargs(reduced_kw) return reduced_pos, final_kw - def expand_default_kwargs(self, kwargs: T.Dict[str, T.Optional[InterpreterObject]]) -> T.Dict[str, T.Optional[InterpreterObject]]: + def expand_default_kwargs(self, kwargs: T.Dict[str, InterpreterObject]) -> T.Dict[str, InterpreterObject]: if 'kwargs' not in kwargs: return kwargs to_expand = _unholder(kwargs.pop('kwargs')) @@ -574,17 +573,13 @@ def assignment(self, node: mparser.AssignmentNode) -> None: if isinstance(value, MutableInterpreterObject): value = copy.deepcopy(value) self.set_variable(var_name, value) - return 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.') - if holderify: - variable = self._holderify(variable) - else: - # Ensure that we are always storing ObjectHolders - if not isinstance(variable, InterpreterObject): - raise mesonlib.MesonBugException(f'set_variable in InterpreterBase called with a non InterpreterObject {variable} of type {type(variable).__name__}') + def set_variable(self, varname: str, variable: InterpreterObject) -> None: + if not variable.is_assignable: + raise InvalidCode(f'Can not assign object of type {variable.display_name()} to variable {varname}.') + # Ensure that we are always storing ObjectHolders + if not isinstance(variable, InterpreterObject): + raise mesonlib.MesonBugException(f'set_variable in InterpreterBase called with a non InterpreterObject {variable} of type {type(variable).__name__}') if not isinstance(varname, str): raise InvalidCode('First argument to set_variable must be a string.') if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None: