interpreter: Introduce BooleanHolder for the bool primitive

pull/9202/head
Daniel Mensinger 4 years ago
parent 86eda3c812
commit 43302d3296
  1. 3
      mesonbuild/ast/interpreter.py
  2. 2
      mesonbuild/interpreter/__init__.py
  3. 3
      mesonbuild/interpreter/interpreter.py
  4. 2
      mesonbuild/interpreter/primitives/__init__.py
  5. 53
      mesonbuild/interpreter/primitives/boolean.py
  6. 4
      mesonbuild/interpreterbase/baseobjects.py
  7. 4
      mesonbuild/interpreterbase/decorators.py
  8. 126
      mesonbuild/interpreterbase/interpreterbase.py

@ -366,7 +366,8 @@ class AstInterpreter(InterpreterBase):
if isinstance(src, str):
result = self.string_method_call(src, node.name, margs, mkwargs)
elif isinstance(src, bool):
result = self.bool_method_call(src, node.name, margs, mkwargs)
from ..interpreter import Interpreter, BooleanHolder
result = BooleanHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
elif isinstance(src, int):
from ..interpreter import Interpreter, IntegerHolder
result = IntegerHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)

@ -35,6 +35,7 @@ __all__ = [
'ExternalProgramHolder',
'extract_required_kwarg',
'BooleanHolder',
'IntegerHolder',
]
@ -47,5 +48,6 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg
extract_required_kwarg)
from .primitives import (
BooleanHolder,
IntegerHolder,
)

@ -26,7 +26,7 @@ from ..programs import ExternalProgram, NonExistingExternalProgram
from ..dependencies import Dependency
from ..depfile import DepFile
from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args
from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, permissive_unholder_return
from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, unholder_return
from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
@ -381,6 +381,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.holder_map.update({
# Primitives
int: P_OBJ.IntegerHolder,
bool: P_OBJ.BooleanHolder,
# Meson types
mesonlib.File: OBJ.FileHolder,

@ -2,7 +2,9 @@
# SPDX-license-identifier: Apache-2.0
__all__ = [
'BooleanHolder',
'IntegerHolder',
]
from .boolean import BooleanHolder
from .integer import IntegerHolder

@ -0,0 +1,53 @@
# Copyright 2021 The Meson development team
# SPDX-license-identifier: Apache-2.0
from ...interpreterbase import (
ObjectHolder,
MesonOperator,
typed_pos_args,
noKwargs,
noPosargs,
TYPE_var,
TYPE_kwargs,
InvalidArguments
)
import typing as T
if T.TYPE_CHECKING:
# Object holders need the actual interpreter
from ...interpreter import Interpreter
class BooleanHolder(ObjectHolder[bool]):
def __init__(self, obj: bool, interpreter: 'Interpreter') -> None:
super().__init__(obj, interpreter)
self.methods.update({
'to_int': self.to_int_method,
'to_string': self.to_string_method,
})
self.trivial_operators.update({
MesonOperator.BOOL: (None, lambda x: self.held_object),
MesonOperator.NOT: (None, lambda x: not self.held_object),
MesonOperator.EQUALS: (bool, lambda x: self.held_object == x),
MesonOperator.NOT_EQUALS: (bool, lambda x: self.held_object != x),
})
def display_name(self) -> str:
return 'bool'
@noKwargs
@noPosargs
def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
return 1 if self.held_object else 0
@noKwargs
@typed_pos_args('bool.to_string', optargs=[str, str])
def to_string_method(self, args: T.Tuple[T.Optional[str], T.Optional[str]], kwargs: TYPE_kwargs) -> str:
true_str = args[0] or 'true'
false_str = args[1] or 'false'
if any(x is not None for x in args) and not all(x is not None for x in args):
raise InvalidArguments('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
return true_str if self.held_object else false_str

@ -129,8 +129,8 @@ class MesonInterpreterObject(InterpreterObject):
class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable '''
HoldableTypes = (HoldableObject, int)
TYPE_HoldableTypes = T.Union[HoldableObject, int]
HoldableTypes = (HoldableObject, int, bool)
TYPE_HoldableTypes = T.Union[HoldableObject, int, bool]
InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)
class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):

@ -77,11 +77,11 @@ def noSecondLevelHolderResolving(f: TV_func) -> TV_func:
setattr(f, 'no-second-level-holder-flattening', True) # noqa: B010
return f
def permissive_unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]:
def unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]:
@wraps(f)
def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
res = f(*wrapped_args, **wrapped_kwargs)
return _unholder(res, permissive=True)
return _unholder(res)
return T.cast(T.Callable[..., TYPE_var], wrapped)
def disablerIfNotFound(f: TV_func) -> TV_func:

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

Loading…
Cancel
Save