diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-08-29 19:40:10 +0200 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-09-01 19:17:01 +0200 |
commit | 43302d3296baff6aeaf8e03f5d701b0402e37a6c (patch) | |
tree | 0dffb59760fc793e92e9c505090c4b7c550de5a1 /mesonbuild/interpreterbase/interpreterbase.py | |
parent | 86eda3c812e8b919dee485c0b6333671652ee30c (diff) | |
download | meson-43302d3296baff6aeaf8e03f5d701b0402e37a6c.zip meson-43302d3296baff6aeaf8e03f5d701b0402e37a6c.tar.gz meson-43302d3296baff6aeaf8e03f5d701b0402e37a6c.tar.bz2 |
interpreter: Introduce BooleanHolder for the bool primitive
Diffstat (limited to 'mesonbuild/interpreterbase/interpreterbase.py')
-rw-r--r-- | mesonbuild/interpreterbase/interpreterbase.py | 126 |
1 files changed, 64 insertions, 62 deletions
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index cfd72b5..3d419d4 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -51,6 +51,7 @@ from ._unholder import _unholder import os, copy, re, pathlib import typing as T import textwrap +from functools import wraps if T.TYPE_CHECKING: from ..interpreter import Interpreter @@ -69,11 +70,23 @@ FunctionType = T.Dict[ T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var] ] +__FN = T.TypeVar('__FN', bound=T.Callable[['InterpreterBase', T.Any], T.Union[TYPE_var, InterpreterObject]]) +def _holderify_result(types: T.Union[None, T.Type, T.Tuple[T.Type, ...]] = None) -> T.Callable[[__FN], __FN]: + def inner(f: __FN) -> __FN: + @wraps(f) + def wrapper(self: 'InterpreterBase', node: mparser.BaseNode) -> T.Union[TYPE_var, InterpreterObject]: + res = f(self, node) + if types is not None and not isinstance(res, types): + raise mesonlib.MesonBugException(f'Expected {types} but got object `{res}` of type {type(res).__name__}') + return self._holderify(res) + return T.cast(__FN, wrapper) + return inner + class MesonVersionString(str): pass class InterpreterBase: - elementary_types = (str, bool, list) + elementary_types = (str, list) def __init__(self, source_root: str, subdir: str, subproject: str): self.source_root = source_root @@ -190,7 +203,7 @@ class InterpreterBase: elif isinstance(cur, mparser.StringNode): return cur.value elif isinstance(cur, mparser.BooleanNode): - return cur.value + return self._holderify(cur.value) elif isinstance(cur, mparser.IfClauseNode): return self.evaluate_if(cur) elif isinstance(cur, mparser.IdNode): @@ -252,13 +265,15 @@ class InterpreterBase: assert not arguments return kwargs - def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[bool, Disabler]: + @_holderify_result((bool, Disabler)) + def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[TYPE_var, InterpreterObject]: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): return v - if not isinstance(v, bool): - raise InterpreterException('Argument to "not" is not a boolean.') - return not v + # TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects + if not isinstance(v, InterpreterObject): + raise mesonlib.MesonBugException(f'Argument to not ({v}) is not an InterpreterObject but {type(v).__name__}.') + return v.operator_call(MesonOperator.NOT, None) def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]: assert isinstance(node, mparser.IfClauseNode) @@ -269,6 +284,9 @@ class InterpreterBase: result = self.evaluate_statement(i.condition) 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__}.') + result = result.operator_call(MesonOperator.BOOL, None) if not isinstance(result, bool): raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.') if result: @@ -289,13 +307,14 @@ class InterpreterBase: return False return True - def evaluate_in(self, val1: T.Any, val2: T.Any) -> bool: + def _evaluate_in(self, val1: T.Any, val2: T.Any) -> bool: if not isinstance(val1, (str, int, float, mesonlib.HoldableObject)): raise InvalidArguments('lvalue of "in" operator must be a string, integer, float, or object') if not isinstance(val2, (list, dict)): raise InvalidArguments('rvalue of "in" operator must be an array or a dict') return val1 in val2 + @_holderify_result((bool, Disabler)) def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[TYPE_var, InterpreterObject]: val1 = self.evaluate_statement(node.left) if isinstance(val1, Disabler): @@ -318,11 +337,11 @@ class InterpreterBase: # Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`) if operator in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val2, InterpreterObject): - return self._holderify(val2.operator_call(operator, _unholder(val1))) + return val2.operator_call(operator, _unholder(val1)) # Normal evaluation, with the same semantics elif operator not in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val1, InterpreterObject): - return self._holderify(val1.operator_call(operator, _unholder(val2))) + return val1.operator_call(operator, _unholder(val2)) # OLD CODE, based on the builtin types -- remove once we have switched # over to all ObjectHolders. @@ -331,9 +350,9 @@ class InterpreterBase: val1 = _unholder(val1) val2 = _unholder(val2) if node.ctype == 'in': - return self.evaluate_in(val1, val2) + return self._evaluate_in(val1, val2) elif node.ctype == 'notin': - return not self.evaluate_in(val1, val2) + return not self._evaluate_in(val1, val2) valid = self.validate_comparison_types(val1, val2) # Ordering comparisons of different types isn't allowed since PR #1810 # (0.41.0). Since PR #2884 we also warn about equality comparisons of @@ -371,36 +390,41 @@ class InterpreterBase: else: raise InvalidCode('You broke my compare eval.') - def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[bool, Disabler]: + @_holderify_result((bool, Disabler)) + def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[TYPE_var, InterpreterObject]: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l - if not isinstance(l, bool): - raise InterpreterException('First argument to "and" is not a boolean.') - if not l: - return False + if not isinstance(l, InterpreterObject): + raise mesonlib.MesonBugException(f'Firtst argument to and ({l}) is not an InterpreterObject but {type(l).__name__}.') + l_bool = l.operator_call(MesonOperator.BOOL, None) + if not l_bool: + return l_bool r = self.evaluate_statement(cur.right) if isinstance(r, Disabler): return r - if not isinstance(r, bool): - raise InterpreterException('Second argument to "and" is not a boolean.') - return r + if not isinstance(r, InterpreterObject): + raise mesonlib.MesonBugException(f'Second argument to and ({r}) is not an InterpreterObject but {type(r).__name__}.') + return r.operator_call(MesonOperator.BOOL, None) - def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[bool, Disabler]: + @_holderify_result((bool, Disabler)) + def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[TYPE_var, InterpreterObject]: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l - if not isinstance(l, bool): - raise InterpreterException('First argument to "or" is not a boolean.') - if l: - return True + if not isinstance(l, InterpreterObject): + raise mesonlib.MesonBugException(f'Firtst argument to or ({l}) is not an InterpreterObject but {type(l).__name__}.') + l_bool = l.operator_call(MesonOperator.BOOL, None) + if l_bool: + return l_bool r = self.evaluate_statement(cur.right) if isinstance(r, Disabler): return r - if not isinstance(r, bool): - raise InterpreterException('Second argument to "or" is not a boolean.') - return r + if not isinstance(r, InterpreterObject): + raise mesonlib.MesonBugException(f'Second argument to ot ({r}) is not an InterpreterObject but {type(r).__name__}.') + return r.operator_call(MesonOperator.BOOL, None) + @_holderify_result() def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[TYPE_var, InterpreterObject]: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): @@ -408,7 +432,7 @@ class InterpreterBase: # TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects if not isinstance(v, InterpreterObject): raise InterpreterException(f'Argument to negation ({v}) is not an InterpreterObject but {type(v).__name__}.') - return self._holderify(v.operator_call(MesonOperator.UMINUS, None)) + return v.operator_call(MesonOperator.UMINUS, None) @FeatureNew('/ with string arguments', '0.49.0') def evaluate_path_join(self, l: str, r: str) -> str: @@ -473,9 +497,10 @@ class InterpreterBase: result = self.evaluate_statement(node.condition) if isinstance(result, Disabler): return result - if not isinstance(result, bool): - raise InterpreterException('Ternary condition is not boolean.') - if result: + if not isinstance(result, InterpreterObject): + raise mesonlib.MesonBugException(f'Ternary condition ({result}) is not an InterpreterObject but {type(result).__name__}.') + result_bool = result.operator_call(MesonOperator.BOOL, None) + if result_bool: return self.evaluate_statement(node.trueblock) else: return self.evaluate_statement(node.falseblock) @@ -554,7 +579,7 @@ class InterpreterBase: new_value = {**old_variable, **addition} elif isinstance(old_variable, InterpreterObject): # TODO: don't make _unholder permissive - new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition, permissive=True))) + new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition))) # Add other data types here. else: raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints') @@ -596,7 +621,8 @@ class InterpreterBase: # We are already checking for the existence of __getitem__, so this should be save raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) # type: ignore - def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_elementary, InterpreterObject]]: + @_holderify_result() + def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: func_name = node.func_name (h_posargs, h_kwargs) = self.reduce_arguments(node.args) (posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs) @@ -609,8 +635,7 @@ class InterpreterBase: func_args = flatten(posargs) 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) + return func(node, func_args, kwargs) else: self.unknown_function_called(func_name) return None @@ -631,7 +656,7 @@ class InterpreterBase: if isinstance(obj, str): return self._holderify(self.string_method_call(obj, method_name, args, kwargs)) if isinstance(obj, bool): - return self._holderify(self.bool_method_call(obj, method_name, args, kwargs)) + raise mesonlib.MesonBugException('Booleans are now wrapped in object holders!') if isinstance(obj, int): raise mesonlib.MesonBugException('Integers are now wrapped in object holders!') if isinstance(obj, list): @@ -653,7 +678,7 @@ class InterpreterBase: # TODO: remove `permissive` once all primitives are ObjectHolders if res is None: return None - if isinstance(res, (bool, str)): + if isinstance(res, str): return res elif isinstance(res, list): return [self._holderify(x, permissive=permissive) for x in res] @@ -684,29 +709,6 @@ class InterpreterBase: kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]: return [_unholder(x) for x in args], {k: _unholder(v) for k, v in kwargs.items()} - @noKwargs - def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int]: - if method_name == 'to_string': - if not posargs: - if obj: - return 'true' - else: - return 'false' - elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str): - if obj: - return posargs[0] - else: - return posargs[1] - else: - raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.') - elif method_name == 'to_int': - if obj: - return 1 - else: - return 0 - else: - raise InterpreterException('Unknown method "%s" for a boolean.' % method_name) - @staticmethod def _get_one_string_posarg(posargs: T.List[TYPE_var], method_name: str) -> str: if len(posargs) > 1: @@ -830,7 +832,7 @@ class InterpreterBase: if element == item: return True return False - return check_contains([_unholder(x) for x in obj]) + return self._holderify(check_contains([_unholder(x) for x in obj])) elif method_name == 'length': return self._holderify(len(obj)) elif method_name == 'get': @@ -876,7 +878,7 @@ class InterpreterBase: has_key = key in obj if method_name == 'has_key': - return has_key + return self._holderify(has_key) if has_key: return obj[key] |