diff options
-rw-r--r-- | mesonbuild/ast/interpreter.py | 19 | ||||
-rw-r--r-- | mesonbuild/interpreter/__init__.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/__init__.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/array.py | 103 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/dict.py | 87 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/_unholder.py | 10 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 31 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/interpreterbase.py | 507 | ||||
-rw-r--r-- | test cases/failing/11 object arithmetic/test.json | 2 | ||||
-rw-r--r-- | test cases/failing/12 string arithmetic/test.json | 2 | ||||
-rw-r--r-- | test cases/failing/13 array arithmetic/test.json | 2 | ||||
-rw-r--r-- | test cases/failing/51 inconsistent comparison/test.json | 2 |
14 files changed, 349 insertions, 428 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 5998e5b..7954b9b 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -30,6 +30,15 @@ from ..interpreterbase import ( TYPE_nkwargs, ) +from ..interpreter import ( + Interpreter, + StringHolder, + BooleanHolder, + IntegerHolder, + ArrayHolder, + DictHolder, +) + from ..mparser import ( AndNode, ArgumentNode, @@ -202,6 +211,9 @@ class AstInterpreter(InterpreterBase): assert isinstance(node, mparser.FormatStringNode) return node.value + def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> TYPE_nvar: + return self.reduce_arguments(cur.args)[0] + def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> int: self.evaluate_statement(cur.left) self.evaluate_statement(cur.right) @@ -364,18 +376,15 @@ class AstInterpreter(InterpreterBase): mkwargs = {} # type: T.Dict[str, TYPE_nvar] try: if isinstance(src, str): - from ..interpreter import Interpreter, StringHolder result = StringHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) elif isinstance(src, bool): - 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) elif isinstance(src, list): - result = self.array_method_call(src, node.name, margs, mkwargs) + result = ArrayHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) elif isinstance(src, dict): - result = self.dict_method_call(src, node.name, margs, mkwargs) + result = DictHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) except mesonlib.MesonException: return None diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py index c93dbc9..2269837 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -35,7 +35,9 @@ __all__ = [ 'ExternalProgramHolder', 'extract_required_kwarg', + 'ArrayHolder', 'BooleanHolder', + 'DictHolder', 'IntegerHolder', 'StringHolder', ] @@ -49,7 +51,9 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg extract_required_kwarg) from .primitives import ( + ArrayHolder, BooleanHolder, + DictHolder, IntegerHolder, StringHolder, ) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index b2774f6..f0b650b 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -386,6 +386,8 @@ class Interpreter(InterpreterBase, HoldableObject): ''' self.holder_map.update({ # Primitives + list: P_OBJ.ArrayHolder, + dict: P_OBJ.DictHolder, int: P_OBJ.IntegerHolder, bool: P_OBJ.BooleanHolder, str: P_OBJ.StringHolder, diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py index d6c0795..d876f56 100644 --- a/mesonbuild/interpreter/primitives/__init__.py +++ b/mesonbuild/interpreter/primitives/__init__.py @@ -2,13 +2,17 @@ # SPDX-license-identifier: Apache-2.0 __all__ = [ + 'ArrayHolder', 'BooleanHolder', + 'DictHolder', 'IntegerHolder', 'StringHolder', 'MesonVersionString', 'MesonVersionStringHolder', ] +from .array import ArrayHolder from .boolean import BooleanHolder +from .dict import DictHolder from .integer import IntegerHolder from .string import StringHolder, MesonVersionString, MesonVersionStringHolder diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py new file mode 100644 index 0000000..566dff9 --- /dev/null +++ b/mesonbuild/interpreter/primitives/array.py @@ -0,0 +1,103 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 + +import typing as T + +from ...interpreterbase import ( + ObjectHolder, + IterableObject, + MesonOperator, + typed_operator, + noKwargs, + noPosargs, + noArgsFlattening, + typed_pos_args, + + TYPE_var, + TYPE_kwargs, + + InvalidArguments, +) + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + +class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): + def __init__(self, obj: T.List[TYPE_var], interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'contains': self.contains_method, + 'length': self.length_method, + 'get': self.get_method, + }) + + + self.trivial_operators.update({ + MesonOperator.EQUALS: (list, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (list, lambda x: self.held_object != x), + MesonOperator.IN: (object, lambda x: x in self.held_object), + MesonOperator.NOT_IN: (object, lambda x: x not in self.held_object), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.PLUS: self.op_plus, + MesonOperator.INDEX: self.op_index, + }) + + def display_name(self) -> str: + return 'array' + + def iter_tuple_size(self) -> None: + return None + + def iter_self(self) -> T.Iterator[TYPE_var]: + return iter(self.held_object) + + def size(self) -> int: + return len(self.held_object) + + @noArgsFlattening + @noKwargs + @typed_pos_args('array.contains', object) + def contains_method(self, args: T.Tuple[object], kwargs: TYPE_kwargs) -> bool: + def check_contains(el: T.List[TYPE_var]) -> bool: + for element in el: + if isinstance(element, list): + found = check_contains(element) + if found: + return True + if element == args[0]: + return True + return False + return check_contains(self.held_object) + + @noKwargs + @noPosargs + def length_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + return len(self.held_object) + + @noArgsFlattening + @noKwargs + @typed_pos_args('array.get', int, optargs=[object]) + def get_method(self, args: T.Tuple[int, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var: + index = args[0] + if index < -len(self.held_object) or index >= len(self.held_object): + if args[1] is None: + raise InvalidArguments(f'Array index {index} is out of bounds for array of size {len(self.held_object)}.') + return args[1] + return self.held_object[index] + + @typed_operator(MesonOperator.PLUS, object) + def op_plus(self, other: TYPE_var) -> T.List[TYPE_var]: + if not isinstance(other, list): + other = [other] + return self.held_object + other + + @typed_operator(MesonOperator.INDEX, int) + def op_index(self, other: int) -> TYPE_var: + try: + return self.held_object[other] + except IndexError: + raise InvalidArguments(f'Index {other} out of bounds of array of size {len(self.held_object)}.') diff --git a/mesonbuild/interpreter/primitives/dict.py b/mesonbuild/interpreter/primitives/dict.py new file mode 100644 index 0000000..24f4d21 --- /dev/null +++ b/mesonbuild/interpreter/primitives/dict.py @@ -0,0 +1,87 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 + +import typing as T + +from ...interpreterbase import ( + ObjectHolder, + IterableObject, + MesonOperator, + typed_operator, + noKwargs, + noPosargs, + noArgsFlattening, + typed_pos_args, + + TYPE_var, + TYPE_kwargs, + + InvalidArguments, +) + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + +class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): + def __init__(self, obj: T.Dict[str, TYPE_var], interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'has_key': self.has_key_method, + 'keys': self.keys_method, + 'get': self.get_method, + }) + + self.trivial_operators.update({ + # Arithmetic + MesonOperator.PLUS: (dict, lambda x: {**self.held_object, **x}), + + # Comparison + MesonOperator.EQUALS: (dict, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (dict, lambda x: self.held_object != x), + MesonOperator.IN: (str, lambda x: x in self.held_object), + MesonOperator.NOT_IN: (str, lambda x: x not in self.held_object), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.INDEX: self.op_index, + }) + + def display_name(self) -> str: + return 'dict' + + def iter_tuple_size(self) -> int: + return 2 + + def iter_self(self) -> T.Iterator[T.Tuple[str, TYPE_var]]: + return iter(self.held_object.items()) + + def size(self) -> int: + return len(self.held_object) + + @noKwargs + @typed_pos_args('dict.has_key', str) + def has_key_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return args[0] in self.held_object + + @noKwargs + @noPosargs + def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: + return sorted(self.held_object) + + @noArgsFlattening + @noKwargs + @typed_pos_args('dict.get', str, optargs=[object]) + def get_method(self, args: T.Tuple[str, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var: + if args[0] in self.held_object: + return self.held_object[args[0]] + if args[1] is not None: + return args[1] + raise InvalidArguments(f'Key {args[0]!r} is not in the dictionary.') + + @typed_operator(MesonOperator.INDEX, str) + def op_index(self, other: str) -> TYPE_var: + if other not in self.held_object: + raise InvalidArguments(f'Key {other} is not in the dictionary.') + return self.held_object[other] diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index 0375430..14462a2 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -16,6 +16,7 @@ __all__ = [ 'InterpreterObject', 'MesonInterpreterObject', 'ObjectHolder', + 'IterableObject', 'RangeHolder', 'MutableInterpreterObject', @@ -76,6 +77,7 @@ from .baseobjects import ( InterpreterObject, MesonInterpreterObject, ObjectHolder, + IterableObject, RangeHolder, MutableInterpreterObject, diff --git a/mesonbuild/interpreterbase/_unholder.py b/mesonbuild/interpreterbase/_unholder.py index 221c52c..7157d17 100644 --- a/mesonbuild/interpreterbase/_unholder.py +++ b/mesonbuild/interpreterbase/_unholder.py @@ -16,14 +16,8 @@ from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder from .exceptions import InvalidArguments from ..mesonlib import HoldableObject, MesonBugException -import typing as T - -def _unholder(obj: T.Union[TYPE_var, InterpreterObject]) -> TYPE_var: - if isinstance(obj, list): - return [_unholder(x) for x in obj] - elif isinstance(obj, dict): - return {k: _unholder(v) for k, v in obj.items()} - elif isinstance(obj, ObjectHolder): +def _unholder(obj: InterpreterObject) -> TYPE_var: + if isinstance(obj, ObjectHolder): assert isinstance(obj.held_object, HoldableTypes) return obj.held_object elif isinstance(obj, MesonInterpreterObject): diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index caeb47b..3c7085b 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -20,6 +20,7 @@ from ..mesonlib import HoldableObject, MesonBugException import textwrap import typing as T +from abc import ABCMeta if T.TYPE_CHECKING: # Object holders need the actual interpreter @@ -54,7 +55,7 @@ class InterpreterObject: self.trivial_operators: T.Dict[ MesonOperator, T.Tuple[ - T.Type[T.Union[TYPE_var, T.Tuple[TYPE_var, ...]]], + T.Union[T.Type, T.Tuple[T.Type, ...]], 'OperatorCall' ] ] = {} @@ -94,7 +95,7 @@ class InterpreterObject: if op[0] is None and other is not None: raise MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}') if op[0] is not None and not isinstance(other, op[0]): - raise InvalidArguments(f'The `{operator.value}` of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})') + raise InvalidArguments(f'The `{operator.value}` operator of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})') return op[1](other) if operator in self.operators: return self.operators[operator](other) @@ -128,8 +129,8 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' -HoldableTypes = (HoldableObject, int, bool, str) -TYPE_HoldableTypes = T.Union[HoldableObject, int, bool, str] +HoldableTypes = (HoldableObject, int, bool, str, list, dict) +TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject] InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): @@ -163,7 +164,20 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): def __repr__(self) -> str: return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>' -class RangeHolder(MesonInterpreterObject): +class IterableObject(metaclass=ABCMeta): + '''Base class for all objects that can be iterated over in a foreach loop''' + + def iter_tuple_size(self) -> T.Optional[int]: + '''Return the size of the tuple for each iteration. Returns None if only a single value is returned.''' + raise MesonBugException(f'iter_tuple_size not implemented for {self.__class__.__name__}') + + def iter_self(self) -> T.Iterator[T.Union[TYPE_var, T.Tuple[TYPE_var, ...]]]: + raise MesonBugException(f'iter not implemented for {self.__class__.__name__}') + + def size(self) -> int: + raise MesonBugException(f'size not implemented for {self.__class__.__name__}') + +class RangeHolder(MesonInterpreterObject, IterableObject): def __init__(self, start: int, stop: int, step: int, *, subproject: str) -> None: super().__init__(subproject=subproject) self.range = range(start, stop, step) @@ -177,8 +191,11 @@ class RangeHolder(MesonInterpreterObject): except: raise InvalidArguments(f'Index {other} out of bounds of range.') - def __iter__(self) -> T.Iterator[int]: + def iter_tuple_size(self) -> None: + return None + + def iter_self(self) -> T.Iterator[int]: return iter(self.range) - def __len__(self) -> int: + def size(self) -> int: return len(self.range) diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index 4b4b3c0..bc21951 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -24,9 +24,8 @@ from .baseobjects import ( MutableInterpreterObject, InterpreterObjectTypeVar, ObjectHolder, - RangeHolder, + IterableObject, - TYPE_elementary, TYPE_var, TYPE_kwargs, @@ -42,7 +41,7 @@ from .exceptions import ( BreakRequest ) -from .decorators import FeatureNew, noKwargs +from .decorators import FeatureNew from .disabler import Disabler, is_disabled from .helpers import default_resolve_key, flatten, resolve_second_level_holders from .operator import MesonOperator @@ -51,7 +50,6 @@ from ._unholder import _unholder import os, copy, re, pathlib import typing as T import textwrap -from functools import wraps if T.TYPE_CHECKING: # T.cast is not handled by flake8 to detect quoted annotation use @@ -64,6 +62,8 @@ HolderMapType = T.Dict[ T.Type[int], T.Type[bool], T.Type[str], + T.Type[list], + T.Type[dict], ], # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar] T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]] @@ -74,21 +74,7 @@ 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 InterpreterBase: - elementary_types = (list, ) - def __init__(self, source_root: str, subdir: str, subproject: str): self.source_root = source_root self.funcs: FunctionType = {} @@ -99,8 +85,7 @@ class InterpreterBase: self.subdir = subdir self.root_subdir = subdir self.subproject = subproject - # TODO: This should actually be more strict: T.Union[TYPE_elementary, InterpreterObject] - self.variables: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} + self.variables: T.Dict[str, InterpreterObject] = {} self.argument_depth = 0 self.current_lineno = -1 # Current node set during a function call. This can be used as location @@ -190,7 +175,7 @@ class InterpreterBase: raise e i += 1 # In THE FUTURE jump over blocks and stuff. - def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: + def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[InterpreterObject]: self.current_node = cur if isinstance(cur, mparser.FunctionNode): return self.function_call(cur) @@ -238,20 +223,18 @@ class InterpreterBase: raise ContinueRequest() elif isinstance(cur, mparser.BreakNode): raise BreakRequest() - elif isinstance(cur, self.elementary_types): - return cur else: raise InvalidCode("Unknown statement.") return None - def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> T.List[T.Union[TYPE_var, InterpreterObject]]: + def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> InterpreterObject: (arguments, kwargs) = self.reduce_arguments(cur.args) if len(kwargs) > 0: raise InvalidCode('Keyword arguments are invalid in array construction.') - return arguments + return self._holderify([_unholder(x) for x in arguments]) @FeatureNew('dict', '0.47.0') - def evaluate_dictstatement(self, cur: mparser.DictNode) -> T.Union[TYPE_var, InterpreterObject]: + 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) @@ -261,17 +244,13 @@ class InterpreterBase: return str_key arguments, kwargs = self.reduce_arguments(cur.args, key_resolver=resolve_key, duplicate_key_error='Duplicate dictionary key: {}') assert not arguments - return kwargs + return self._holderify({k: _unholder(v) for k, v in kwargs.items()}) - @_holderify_result((bool, Disabler)) - def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[TYPE_var, InterpreterObject]: + def evaluate_notstatement(self, cur: mparser.NotNode) -> InterpreterObject: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): return 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) + return self._holderify(v.operator_call(MesonOperator.NOT, None)) def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]: assert isinstance(node, mparser.IfClauseNode) @@ -284,10 +263,10 @@ class InterpreterBase: 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): + 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.') - if result: + if res: prev_meson_version = mesonlib.project_meson_versions[self.subproject] if self.tmp_meson_version: mesonlib.project_meson_versions[self.subproject] = self.tmp_meson_version @@ -300,20 +279,7 @@ class InterpreterBase: self.evaluate_codeblock(node.elseblock) return None - def validate_comparison_types(self, val1: T.Any, val2: T.Any) -> bool: - if type(val1) != type(val2): - return False - return True - - 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]: + def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject: val1 = self.evaluate_statement(node.left) if isinstance(val1, Disabler): return val1 @@ -334,105 +300,42 @@ class InterpreterBase: }[node.ctype] # 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 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 val1.operator_call(operator, _unholder(val2)) - - # OLD CODE, based on the builtin types -- remove once we have switched - # over to all ObjectHolders. - - # Do not compare the ObjectHolders but the actual held objects - val1 = _unholder(val1) - val2 = _unholder(val2) - if node.ctype == 'in': - return self._evaluate_in(val1, val2) - elif node.ctype == 'notin': - 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 - # different types, which is now an error. - if not valid and (node.ctype == '==' or node.ctype == '!='): - raise InvalidArguments(textwrap.dedent( - f''' - Trying to compare values of different types ({type(val1).__name__}, {type(val2).__name__}) using {node.ctype}. - This was deprecated and undefined behavior previously and is as of 0.60.0 a hard error. - ''' - )) - if node.ctype == '==': - return val1 == val2 - elif node.ctype == '!=': - return val1 != val2 - elif not valid: - raise InterpreterException( - 'Values of different types ({}, {}) cannot be compared using {}.'.format(type(val1).__name__, - type(val2).__name__, - node.ctype)) - elif not isinstance(val1, self.elementary_types): - raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.left, 'value', '<ERROR>'))) - elif not isinstance(val2, self.elementary_types): - raise InterpreterException('{} can only be compared for equality.'.format(getattr(node.right, 'value', '<ERROR>'))) - # Use type: ignore because mypy will complain that we are comparing two Unions, - # but we actually guarantee earlier that both types are the same - elif node.ctype == '<': - return val1 < val2 - elif node.ctype == '<=': - return val1 <= val2 - elif node.ctype == '>': - return val1 > val2 - elif node.ctype == '>=': - return val1 >= val2 - else: - raise InvalidCode('You broke my compare eval.') + if operator in (MesonOperator.IN, MesonOperator.NOT_IN): + val1, val2 = val2, val1 - @_holderify_result((bool, Disabler)) - def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[TYPE_var, InterpreterObject]: + return self._holderify(val1.operator_call(operator, _unholder(val2))) + + def evaluate_andstatement(self, cur: mparser.AndNode) -> InterpreterObject: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l - 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 + return self._holderify(l_bool) r = self.evaluate_statement(cur.right) if isinstance(r, Disabler): 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) + return self._holderify(r.operator_call(MesonOperator.BOOL, None)) - @_holderify_result((bool, Disabler)) - def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[TYPE_var, InterpreterObject]: + def evaluate_orstatement(self, cur: mparser.OrNode) -> InterpreterObject: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l - 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 + return self._holderify(l_bool) r = self.evaluate_statement(cur.right) if isinstance(r, Disabler): 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) + return self._holderify(r.operator_call(MesonOperator.BOOL, None)) - @_holderify_result() - def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[TYPE_var, InterpreterObject]: + def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> InterpreterObject: v = self.evaluate_statement(cur.value) if isinstance(v, Disabler): return v - # 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 v.operator_call(MesonOperator.UMINUS, None) + return self._holderify(v.operator_call(MesonOperator.UMINUS, None)) - def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[TYPE_var, InterpreterObject]: + def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> InterpreterObject: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): return l @@ -440,53 +343,21 @@ class InterpreterBase: if isinstance(r, Disabler): return r - # New code based on InterpreterObjects - if isinstance(l, InterpreterObject): - mapping: T.Dict[str, MesonOperator] = { - 'add': MesonOperator.PLUS, - 'sub': MesonOperator.MINUS, - 'mul': MesonOperator.TIMES, - 'div': MesonOperator.DIV, - 'mod': MesonOperator.MOD, - } - res = l.operator_call(mapping[cur.operation], _unholder(r)) - return self._holderify(res) - - # OLD CODE, based on the builtin types -- remove once we have switched - # over to all ObjectHolders. - - if cur.operation == 'add': - if isinstance(l, dict) and isinstance(r, dict): - return {**l, **r} - try: - # MyPy error due to handling two Unions (we are catching all exceptions anyway) - return l + r # type: ignore - except Exception as e: - raise InvalidCode('Invalid use of addition: ' + str(e)) - elif cur.operation == 'sub': - if not isinstance(l, int) or not isinstance(r, int): - raise InvalidCode('Subtraction works only with integers.') - raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!') - elif cur.operation == 'mul': - if not isinstance(l, int) or not isinstance(r, int): - raise InvalidCode('Multiplication works only with integers.') - raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!') - elif cur.operation == 'div': - raise mesonlib.MesonBugException('The integer or string was not held by an ObjectHolder!') - elif cur.operation == 'mod': - if not isinstance(l, int) or not isinstance(r, int): - raise InvalidCode('Modulo works only with integers.') - raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!') - else: - raise InvalidCode('You broke me.') - - def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Union[TYPE_var, InterpreterObject]: + mapping: T.Dict[str, MesonOperator] = { + 'add': MesonOperator.PLUS, + 'sub': MesonOperator.MINUS, + 'mul': MesonOperator.TIMES, + 'div': MesonOperator.DIV, + 'mod': MesonOperator.MOD, + } + res = l.operator_call(mapping[cur.operation], _unholder(r)) + return self._holderify(res) + + def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterObject]: assert isinstance(node, mparser.TernaryNode) result = self.evaluate_statement(node.condition) if isinstance(result, Disabler): return 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) @@ -494,8 +365,7 @@ class InterpreterBase: return self.evaluate_statement(node.falseblock) @FeatureNew('format strings', '0.58.0') - @_holderify_result(str) - def evaluate_fstring(self, node: mparser.FormatStringNode) -> str: + def evaluate_fstring(self, node: mparser.FormatStringNode) -> InterpreterObject: assert isinstance(node, mparser.FormatStringNode) def replace(match: T.Match[str]) -> str: @@ -510,38 +380,37 @@ class InterpreterBase: except KeyError: raise InvalidCode(f'Identifier "{var}" does not name a variable.') - return re.sub(r'@([_a-zA-Z][_0-9a-zA-Z]*)@', replace, node.value) + res = re.sub(r'@([_a-zA-Z][_0-9a-zA-Z]*)@', replace, node.value) + return self._holderify(res) def evaluate_foreach(self, node: mparser.ForeachClauseNode) -> None: assert isinstance(node, mparser.ForeachClauseNode) items = self.evaluate_statement(node.items) - - if isinstance(items, (list, RangeHolder)): - if len(node.varnames) != 1: - raise InvalidArguments('Foreach on array does not unpack') - varname = node.varnames[0] - for item in items: - self.set_variable(varname, self._holderify(item, permissive=True)) - try: - self.evaluate_codeblock(node.block) - except ContinueRequest: - continue - except BreakRequest: - break - elif isinstance(items, dict): - if len(node.varnames) != 2: - raise InvalidArguments('Foreach on dict unpacks key and value') - for key, value in sorted(items.items()): - self.set_variable(node.varnames[0], self._holderify(key)) - self.set_variable(node.varnames[1], self._holderify(value, permissive=True)) - try: - self.evaluate_codeblock(node.block) - except ContinueRequest: - continue - except BreakRequest: - break - else: - raise InvalidArguments('Items of foreach loop must be an array or a dict') + if not isinstance(items, IterableObject): + raise InvalidArguments('Items of foreach loop do not support iterating') + + tsize = items.iter_tuple_size() + if len(node.varnames) != (tsize or 1): + raise InvalidArguments(f'Foreach expects exactly {tsize or 1} variables for iterating over objects of type {items.display_name()}') + + for i in items.iter_self(): + if tsize is None: + if isinstance(i, tuple): + raise mesonlib.MesonBugException(f'Iteration of {items} returned a tuple even though iter_tuple_size() is None') + self.set_variable(node.varnames[0], self._holderify(i)) + else: + if not isinstance(i, tuple): + raise mesonlib.MesonBugException(f'Iteration of {items} did not return a tuple even though iter_tuple_size() is {tsize}') + if len(i) != tsize: + raise mesonlib.MesonBugException(f'Iteration of {items} did not return a tuple even though iter_tuple_size() is {tsize}') + for j in range(tsize): + self.set_variable(node.varnames[j], self._holderify(i[j])) + try: + self.evaluate_codeblock(node.block) + except ContinueRequest: + continue + except BreakRequest: + break def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None: assert isinstance(node, mparser.PlusAssignmentNode) @@ -551,69 +420,21 @@ class InterpreterBase: # Remember that all variables are immutable. We must always create a # full new variable and then assign it. old_variable = self.get_variable(varname) - # TYPING TODO: This should only be InterpreterObject in the future - new_value: T.Union[None, TYPE_var, InterpreterObject] = None - if isinstance(old_variable, str): - if not isinstance(addition, str): - raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string') - new_value = old_variable + addition - elif isinstance(old_variable, list): - if isinstance(addition, list): - new_value = old_variable + addition - else: - new_value = old_variable + [addition] - elif isinstance(old_variable, dict): - if not isinstance(addition, dict): - raise InvalidArguments('The += operator requires a dict on the right hand side if the variable on the left is a dict') - 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))) - # Add other data types here. - else: - raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints') + new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition))) self.set_variable(varname, new_value) - def evaluate_indexing(self, node: mparser.IndexNode) -> T.Union[TYPE_elementary, InterpreterObject]: + def evaluate_indexing(self, node: mparser.IndexNode) -> InterpreterObject: assert isinstance(node, mparser.IndexNode) iobject = self.evaluate_statement(node.iobject) if isinstance(iobject, Disabler): return iobject index = _unholder(self.evaluate_statement(node.index)) - if isinstance(iobject, InterpreterObject): - return self._holderify(iobject.operator_call(MesonOperator.INDEX, index)) - if not hasattr(iobject, '__getitem__'): - raise InterpreterException( - 'Tried to index an object that doesn\'t support indexing.') - if isinstance(iobject, dict): - if not isinstance(index, str): - raise InterpreterException('Key is not a string') - try: - # The cast is required because we don't have recursive types... - return T.cast(T.Union[TYPE_elementary, InterpreterObject], iobject[index]) - except KeyError: - raise InterpreterException('Key %s is not in dict' % index) - else: - if not isinstance(index, int): - raise InterpreterException('Index value is not an integer.') - try: - # Ignore the MyPy error, since we don't know all indexable types here - # and we handle non indexable types with an exception - # TODO maybe find a better solution - res = iobject[index] # type: ignore - # Only holderify if we are dealing with `InterpreterObject`, since raw - # lists already store ObjectHolders - if isinstance(iobject, InterpreterObject): - return self._holderify(res) - else: - return res - except IndexError: - # 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 + if iobject is None: + raise InterpreterException('Tried to evaluate indexing on None') + return self._holderify(iobject.operator_call(MesonOperator.INDEX, index)) - @_holderify_result() - def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: + def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObject]: func_name = node.func_name (h_posargs, h_kwargs) = self.reduce_arguments(node.args) (posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs) @@ -626,14 +447,15 @@ 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) - return func(node, func_args, kwargs) + res = func(node, func_args, kwargs) + return self._holderify(res) if res is not None else None else: self.unknown_function_called(func_name) return None - def method_call(self, node: mparser.MethodNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: + def method_call(self, node: mparser.MethodNode) -> T.Optional[InterpreterObject]: invokable = node.source_object - obj: T.Union[TYPE_var, InterpreterObject] + obj: T.Optional[InterpreterObject] if isinstance(invokable, mparser.IdNode): object_name = invokable.value obj = self.get_variable(object_name) @@ -644,16 +466,6 @@ class InterpreterBase: (args, kwargs) = self._unholder_args(h_args, h_kwargs) if is_disabled(args, kwargs): return Disabler() - if isinstance(obj, str): - raise mesonlib.MesonBugException('Strings are now wrapped in object holders!') - if isinstance(obj, bool): - 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): - return self.array_method_call(obj, method_name, args, kwargs) - if isinstance(obj, dict): - return self.dict_method_call(obj, method_name, args, kwargs) if not isinstance(obj, InterpreterObject): raise InvalidArguments('Variable "%s" is not callable.' % object_name) # TODO: InterpreterBase **really** shouldn't be in charge of checking this @@ -663,17 +475,11 @@ class InterpreterBase: elif not isinstance(obj, Disabler): raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}" of type {type(obj).__name__}') obj.current_node = node - return self._holderify(obj.method_call(method_name, args, kwargs)) + res = obj.method_call(method_name, args, kwargs) + return self._holderify(res) if res is not None else None - def _holderify(self, res: T.Union[TYPE_var, InterpreterObject, None], *, permissive: bool = False) -> T.Union[TYPE_elementary, InterpreterObject]: - # TODO: remove `permissive` once all primitives are ObjectHolders - if res is None: - return None - elif isinstance(res, list): - return [self._holderify(x, permissive=permissive) for x in res] - elif isinstance(res, dict): - return {k: self._holderify(v, permissive=permissive) for k, v in res.items()} - elif isinstance(res, HoldableTypes): + def _holderify(self, res: T.Union[TYPE_var, InterpreterObject]) -> InterpreterObject: + if isinstance(res, HoldableTypes): # Always check for an exact match first. cls = self.holder_map.get(type(res), None) if cls is not None: @@ -686,136 +492,42 @@ class InterpreterBase: return cls(res, T.cast('Interpreter', self)) raise mesonlib.MesonBugException(f'Object {res} of type {type(res).__name__} is neither in self.holder_map nor self.bound_holder_map.') elif isinstance(res, ObjectHolder): - if permissive: - return res raise mesonlib.MesonBugException(f'Returned object {res} of type {type(res).__name__} is an object holder.') elif isinstance(res, MesonInterpreterObject): return res raise mesonlib.MesonBugException(f'Unknown returned object {res} of type {type(res).__name__} in the parameters.') def _unholder_args(self, - args: T.List[T.Union[TYPE_var, InterpreterObject]], - kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]: + args: T.List[InterpreterObject], + kwargs: T.Dict[str, 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()} - @staticmethod - def _get_one_string_posarg(posargs: T.List[TYPE_var], method_name: str) -> str: - if len(posargs) > 1: - raise InterpreterException(f'{method_name}() must have zero or one arguments') - elif len(posargs) == 1: - s = posargs[0] - if not isinstance(s, str): - raise InterpreterException(f'{method_name}() argument must be a string') - return s - return None - def unknown_function_called(self, func_name: str) -> None: raise InvalidCode('Unknown function "%s".' % func_name) - @noKwargs - def array_method_call(self, - obj: T.List[T.Union[TYPE_elementary, InterpreterObject]], - method_name: str, - posargs: T.List[TYPE_var], - kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: - if method_name == 'contains': - def check_contains(el: T.List[TYPE_var]) -> bool: - if len(posargs) != 1: - raise InterpreterException('Contains method takes exactly one argument.') - item = posargs[0] - for element in el: - if isinstance(element, list): - found = check_contains(element) - if found: - return True - if element == item: - return True - return False - return self._holderify(check_contains([_unholder(x) for x in obj])) - elif method_name == 'length': - return self._holderify(len(obj)) - elif method_name == 'get': - index = posargs[0] - fallback = None - if len(posargs) == 2: - fallback = self._holderify(posargs[1]) - elif len(posargs) > 2: - m = 'Array method \'get()\' only takes two arguments: the ' \ - 'index and an optional fallback value if the index is ' \ - 'out of range.' - raise InvalidArguments(m) - if not isinstance(index, int): - raise InvalidArguments('Array index must be a number.') - if index < -len(obj) or index >= len(obj): - if fallback is None: - m = 'Array index {!r} is out of bounds for array of size {!r}.' - raise InvalidArguments(m.format(index, len(obj))) - if isinstance(fallback, mparser.BaseNode): - return self.evaluate_statement(fallback) - return fallback - return obj[index] - raise InterpreterException(f'Arrays do not have a method called {method_name!r}.') - - @noKwargs - def dict_method_call(self, - obj: T.Dict[str, T.Union[TYPE_elementary, InterpreterObject]], - method_name: str, - posargs: T.List[TYPE_var], - kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: - if method_name in ('has_key', 'get'): - if method_name == 'has_key': - if len(posargs) != 1: - raise InterpreterException('has_key() takes exactly one argument.') - else: - if len(posargs) not in (1, 2): - raise InterpreterException('get() takes one or two arguments.') - - key = posargs[0] - if not isinstance(key, (str)): - raise InvalidArguments('Dictionary key must be a string.') - - has_key = key in obj - - if method_name == 'has_key': - return self._holderify(has_key) - - if has_key: - return obj[key] - - if len(posargs) == 2: - fallback = self._holderify(posargs[1]) - if isinstance(fallback, mparser.BaseNode): - return self.evaluate_statement(fallback) - return fallback - - raise InterpreterException(f'Key {key!r} is not in the dictionary.') - - if method_name == 'keys': - if len(posargs) != 0: - raise InterpreterException('keys() takes no arguments.') - return sorted(obj.keys()) - - raise InterpreterException('Dictionaries do not have a method called "%s".' % method_name) - def reduce_arguments( self, args: mparser.ArgumentNode, key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key, duplicate_key_error: T.Optional[str] = None, ) -> T.Tuple[ - T.List[T.Union[TYPE_var, InterpreterObject]], - T.Dict[str, T.Union[TYPE_var, InterpreterObject]] + T.List[InterpreterObject], + T.Dict[str, InterpreterObject] ]: assert isinstance(args, mparser.ArgumentNode) if args.incorrect_order(): raise InvalidArguments('All keyword arguments must be after positional arguments.') self.argument_depth += 1 - reduced_pos: T.List[T.Union[TYPE_var, InterpreterObject]] = [self.evaluate_statement(arg) for arg in args.arguments] - reduced_kw: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} + 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.') + 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 duplicate_key_error and reduced_key in reduced_kw: raise InvalidArguments(duplicate_key_error.format(reduced_key)) reduced_kw[reduced_key] = reduced_val @@ -823,10 +535,10 @@ class InterpreterBase: final_kw = self.expand_default_kwargs(reduced_kw) return reduced_pos, final_kw - def expand_default_kwargs(self, kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Dict[str, T.Union[TYPE_var, InterpreterObject]]: + def expand_default_kwargs(self, kwargs: T.Dict[str, T.Optional[InterpreterObject]]) -> T.Dict[str, T.Optional[InterpreterObject]]: if 'kwargs' not in kwargs: return kwargs - to_expand = kwargs.pop('kwargs') + to_expand = _unholder(kwargs.pop('kwargs')) if not isinstance(to_expand, dict): raise InterpreterException('Value of "kwargs" must be dictionary.') if 'kwargs' in to_expand: @@ -834,20 +546,20 @@ class InterpreterBase: for k, v in to_expand.items(): if k in kwargs: raise InterpreterException(f'Entry "{k}" defined both as a keyword argument and in a "kwarg" entry.') - kwargs[k] = v + kwargs[k] = self._holderify(v) return kwargs def assignment(self, node: mparser.AssignmentNode) -> None: assert isinstance(node, mparser.AssignmentNode) if self.argument_depth != 0: - raise InvalidArguments('''Tried to assign values inside an argument list. -To specify a keyword argument, use : instead of =.''') + raise InvalidArguments(textwrap.dedent('''\ + Tried to assign values inside an argument list. + To specify a keyword argument, use : instead of =. + ''')) var_name = node.var_name if not isinstance(var_name, str): raise InvalidArguments('Tried to assign value to a non-variable.') value = self.evaluate_statement(node.value) - if not self.is_assignable(value): - raise InvalidCode(f'Tried to assign the invalid value "{value}" of type {type(value).__name__} to variable.') # For mutable objects we need to make a copy on assignment if isinstance(value, MutableInterpreterObject): value = copy.deepcopy(value) @@ -860,36 +572,23 @@ To specify a keyword argument, use : instead of =.''') if holderify: variable = self._holderify(variable) else: - # Ensure that we are never storing a HoldableObject - def check(x: T.Union[TYPE_var, InterpreterObject]) -> None: - if isinstance(x, mesonlib.HoldableObject): - raise mesonlib.MesonBugException(f'set_variable in InterpreterBase called with a HoldableObject {x} of type {type(x).__name__}') - elif isinstance(x, list): - for y in x: - check(y) - elif isinstance(x, dict): - for v in x.values(): - check(v) - check(variable) + # 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 not self.is_assignable(variable): - raise InvalidCode(f'Assigned value "{variable}" of type {type(variable).__name__} is not an assignable type.') if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None: raise InvalidCode('Invalid variable name: ' + varname) if varname in self.builtin: raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) self.variables[varname] = variable - def get_variable(self, varname: str) -> T.Union[TYPE_var, InterpreterObject]: + def get_variable(self, varname: str) -> InterpreterObject: if varname in self.builtin: return self.builtin[varname] if varname in self.variables: return self.variables[varname] raise InvalidCode('Unknown variable "%s".' % varname) - def is_assignable(self, value: T.Any) -> bool: - return isinstance(value, (InterpreterObject, list, dict)) - def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None: raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)') diff --git a/test cases/failing/11 object arithmetic/test.json b/test cases/failing/11 object arithmetic/test.json index 84f5c46..822e504 100644 --- a/test cases/failing/11 object arithmetic/test.json +++ b/test cases/failing/11 object arithmetic/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/11 object arithmetic/meson\\.build:3:0: ERROR: The `\\+` of str does not accept objects of type MesonMain .*" + "line": "test cases/failing/11 object arithmetic/meson\\.build:3:0: ERROR: The `\\+` operator of str does not accept objects of type MesonMain .*" } ] } diff --git a/test cases/failing/12 string arithmetic/test.json b/test cases/failing/12 string arithmetic/test.json index c762229..96595c8 100644 --- a/test cases/failing/12 string arithmetic/test.json +++ b/test cases/failing/12 string arithmetic/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/12 string arithmetic/meson.build:3:0: ERROR: The `+` of str does not accept objects of type int (3)" + "line": "test cases/failing/12 string arithmetic/meson.build:3:0: ERROR: The `+` operator of str does not accept objects of type int (3)" } ] } diff --git a/test cases/failing/13 array arithmetic/test.json b/test cases/failing/13 array arithmetic/test.json index 55056ce..8904775 100644 --- a/test cases/failing/13 array arithmetic/test.json +++ b/test cases/failing/13 array arithmetic/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/13 array arithmetic/meson.build:3:0: ERROR: Multiplication works only with integers." + "line": "test cases/failing/13 array arithmetic/meson.build:3:0: ERROR: Object <[ArrayHolder] holds [list]: ['a', 'b']> of type array does not support the `*` operator." } ] } diff --git a/test cases/failing/51 inconsistent comparison/test.json b/test cases/failing/51 inconsistent comparison/test.json index 5867f0a..bd26554 100644 --- a/test cases/failing/51 inconsistent comparison/test.json +++ b/test cases/failing/51 inconsistent comparison/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/51 inconsistent comparison/meson.build:5:0: ERROR: Values of different types (list, str) cannot be compared using <." + "line": "test cases/failing/51 inconsistent comparison/meson.build:5:0: ERROR: Object <[ArrayHolder] holds [list]: []> of type array does not support the `<` operator." } ] } |