interpreter: Holderify arrays and dicts

This is the final refactoring for extracting the bultin object
logic out of Interpreterbase. I decided to do both arrays and
dicts in one go since splitting it would have been a lot more
confusing.
pull/9366/head
Daniel Mensinger 3 years ago
parent b19530bd7d
commit af0587cb49
  1. 19
      mesonbuild/ast/interpreter.py
  2. 4
      mesonbuild/interpreter/__init__.py
  3. 2
      mesonbuild/interpreter/interpreter.py
  4. 4
      mesonbuild/interpreter/primitives/__init__.py
  5. 103
      mesonbuild/interpreter/primitives/array.py
  6. 87
      mesonbuild/interpreter/primitives/dict.py
  7. 2
      mesonbuild/interpreterbase/__init__.py
  8. 10
      mesonbuild/interpreterbase/_unholder.py
  9. 31
      mesonbuild/interpreterbase/baseobjects.py
  10. 507
      mesonbuild/interpreterbase/interpreterbase.py
  11. 2
      test cases/failing/11 object arithmetic/test.json
  12. 2
      test cases/failing/12 string arithmetic/test.json
  13. 2
      test cases/failing/13 array arithmetic/test.json
  14. 2
      test cases/failing/51 inconsistent comparison/test.json

@ -30,6 +30,15 @@ from ..interpreterbase import (
TYPE_nkwargs, TYPE_nkwargs,
) )
from ..interpreter import (
Interpreter,
StringHolder,
BooleanHolder,
IntegerHolder,
ArrayHolder,
DictHolder,
)
from ..mparser import ( from ..mparser import (
AndNode, AndNode,
ArgumentNode, ArgumentNode,
@ -202,6 +211,9 @@ class AstInterpreter(InterpreterBase):
assert isinstance(node, mparser.FormatStringNode) assert isinstance(node, mparser.FormatStringNode)
return node.value 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: def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> int:
self.evaluate_statement(cur.left) self.evaluate_statement(cur.left)
self.evaluate_statement(cur.right) self.evaluate_statement(cur.right)
@ -364,18 +376,15 @@ class AstInterpreter(InterpreterBase):
mkwargs = {} # type: T.Dict[str, TYPE_nvar] mkwargs = {} # type: T.Dict[str, TYPE_nvar]
try: try:
if isinstance(src, str): if isinstance(src, str):
from ..interpreter import Interpreter, StringHolder
result = StringHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) result = StringHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
elif isinstance(src, bool): elif isinstance(src, bool):
from ..interpreter import Interpreter, BooleanHolder
result = BooleanHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) result = BooleanHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
elif isinstance(src, int): elif isinstance(src, int):
from ..interpreter import Interpreter, IntegerHolder
result = IntegerHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) result = IntegerHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
elif isinstance(src, list): 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): 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: except mesonlib.MesonException:
return None return None

@ -35,7 +35,9 @@ __all__ = [
'ExternalProgramHolder', 'ExternalProgramHolder',
'extract_required_kwarg', 'extract_required_kwarg',
'ArrayHolder',
'BooleanHolder', 'BooleanHolder',
'DictHolder',
'IntegerHolder', 'IntegerHolder',
'StringHolder', 'StringHolder',
] ]
@ -49,7 +51,9 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg
extract_required_kwarg) extract_required_kwarg)
from .primitives import ( from .primitives import (
ArrayHolder,
BooleanHolder, BooleanHolder,
DictHolder,
IntegerHolder, IntegerHolder,
StringHolder, StringHolder,
) )

