diff options
-rw-r--r-- | mesonbuild/ast/interpreter.py | 3 | ||||
-rw-r--r-- | mesonbuild/interpreter/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 3 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/boolean.py | 53 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/decorators.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/interpreterbase.py | 126 |
8 files changed, 129 insertions, 68 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 7e4bc62..4fd1378 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -366,7 +366,8 @@ class AstInterpreter(InterpreterBase): if isinstance(src, str): result = self.string_method_call(src, node.name, margs, mkwargs) elif isinstance(src, bool): - result = self.bool_method_call(src, node.name, margs, mkwargs) + from ..interpreter import Interpreter, BooleanHolder + result = BooleanHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) elif isinstance(src, int): from ..interpreter import Interpreter, IntegerHolder result = IntegerHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py index 0a7717f..90d7faf 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -35,6 +35,7 @@ __all__ = [ 'ExternalProgramHolder', 'extract_required_kwarg', + 'BooleanHolder', 'IntegerHolder', ] @@ -47,5 +48,6 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg extract_required_kwarg) from .primitives import ( + BooleanHolder, IntegerHolder, ) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index d4d5564..41cb7b8 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -26,7 +26,7 @@ from ..programs import ExternalProgram, NonExistingExternalProgram from ..dependencies import Dependency from ..depfile import DepFile from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args -from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, permissive_unholder_return +from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, unholder_return from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from ..interpreterbase import Disabler, disablerIfNotFound from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs @@ -381,6 +381,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.holder_map.update({ # Primitives int: P_OBJ.IntegerHolder, + bool: P_OBJ.BooleanHolder, # Meson types mesonlib.File: OBJ.FileHolder, diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py index 56af72a..5d16744 100644 --- a/mesonbuild/interpreter/primitives/__init__.py +++ b/mesonbuild/interpreter/primitives/__init__.py @@ -2,7 +2,9 @@ # SPDX-license-identifier: Apache-2.0 __all__ = [ + 'BooleanHolder', 'IntegerHolder', ] +from .boolean import BooleanHolder from .integer import IntegerHolder diff --git a/mesonbuild/interpreter/primitives/boolean.py b/mesonbuild/interpreter/primitives/boolean.py new file mode 100644 index 0000000..0a434af --- /dev/null +++ b/mesonbuild/interpreter/primitives/boolean.py @@ -0,0 +1,53 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 + +from ...interpreterbase import ( + ObjectHolder, + MesonOperator, + typed_pos_args, + noKwargs, + noPosargs, + + TYPE_var, + TYPE_kwargs, + + InvalidArguments +) + +import typing as T + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + +class BooleanHolder(ObjectHolder[bool]): + def __init__(self, obj: bool, interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'to_int': self.to_int_method, + 'to_string': self.to_string_method, + }) + + self.trivial_operators.update({ + MesonOperator.BOOL: (None, lambda x: self.held_object), + MesonOperator.NOT: (None, lambda x: not self.held_object), + MesonOperator.EQUALS: (bool, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (bool, lambda x: self.held_object != x), + }) + + def display_name(self) -> str: + return 'bool' + + @noKwargs + @noPosargs + def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + return 1 if self.held_object else 0 + + @noKwargs + @typed_pos_args('bool.to_string', optargs=[str, str]) + def to_string_method(self, args: T.Tuple[T.Optional[str], T.Optional[str]], kwargs: TYPE_kwargs) -> str: + true_str = args[0] or 'true' + false_str = args[1] or 'false' + if any(x is not None for x in args) and not all(x is not None for x in args): + raise InvalidArguments('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.') + return true_str if self.held_object else false_str diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 4b538fb..80cf0b5 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -129,8 +129,8 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' -HoldableTypes = (HoldableObject, int) -TYPE_HoldableTypes = T.Union[HoldableObject, int] +HoldableTypes = (HoldableObject, int, bool) +TYPE_HoldableTypes = T.Union[HoldableObject, int, bool] InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index 156b4c9..b9c4a1f 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -77,11 +77,11 @@ def noSecondLevelHolderResolving(f: TV_func) -> TV_func: setattr(f, 'no-second-level-holder-flattening', True) # noqa: B010 return f -def permissive_unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]: +def unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]: @wraps(f) def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: res = f(*wrapped_args, **wrapped_kwargs) - return _unholder(res, permissive=True) + return _unholder(res) return T.cast(T.Callable[..., TYPE_var], wrapped) def disablerIfNotFound(f: TV_func) -> TV_func: 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] |