@ -386,6 +386,8 @@ class Interpreter(InterpreterBase, HoldableObject):
''' '''
self.holder_map.update({ self.holder_map.update({
# Primitives # Primitives
list: P_OBJ.ArrayHolder,
dict: P_OBJ.DictHolder,
int: P_OBJ.IntegerHolder, int: P_OBJ.IntegerHolder,
bool: P_OBJ.BooleanHolder, bool: P_OBJ.BooleanHolder,
str: P_OBJ.StringHolder, str: P_OBJ.StringHolder,

@ -2,13 +2,17 @@
# SPDX-license-identifier: Apache-2.0 # SPDX-license-identifier: Apache-2.0
__all__ = [ __all__ = [
'ArrayHolder',
'BooleanHolder', 'BooleanHolder',
'DictHolder',
'IntegerHolder', 'IntegerHolder',
'StringHolder', 'StringHolder',
'MesonVersionString', 'MesonVersionString',
'MesonVersionStringHolder', 'MesonVersionStringHolder',
] ]
from .array import ArrayHolder
from .boolean import BooleanHolder from .boolean import BooleanHolder
from .dict import DictHolder
from .integer import IntegerHolder from .integer import IntegerHolder
from .string import StringHolder, MesonVersionString, MesonVersionStringHolder from .string import StringHolder, MesonVersionString, MesonVersionStringHolder

@ -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)}.')

@ -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]

@ -16,6 +16,7 @@ __all__ = [
'InterpreterObject', 'InterpreterObject',
'MesonInterpreterObject', 'MesonInterpreterObject',
'ObjectHolder', 'ObjectHolder',
'IterableObject',
'RangeHolder', 'RangeHolder',
'MutableInterpreterObject', 'MutableInterpreterObject',
@ -76,6 +77,7 @@ from .baseobjects import (
InterpreterObject, InterpreterObject,
MesonInterpreterObject, MesonInterpreterObject,
ObjectHolder, ObjectHolder,
IterableObject,
RangeHolder, RangeHolder,
MutableInterpreterObject, MutableInterpreterObject,

@ -16,14 +16,8 @@ from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder
from .exceptions import InvalidArguments from .exceptions import InvalidArguments
from ..mesonlib import HoldableObject, MesonBugException from ..mesonlib import HoldableObject, MesonBugException
import typing as T def _unholder(obj: InterpreterObject) -> TYPE_var:
if isinstance(obj, ObjectHolder):
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):
assert isinstance(obj.held_object, HoldableTypes) assert isinstance(obj.held_object, HoldableTypes)
return obj.held_object return obj.held_object
elif isinstance(obj, MesonInterpreterObject): elif isinstance(obj, MesonInterpreterObject):

@ -20,6 +20,7 @@ from ..mesonlib import HoldableObject, MesonBugException
import textwrap import textwrap
import typing as T import typing as T
from abc import ABCMeta
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
# Object holders need the actual interpreter # Object holders need the actual interpreter
@ -54,7 +55,7 @@ class InterpreterObject:
self.trivial_operators: T.Dict[ self.trivial_operators: T.Dict[
MesonOperator, MesonOperator,
T.Tuple[ T.Tuple[
T.Type[T.Union[TYPE_var, T.Tuple[TYPE_var, ...]]], T.Union[T.Type, T.Tuple[T.Type, ...]],
'OperatorCall' 'OperatorCall'
] ]
] = {} ] = {}
@ -94,7 +95,7 @@ class InterpreterObject:
if op[0] is None and other is not None: 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__}') 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]): 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) return op[1](other)
if operator in self.operators: if operator in self.operators:
return self.operators[operator](other) return self.operators[operator](other)
@ -128,8 +129,8 @@ class MesonInterpreterObject(InterpreterObject):
class MutableInterpreterObject: class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable ''' ''' Dummy class to mark the object type as mutable '''
HoldableTypes = (HoldableObject, int, bool, str) HoldableTypes = (HoldableObject, int, bool, str, list, dict)
TYPE_HoldableTypes = T.Union[HoldableObject, int, bool, str] TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject]
InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)
class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
@ -163,7 +164,20 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>' 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: def __init__(self, start: int, stop: int, step: int, *, subproject: str) -> None:
super().__init__(subproject=subproject) super().__init__(subproject=subproject)
self.range = range(start, stop, step) self.range = range(start, stop, step)
@ -177,8 +191,11 @@ class RangeHolder(MesonInterpreterObject):
except: except:
raise InvalidArguments(f'Index {other} out of bounds of range.') 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) return iter(self.range)
def __len__(self) -> int: def size(self) -> int:
return len(self.range) return len(self.range)

@ -24,9 +24,8 @@ from .baseobjects import (
MutableInterpreterObject, MutableInterpreterObject,
InterpreterObjectTypeVar, InterpreterObjectTypeVar,
ObjectHolder, ObjectHolder,
RangeHolder, IterableObject,
TYPE_elementary,
TYPE_var, TYPE_var,
TYPE_kwargs, TYPE_kwargs,
@ -42,7 +41,7 @@ from .exceptions import (
BreakRequest BreakRequest
) )
from .decorators import FeatureNew, noKwargs from .decorators import FeatureNew
from .disabler import Disabler, is_disabled from .disabler import Disabler, is_disabled
from .helpers import default_resolve_key, flatten, resolve_second_level_holders from .helpers import default_resolve_key, flatten, resolve_second_level_holders
from .operator import MesonOperator from .operator import MesonOperator
@ -51,7 +50,6 @@ from ._unholder import _unholder
import os, copy, re, pathlib import os, copy, re, pathlib
import typing as T import typing as T
import textwrap import textwrap
from functools import wraps
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
# T.cast is not handled by flake8 to detect quoted annotation use # T.cast is not handled by flake8 to detect quoted annotation use
@ -64,6 +62,8 @@ HolderMapType = T.Dict[
T.Type[int], T.Type[int],
T.Type[bool], T.Type[bool],
T.Type[str], 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] # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar]
T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], 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] 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: class InterpreterBase:
elementary_types = (list, )
def __init__(self, source_root: str, subdir: str, subproject: str): def __init__(self, source_root: str, subdir: str, subproject: str):
self.source_root = source_root self.source_root = source_root
self.funcs: FunctionType = {} self.funcs: FunctionType = {}
@ -99,8 +85,7 @@ class InterpreterBase:
self.subdir = subdir self.subdir = subdir
self.root_subdir = subdir self.root_subdir = subdir
self.subproject = subproject self.subproject = subproject
# TODO: This should actually be more strict: T.Union[TYPE_elementary, InterpreterObject] self.variables: T.Dict[str, InterpreterObject] = {}
self.variables: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {}
self.argument_depth = 0 self.argument_depth = 0
self.current_lineno = -1 self.current_lineno = -1
# Current node set during a function call. This can be used as location # Current node set during a function call. This can be used as location
@ -190,7 +175,7 @@ class InterpreterBase:
raise e raise e
i += 1 # In THE FUTURE jump over blocks and stuff. 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 self.current_node = cur
if isinstance(cur, mparser.FunctionNode): if isinstance(cur, mparser.FunctionNode):
return self.function_call(cur) return self.function_call(cur)
@ -238,20 +223,18 @@ class InterpreterBase:
raise ContinueRequest() raise ContinueRequest()
elif isinstance(cur, mparser.BreakNode): elif isinstance(cur, mparser.BreakNode):
raise BreakRequest() raise BreakRequest()
elif isinstance(cur, self.elementary_types):
return cur
else: else:
raise InvalidCode("Unknown statement.") raise InvalidCode("Unknown statement.")
return None 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) (arguments, kwargs) = self.reduce_arguments(cur.args)
if len(kwargs) > 0: if len(kwargs) > 0:
raise InvalidCode('Keyword arguments are invalid in array construction.') 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') @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: def resolve_key(key: mparser.BaseNode) -> str:
if not isinstance(key, mparser.StringNode): if not isinstance(key, mparser.StringNode):
FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject) FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject)
@ -261,17 +244,13 @@ class InterpreterBase:
return str_key return str_key
arguments, kwargs = self.reduce_arguments(cur.args, key_resolver=resolve_key, duplicate_key_error='Duplicate dictionary key: {}') arguments, kwargs = self.reduce_arguments(cur.args, key_resolver=resolve_key, duplicate_key_error='Duplicate dictionary key: {}')
assert not arguments 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) -> InterpreterObject:
def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[TYPE_var, InterpreterObject]:
v = self.evaluate_statement(cur.value) v = self.evaluate_statement(cur.value)
if isinstance(v, Disabler): if isinstance(v, Disabler):
return v return v
# TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects return self._holderify(v.operator_call(MesonOperator.NOT, None))
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]: def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
assert isinstance(node, mparser.IfClauseNode) assert isinstance(node, mparser.IfClauseNode)
@ -284,10 +263,10 @@ class InterpreterBase:
return result return result
if not isinstance(result, InterpreterObject): if not isinstance(result, InterpreterObject):
raise mesonlib.MesonBugException(f'Argument to not ({result}) is not an InterpreterObject but {type(result).__name__}.') raise mesonlib.MesonBugException(f'Argument to not ({result}) is not an InterpreterObject but {type(result).__name__}.')
result = result.operator_call(MesonOperator.BOOL, None) res = result.operator_call(MesonOperator.BOOL, None)
if not isinstance(result, bool): if not isinstance(res, bool):
raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.') 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] prev_meson_version = mesonlib.project_meson_versions[self.subproject]
if self.tmp_meson_version: if self.tmp_meson_version:
mesonlib.project_meson_versions[self.subproject] = 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) self.evaluate_codeblock(node.elseblock)
return None return None
def validate_comparison_types(self, val1: T.Any, val2: T.Any) -> bool: def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject:
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]:
val1 = self.evaluate_statement(node.left) val1 = self.evaluate_statement(node.left)
if isinstance(val1, Disabler): if isinstance(val1, Disabler):
return val1 return val1
@ -334,105 +300,42 @@ class InterpreterBase:
}[node.ctype] }[node.ctype]
# Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`) # 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): if operator in (MesonOperator.IN, MesonOperator.NOT_IN):
return val2.operator_call(operator, _unholder(val1)) val1, val2 = val2, 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.')
@_holderify_result((bool, Disabler)) return self._holderify(val1.operator_call(operator, _unholder(val2)))
def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[TYPE_var, InterpreterObject]:
def evaluate_andstatement(self, cur: mparser.AndNode) -> InterpreterObject:
l = self.evaluate_statement(cur.left) l = self.evaluate_statement(cur.left)
if isinstance(l, Disabler): if isinstance(l, Disabler):
return l 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) l_bool = l.operator_call(MesonOperator.BOOL, None)
if not l_bool: if not l_bool:
return l_bool return self._holderify(l_bool)
r = self.evaluate_statement(cur.right) r = self.evaluate_statement(cur.right)
if isinstance(r, Disabler): if isinstance(r, Disabler):
return r return r
if not isinstance(r, InterpreterObject): return self._holderify(r.operator_call(MesonOperator.BOOL, None))
raise mesonlib.MesonBugException(f'Second argument to and ({r}) is not an InterpreterObject but {type(r).__name__}.')
return r.operator_call(MesonOperator.BOOL, None)
@_holderify_result((bool, Disabler)) def evaluate_orstatement(self, cur: mparser.OrNode) -> InterpreterObject:
def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[TYPE_var, InterpreterObject]:
l = self.evaluate_statement(cur.left) l = self.evaluate_statement(cur.left)
if isinstance(l, Disabler): if isinstance(l, Disabler):
return l 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) l_bool = l.operator_call(MesonOperator.BOOL, None)
if l_bool: if l_bool:
return l_bool return self._holderify(l_bool)
r = self.evaluate_statement(cur.right) r = self.evaluate_statement(cur.right)
if isinstance(r, Disabler): if isinstance(r, Disabler):
return r return r
if not isinstance(r, InterpreterObject): return self._holderify(r.operator_call(MesonOperator.BOOL, None))
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) -> InterpreterObject:
def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[TYPE_var, InterpreterObject]:
v = self.evaluate_statement(cur.value) v = self.evaluate_statement(cur.value)
if isinstance(v, Disabler): if isinstance(v, Disabler):
return v return v
# TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects return self._holderify(v.operator_call(MesonOperator.UMINUS, None))
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)
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) l = self.evaluate_statement(cur.left)
if isinstance(l, Disabler): if isinstance(l, Disabler):
return l return l
@ -440,53 +343,21 @@ class InterpreterBase:
if isinstance(r, Disabler): if isinstance(r, Disabler):
return r return r
# New code based on InterpreterObjects mapping: T.Dict[str, MesonOperator] = {
if isinstance(l, InterpreterObject): 'add': MesonOperator.PLUS,
mapping: T.Dict[str, MesonOperator] = { 'sub': MesonOperator.MINUS,
'add': MesonOperator.PLUS, 'mul': MesonOperator.TIMES,
'sub': MesonOperator.MINUS, 'div': MesonOperator.DIV,
'mul': MesonOperator.TIMES, 'mod': MesonOperator.MOD,
'div': MesonOperator.DIV, }
'mod': MesonOperator.MOD, res = l.operator_call(mapping[cur.operation], _unholder(r))
} return self._holderify(res)
res = l.operator_call(mapping[cur.operation], _unholder(r))
return self._holderify(res) def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterObject]:
# 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]:
assert isinstance(node, mparser.TernaryNode) assert isinstance(node, mparser.TernaryNode)
result = self.evaluate_statement(node.condition) result = self.evaluate_statement(node.condition)
if isinstance(result, Disabler): if isinstance(result, Disabler):
return result 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) result_bool = result.operator_call(MesonOperator.BOOL, None)
if result_bool: if result_bool:
return self.evaluate_statement(node.trueblock) return self.evaluate_statement(node.trueblock)
@ -494,8 +365,7 @@ class InterpreterBase:
return self.evaluate_statement(node.falseblock) return self.evaluate_statement(node.falseblock)
@FeatureNew('format strings', '0.58.0') @FeatureNew('format strings', '0.58.0')
@_holderify_result(str) def evaluate_fstring(self, node: mparser.FormatStringNode) -> InterpreterObject:
def evaluate_fstring(self, node: mparser.FormatStringNode) -> str:
assert isinstance(node, mparser.FormatStringNode) assert isinstance(node, mparser.FormatStringNode)
def replace(match: T.Match[str]) -> str: def replace(match: T.Match[str]) -> str:
@ -510,38 +380,37 @@ class InterpreterBase:
except KeyError: except KeyError:
raise InvalidCode(f'Identifier "{var}" does not name a variable.') 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: def evaluate_foreach(self, node: mparser.ForeachClauseNode) -> None:
assert isinstance(node, mparser.ForeachClauseNode) assert isinstance(node, mparser.ForeachClauseNode)
items = self.evaluate_statement(node.items) items = self.evaluate_statement(node.items)
if not isinstance(items, IterableObject):
if isinstance(items, (list, RangeHolder)): raise InvalidArguments('Items of foreach loop do not support iterating')
if len(node.varnames) != 1:
raise InvalidArguments('Foreach on array does not unpack') tsize = items.iter_tuple_size()
varname = node.varnames[0] if len(node.varnames) != (tsize or 1):
for item in items: raise InvalidArguments(f'Foreach expects exactly {tsize or 1} variables for iterating over objects of type {items.display_name()}')
self.set_variable(varname, self._holderify(item, permissive=True))
try: for i in items.iter_self():
self.evaluate_codeblock(node.block) if tsize is None:
except ContinueRequest: if isinstance(i, tuple):
continue raise mesonlib.MesonBugException(f'Iteration of {items} returned a tuple even though iter_tuple_size() is None')
except BreakRequest: self.set_variable(node.varnames[0], self._holderify(i))
break else:
elif isinstance(items, dict): if not isinstance(i, tuple):
if len(node.varnames) != 2: raise mesonlib.MesonBugException(f'Iteration of {items} did not return a tuple even though iter_tuple_size() is {tsize}')
raise InvalidArguments('Foreach on dict unpacks key and value') if len(i) != tsize:
for key, value in sorted(items.items()): raise mesonlib.MesonBugException(f'Iteration of {items} did not return a tuple even though iter_tuple_size() is {tsize}')
self.set_variable(node.varnames[0], self._holderify(key)) for j in range(tsize):
self.set_variable(node.varnames[1], self._holderify(value, permissive=True)) self.set_variable(node.varnames[j], self._holderify(i[j]))
try: try:
self.evaluate_codeblock(node.block) self.evaluate_codeblock(node.block)
except ContinueRequest: except ContinueRequest:
continue continue
except BreakRequest: except BreakRequest:
break break
else:
raise InvalidArguments('Items of foreach loop must be an array or a dict')
def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None: def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None:
assert isinstance(node, mparser.PlusAssignmentNode) assert isinstance(node, mparser.PlusAssignmentNode)
@ -551,69 +420,21 @@ class InterpreterBase:
# Remember that all variables are immutable. We must always create a # Remember that all variables are immutable. We must always create a
# full new variable and then assign it. # full new variable and then assign it.
old_variable = self.get_variable(varname) old_variable = self.get_variable(varname)
# TYPING TODO: This should only be InterpreterObject in the future new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition)))
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')
self.set_variable(varname, new_value) 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) assert isinstance(node, mparser.IndexNode)
iobject = self.evaluate_statement(node.iobject) iobject = self.evaluate_statement(node.iobject)
if isinstance(iobject, Disabler): if isinstance(iobject, Disabler):
return iobject return iobject
index = _unholder(self.evaluate_statement(node.index)) index = _unholder(self.evaluate_statement(node.index))
if isinstance(iobject, InterpreterObject): if iobject is None:
return self._holderify(iobject.operator_call(MesonOperator.INDEX, index)) raise InterpreterException('Tried to evaluate indexing on None')
if not hasattr(iobject, '__getitem__'): return self._holderify(iobject.operator_call(MesonOperator.INDEX, index))
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
@_holderify_result() def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObject]:
def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]:
func_name = node.func_name func_name = node.func_name
(h_posargs, h_kwargs) = self.reduce_arguments(node.args) (h_posargs, h_kwargs) = self.reduce_arguments(node.args)
(posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs) (posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs)
@ -626,14 +447,15 @@ class InterpreterBase:
func_args = flatten(posargs) func_args = flatten(posargs)
if not getattr(func, 'no-second-level-holder-flattening', False): if not getattr(func, 'no-second-level-holder-flattening', False):
func_args, kwargs = resolve_second_level_holders(func_args, kwargs) 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: else:
self.unknown_function_called(func_name) self.unknown_function_called(func_name)
return None 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 invokable = node.source_object
obj: T.Union[TYPE_var, InterpreterObject] obj: T.Optional[InterpreterObject]
if isinstance(invokable, mparser.IdNode): if isinstance(invokable, mparser.IdNode):
object_name = invokable.value object_name = invokable.value
obj = self.get_variable(object_name) obj = self.get_variable(object_name)
@ -644,16 +466,6 @@ class InterpreterBase:
(args, kwargs) = self._unholder_args(h_args, h_kwargs) (args, kwargs) = self._unholder_args(h_args, h_kwargs)
if is_disabled(args, kwargs): if is_disabled(args, kwargs):
return Disabler() 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): if not isinstance(obj, InterpreterObject):
raise InvalidArguments('Variable "%s" is not callable.' % object_name) raise InvalidArguments('Variable "%s" is not callable.' % object_name)
# TODO: InterpreterBase **really** shouldn't be in charge of checking this # TODO: InterpreterBase **really** shouldn't be in charge of checking this
@ -663,17 +475,11 @@ class InterpreterBase:
elif not isinstance(obj, Disabler): elif not isinstance(obj, Disabler):
raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}" of type {type(obj).__name__}') raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}" of type {type(obj).__name__}')
obj.current_node = node 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]: def _holderify(self, res: T.Union[TYPE_var, InterpreterObject]) -> InterpreterObject:
# TODO: remove `permissive` once all primitives are ObjectHolders if isinstance(res, HoldableTypes):
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):
# Always check for an exact match first. # Always check for an exact match first.
cls = self.holder_map.get(type(res), None) cls = self.holder_map.get(type(res), None)
if cls is not None: if cls is not None:
@ -686,136 +492,42 @@ class InterpreterBase:
return cls(res, T.cast('Interpreter', self)) 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.') 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): elif isinstance(res, ObjectHolder):
if permissive:
return res
raise mesonlib.MesonBugException(f'Returned object {res} of type {type(res).__name__} is an object holder.') raise mesonlib.MesonBugException(f'Returned object {res} of type {type(res).__name__} is an object holder.')
elif isinstance(res, MesonInterpreterObject): elif isinstance(res, MesonInterpreterObject):
return res return res
raise mesonlib.MesonBugException(f'Unknown returned object {res} of type {type(res).__name__} in the parameters.') raise mesonlib.MesonBugException(f'Unknown returned object {res} of type {type(res).__name__} in the parameters.')
def _unholder_args(self, def _unholder_args(self,
args: T.List[T.Union[TYPE_var, InterpreterObject]], args: T.List[InterpreterObject],
kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]: 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()} 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: def unknown_function_called(self, func_name: str) -> None:
raise InvalidCode('Unknown function "%s".' % func_name) 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( def reduce_arguments(
self, self,
args: mparser.ArgumentNode, args: mparser.ArgumentNode,
key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key, key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key,
duplicate_key_error: T.Optional[str] = None, duplicate_key_error: T.Optional[str] = None,
) -> T.Tuple[ ) -> T.Tuple[
T.List[T.Union[TYPE_var, InterpreterObject]], T.List[InterpreterObject],
T.Dict[str, T.Union[TYPE_var, InterpreterObject]] T.Dict[str, InterpreterObject]
]: ]:
assert isinstance(args, mparser.ArgumentNode) assert isinstance(args, mparser.ArgumentNode)
if args.incorrect_order(): if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.') raise InvalidArguments('All keyword arguments must be after positional arguments.')
self.argument_depth += 1 self.argument_depth += 1
reduced_pos: T.List[T.Union[TYPE_var, InterpreterObject]] = [self.evaluate_statement(arg) for arg in args.arguments] reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
reduced_kw: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} 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(): for key, val in args.kwargs.items():
reduced_key = key_resolver(key) reduced_key = key_resolver(key)
assert isinstance(val, mparser.BaseNode) assert isinstance(val, mparser.BaseNode)
reduced_val = self.evaluate_statement(val) 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: if duplicate_key_error and reduced_key in reduced_kw:
raise InvalidArguments(duplicate_key_error.format(reduced_key)) raise InvalidArguments(duplicate_key_error.format(reduced_key))
reduced_kw[reduced_key] = reduced_val reduced_kw[reduced_key] = reduced_val
@ -823,10 +535,10 @@ class InterpreterBase:
final_kw = self.expand_default_kwargs(reduced_kw) final_kw = self.expand_default_kwargs(reduced_kw)
return reduced_pos, final_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: if 'kwargs' not in kwargs:
return kwargs return kwargs
to_expand = kwargs.pop('kwargs') to_expand = _unholder(kwargs.pop('kwargs'))
if not isinstance(to_expand, dict): if not isinstance(to_expand, dict):
raise InterpreterException('Value of "kwargs" must be dictionary.') raise InterpreterException('Value of "kwargs" must be dictionary.')
if 'kwargs' in to_expand: if 'kwargs' in to_expand:
@ -834,20 +546,20 @@ class InterpreterBase:
for k, v in to_expand.items(): for k, v in to_expand.items():
if k in kwargs: if k in kwargs:
raise InterpreterException(f'Entry "{k}" defined both as a keyword argument and in a "kwarg" entry.') 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 return kwargs
def assignment(self, node: mparser.AssignmentNode) -> None: def assignment(self, node: mparser.AssignmentNode) -> None:
assert isinstance(node, mparser.AssignmentNode) assert isinstance(node, mparser.AssignmentNode)
if self.argument_depth != 0: if self.argument_depth != 0:
raise InvalidArguments('''Tried to assign values inside an argument list. raise InvalidArguments(textwrap.dedent('''\
To specify a keyword argument, use : instead of =.''') Tried to assign values inside an argument list.
To specify a keyword argument, use : instead of =.
'''))
var_name = node.var_name var_name = node.var_name
if not isinstance(var_name, str): if not isinstance(var_name, str):
raise InvalidArguments('Tried to assign value to a non-variable.') raise InvalidArguments('Tried to assign value to a non-variable.')
value = self.evaluate_statement(node.value) 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 # For mutable objects we need to make a copy on assignment
if isinstance(value, MutableInterpreterObject): if isinstance(value, MutableInterpreterObject):
value = copy.deepcopy(value) value = copy.deepcopy(value)
@ -860,36 +572,23 @@ To specify a keyword argument, use : instead of =.''')
if holderify: if holderify:
variable = self._holderify(variable) variable = self._holderify(variable)
else: else:
# Ensure that we are never storing a HoldableObject # Ensure that we are always storing ObjectHolders
def check(x: T.Union[TYPE_var, InterpreterObject]) -> None: if not isinstance(variable, InterpreterObject):
if isinstance(x, mesonlib.HoldableObject): raise mesonlib.MesonBugException(f'set_variable in InterpreterBase called with a non InterpreterObject {variable} of type {type(variable).__name__}')
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)
if not isinstance(varname, str): if not isinstance(varname, str):
raise InvalidCode('First argument to set_variable must be a string.') 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: if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None:
raise InvalidCode('Invalid variable name: ' + varname) raise InvalidCode('Invalid variable name: ' + varname)
if varname in self.builtin: if varname in self.builtin:
raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) raise InvalidCode('Tried to overwrite internal variable "%s"' % varname)
self.variables[varname] = variable 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: if varname in self.builtin:
return self.builtin[varname] return self.builtin[varname]
if varname in self.variables: if varname in self.variables:
return self.variables[varname] return self.variables[varname]
raise InvalidCode('Unknown variable "%s".' % 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: def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None:
raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)') raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)')

@ -2,7 +2,7 @@
"stdout": [ "stdout": [
{ {
"match": "re", "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 .*"
} }
] ]
} }

@ -1,7 +1,7 @@
{ {
"stdout": [ "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)"
} }
] ]
} }

@ -1,7 +1,7 @@
{ {
"stdout": [ "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."
} }
] ]
} }

@ -1,7 +1,7 @@
{ {
"stdout": [ "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."
} }
] ]
} }

Loading…
Cancel
Save