aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mensinger <daniel@mensinger-ka.de>2021-08-29 19:40:10 +0200
committerDaniel Mensinger <daniel@mensinger-ka.de>2021-09-01 19:17:01 +0200
commit43302d3296baff6aeaf8e03f5d701b0402e37a6c (patch)
tree0dffb59760fc793e92e9c505090c4b7c550de5a1
parent86eda3c812e8b919dee485c0b6333671652ee30c (diff)
downloadmeson-43302d3296baff6aeaf8e03f5d701b0402e37a6c.zip
meson-43302d3296baff6aeaf8e03f5d701b0402e37a6c.tar.gz
meson-43302d3296baff6aeaf8e03f5d701b0402e37a6c.tar.bz2
interpreter: Introduce BooleanHolder for the bool primitive
-rw-r--r--mesonbuild/ast/interpreter.py3
-rw-r--r--mesonbuild/interpreter/__init__.py2
-rw-r--r--mesonbuild/interpreter/interpreter.py3
-rw-r--r--mesonbuild/interpreter/primitives/__init__.py2
-rw-r--r--mesonbuild/interpreter/primitives/boolean.py53
-rw-r--r--mesonbuild/interpreterbase/baseobjects.py4
-rw-r--r--mesonbuild/interpreterbase/decorators.py4
-rw-r--r--mesonbuild/interpreterbase/interpreterbase.py126
8 files changed, 129 insertions, 68 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index 7e4bc62..4fd1378 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -366,7 +366,8 @@ class AstInterpreter(InterpreterBase):
if isinstance(src, str):
result = self.string_method_call(src, node.name, margs, mkwargs)
elif isinstance(src, bool):
- result = self.bool_method_call(src, node.name, margs, mkwargs)
+ from ..interpreter import Interpreter, BooleanHolder
+ result = BooleanHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
elif isinstance(src, int):
from ..interpreter import Interpreter, IntegerHolder
result = IntegerHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs)
diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py
index 0a7717f..90d7faf 100644
--- a/mesonbuild/interpreter/__init__.py
+++ b/mesonbuild/interpreter/__init__.py
@@ -35,6 +35,7 @@ __all__ = [
'ExternalProgramHolder',
'extract_required_kwarg',
+ 'BooleanHolder',
'IntegerHolder',
]
@@ -47,5 +48,6 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg
extract_required_kwarg)
from .primitives import (
+ BooleanHolder,
IntegerHolder,
)
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index d4d5564..41cb7b8 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -26,7 +26,7 @@ from ..programs import ExternalProgram, NonExistingExternalProgram
from ..dependencies import Dependency
from ..depfile import DepFile
from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args
-from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, permissive_unholder_return
+from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, unholder_return
from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
@@ -381,6 +381,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.holder_map.update({
# Primitives
int: P_OBJ.IntegerHolder,
+ bool: P_OBJ.BooleanHolder,
# Meson types
mesonlib.File: OBJ.FileHolder,
diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py
index 56af72a..5d16744 100644
--- a/mesonbuild/interpreter/primitives/__init__.py
+++ b/mesonbuild/interpreter/primitives/__init__.py
@@ -2,7 +2,9 @@
# SPDX-license-identifier: Apache-2.0
__all__ = [
+ 'BooleanHolder',
'IntegerHolder',
]
+from .boolean import BooleanHolder
from .integer import IntegerHolder
diff --git a/mesonbuild/interpreter/primitives/boolean.py b/mesonbuild/interpreter/primitives/boolean.py
new file mode 100644
index 0000000..0a434af
--- /dev/null
+++ b/mesonbuild/interpreter/primitives/boolean.py
@@ -0,0 +1,53 @@
+# Copyright 2021 The Meson development team
+# SPDX-license-identifier: Apache-2.0
+
+from ...interpreterbase import (
+ ObjectHolder,
+ MesonOperator,
+ typed_pos_args,
+ noKwargs,
+ noPosargs,
+
+ TYPE_var,
+ TYPE_kwargs,
+
+ InvalidArguments
+)
+
+import typing as T
+
+if T.TYPE_CHECKING:
+ # Object holders need the actual interpreter
+ from ...interpreter import Interpreter
+
+class BooleanHolder(ObjectHolder[bool]):
+ def __init__(self, obj: bool, interpreter: 'Interpreter') -> None:
+ super().__init__(obj, interpreter)
+ self.methods.update({
+ 'to_int': self.to_int_method,
+ 'to_string': self.to_string_method,
+ })
+
+ self.trivial_operators.update({
+ MesonOperator.BOOL: (None, lambda x: self.held_object),
+ MesonOperator.NOT: (None, lambda x: not self.held_object),
+ MesonOperator.EQUALS: (bool, lambda x: self.held_object == x),
+ MesonOperator.NOT_EQUALS: (bool, lambda x: self.held_object != x),
+ })
+
+ def display_name(self) -> str:
+ return 'bool'
+
+ @noKwargs
+ @noPosargs
+ def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
+ return 1 if self.held_object else 0
+
+ @noKwargs
+ @typed_pos_args('bool.to_string', optargs=[str, str])
+ def to_string_method(self, args: T.Tuple[T.Optional[str], T.Optional[str]], kwargs: TYPE_kwargs) -> str:
+ true_str = args[0] or 'true'
+ false_str = args[1] or 'false'
+ if any(x is not None for x in args) and not all(x is not None for x in args):
+ raise InvalidArguments('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
+ return true_str if self.held_object else false_str
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py
index 4b538fb..80cf0b5 100644
--- a/mesonbuild/interpreterbase/baseobjects.py
+++ b/mesonbuild/interpreterbase/baseobjects.py
@@ -129,8 +129,8 @@ class MesonInterpreterObject(InterpreterObject):
class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable '''
-HoldableTypes = (HoldableObject, int)
-TYPE_HoldableTypes = T.Union[HoldableObject, int]
+HoldableTypes = (HoldableObject, int, bool)
+TYPE_HoldableTypes = T.Union[HoldableObject, int, bool]
InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)
class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py
index 156b4c9..b9c4a1f 100644
--- a/mesonbuild/interpreterbase/decorators.py
+++ b/mesonbuild/interpreterbase/decorators.py
@@ -77,11 +77,11 @@ def noSecondLevelHolderResolving(f: TV_func) -> TV_func:
setattr(f, 'no-second-level-holder-flattening', True) # noqa: B010
return f
-def permissive_unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]:
+def unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]:
@wraps(f)
def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
res = f(*wrapped_args, **wrapped_kwargs)
- return _unholder(res, permissive=True)
+ return _unholder(res)
return T.cast(T.Callable[..., TYPE_var], wrapped)
def disablerIfNotFound(f: TV_func) -> TV_func:
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
index cfd72b5..3d419d4 100644
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -51,6 +51,7 @@ from ._unholder import _unholder
import os, copy, re, pathlib
import typing as T
import textwrap
+from functools import wraps
if T.TYPE_CHECKING:
from ..interpreter import Interpreter
@@ -69,11 +70,23 @@ FunctionType = T.Dict[
T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var]
]
+__FN = T.TypeVar('__FN', bound=T.Callable[['InterpreterBase', T.Any], T.Union[TYPE_var, InterpreterObject]])
+def _holderify_result(types: T.Union[None, T.Type, T.Tuple[T.Type, ...]] = None) -> T.Callable[[__FN], __FN]:
+ def inner(f: __FN) -> __FN:
+ @wraps(f)
+ def wrapper(self: 'InterpreterBase', node: mparser.BaseNode) -> T.Union[TYPE_var, InterpreterObject]:
+ res = f(self, node)
+ if types is not None and not isinstance(res, types):
+ raise mesonlib.MesonBugException(f'Expected {types} but got object `{res}` of type {type(res).__name__}')
+ return self._holderify(res)
+ return T.cast(__FN, wrapper)
+ return inner
+
class MesonVersionString(str):
pass
class InterpreterBase:
- elementary_types = (str, bool, list)
+ elementary_types = (str, list)
def __init__(self, source_root: str, subdir: str, subproject: str):
self.source_root = source_root
@@ -190,7 +203,7 @@ class InterpreterBase:
elif isinstance(cur, mparser.StringNode):
return cur.value
elif isinstance(cur, mparser.BooleanNode):
- return cur.value
+ return self._holderify(cur.value)
elif isinstance(cur, mparser.IfClauseNode):
return self.evaluate_if(cur)
elif isinstance(cur, mparser.IdNode):
@@ -252,13 +265,15 @@ class InterpreterBase:
assert not arguments
return kwargs
- def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[bool, Disabler]:
+ @_holderify_result((bool, Disabler))
+ def evaluate_notstatement(self, cur: mparser.NotNode) -> T.Union[TYPE_var, InterpreterObject]:
v = self.evaluate_statement(cur.value)
if isinstance(v, Disabler):
return v
- if not isinstance(v, bool):
- raise InterpreterException('Argument to "not" is not a boolean.')
- return not v
+ # TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects
+ if not isinstance(v, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Argument to not ({v}) is not an InterpreterObject but {type(v).__name__}.')
+ return v.operator_call(MesonOperator.NOT, None)
def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
assert isinstance(node, mparser.IfClauseNode)
@@ -269,6 +284,9 @@ class InterpreterBase:
result = self.evaluate_statement(i.condition)
if isinstance(result, Disabler):
return result
+ if not isinstance(result, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Argument to not ({result}) is not an InterpreterObject but {type(result).__name__}.')
+ result = result.operator_call(MesonOperator.BOOL, None)
if not isinstance(result, bool):
raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.')
if result:
@@ -289,13 +307,14 @@ class InterpreterBase:
return False
return True
- def evaluate_in(self, val1: T.Any, val2: T.Any) -> bool:
+ def _evaluate_in(self, val1: T.Any, val2: T.Any) -> bool:
if not isinstance(val1, (str, int, float, mesonlib.HoldableObject)):
raise InvalidArguments('lvalue of "in" operator must be a string, integer, float, or object')
if not isinstance(val2, (list, dict)):
raise InvalidArguments('rvalue of "in" operator must be an array or a dict')
return val1 in val2
+ @_holderify_result((bool, Disabler))
def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[TYPE_var, InterpreterObject]:
val1 = self.evaluate_statement(node.left)
if isinstance(val1, Disabler):
@@ -318,11 +337,11 @@ class InterpreterBase:
# Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`)
if operator in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val2, InterpreterObject):
- return self._holderify(val2.operator_call(operator, _unholder(val1)))
+ return val2.operator_call(operator, _unholder(val1))
# Normal evaluation, with the same semantics
elif operator not in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val1, InterpreterObject):
- return self._holderify(val1.operator_call(operator, _unholder(val2)))
+ return val1.operator_call(operator, _unholder(val2))
# OLD CODE, based on the builtin types -- remove once we have switched
# over to all ObjectHolders.
@@ -331,9 +350,9 @@ class InterpreterBase:
val1 = _unholder(val1)
val2 = _unholder(val2)
if node.ctype == 'in':
- return self.evaluate_in(val1, val2)
+ return self._evaluate_in(val1, val2)
elif node.ctype == 'notin':
- return not self.evaluate_in(val1, val2)
+ return not self._evaluate_in(val1, val2)
valid = self.validate_comparison_types(val1, val2)
# Ordering comparisons of different types isn't allowed since PR #1810
# (0.41.0). Since PR #2884 we also warn about equality comparisons of
@@ -371,36 +390,41 @@ class InterpreterBase:
else:
raise InvalidCode('You broke my compare eval.')
- def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[bool, Disabler]:
+ @_holderify_result((bool, Disabler))
+ def evaluate_andstatement(self, cur: mparser.AndNode) -> T.Union[TYPE_var, InterpreterObject]:
l = self.evaluate_statement(cur.left)
if isinstance(l, Disabler):
return l
- if not isinstance(l, bool):
- raise InterpreterException('First argument to "and" is not a boolean.')
- if not l:
- return False
+ if not isinstance(l, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Firtst argument to and ({l}) is not an InterpreterObject but {type(l).__name__}.')
+ l_bool = l.operator_call(MesonOperator.BOOL, None)
+ if not l_bool:
+ return l_bool
r = self.evaluate_statement(cur.right)
if isinstance(r, Disabler):
return r
- if not isinstance(r, bool):
- raise InterpreterException('Second argument to "and" is not a boolean.')
- return r
+ if not isinstance(r, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Second argument to and ({r}) is not an InterpreterObject but {type(r).__name__}.')
+ return r.operator_call(MesonOperator.BOOL, None)
- def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[bool, Disabler]:
+ @_holderify_result((bool, Disabler))
+ def evaluate_orstatement(self, cur: mparser.OrNode) -> T.Union[TYPE_var, InterpreterObject]:
l = self.evaluate_statement(cur.left)
if isinstance(l, Disabler):
return l
- if not isinstance(l, bool):
- raise InterpreterException('First argument to "or" is not a boolean.')
- if l:
- return True
+ if not isinstance(l, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Firtst argument to or ({l}) is not an InterpreterObject but {type(l).__name__}.')
+ l_bool = l.operator_call(MesonOperator.BOOL, None)
+ if l_bool:
+ return l_bool
r = self.evaluate_statement(cur.right)
if isinstance(r, Disabler):
return r
- if not isinstance(r, bool):
- raise InterpreterException('Second argument to "or" is not a boolean.')
- return r
+ if not isinstance(r, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Second argument to ot ({r}) is not an InterpreterObject but {type(r).__name__}.')
+ return r.operator_call(MesonOperator.BOOL, None)
+ @_holderify_result()
def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> T.Union[TYPE_var, InterpreterObject]:
v = self.evaluate_statement(cur.value)
if isinstance(v, Disabler):
@@ -408,7 +432,7 @@ class InterpreterBase:
# TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects
if not isinstance(v, InterpreterObject):
raise InterpreterException(f'Argument to negation ({v}) is not an InterpreterObject but {type(v).__name__}.')
- return self._holderify(v.operator_call(MesonOperator.UMINUS, None))
+ return v.operator_call(MesonOperator.UMINUS, None)
@FeatureNew('/ with string arguments', '0.49.0')
def evaluate_path_join(self, l: str, r: str) -> str:
@@ -473,9 +497,10 @@ class InterpreterBase:
result = self.evaluate_statement(node.condition)
if isinstance(result, Disabler):
return result
- if not isinstance(result, bool):
- raise InterpreterException('Ternary condition is not boolean.')
- if result:
+ if not isinstance(result, InterpreterObject):
+ raise mesonlib.MesonBugException(f'Ternary condition ({result}) is not an InterpreterObject but {type(result).__name__}.')
+ result_bool = result.operator_call(MesonOperator.BOOL, None)
+ if result_bool:
return self.evaluate_statement(node.trueblock)
else:
return self.evaluate_statement(node.falseblock)
@@ -554,7 +579,7 @@ class InterpreterBase:
new_value = {**old_variable, **addition}
elif isinstance(old_variable, InterpreterObject):
# TODO: don't make _unholder permissive
- new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition, permissive=True)))
+ new_value = self._holderify(old_variable.operator_call(MesonOperator.PLUS, _unholder(addition)))
# Add other data types here.
else:
raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints')
@@ -596,7 +621,8 @@ class InterpreterBase:
# We are already checking for the existence of __getitem__, so this should be save
raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) # type: ignore
- def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_elementary, InterpreterObject]]:
+ @_holderify_result()
+ def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]:
func_name = node.func_name
(h_posargs, h_kwargs) = self.reduce_arguments(node.args)
(posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs)
@@ -609,8 +635,7 @@ class InterpreterBase:
func_args = flatten(posargs)
if not getattr(func, 'no-second-level-holder-flattening', False):
func_args, kwargs = resolve_second_level_holders(func_args, kwargs)
- res = func(node, func_args, kwargs)
- return self._holderify(res)
+ return func(node, func_args, kwargs)
else:
self.unknown_function_called(func_name)
return None
@@ -631,7 +656,7 @@ class InterpreterBase:
if isinstance(obj, str):
return self._holderify(self.string_method_call(obj, method_name, args, kwargs))
if isinstance(obj, bool):
- return self._holderify(self.bool_method_call(obj, method_name, args, kwargs))
+ raise mesonlib.MesonBugException('Booleans are now wrapped in object holders!')
if isinstance(obj, int):
raise mesonlib.MesonBugException('Integers are now wrapped in object holders!')
if isinstance(obj, list):
@@ -653,7 +678,7 @@ class InterpreterBase:
# TODO: remove `permissive` once all primitives are ObjectHolders
if res is None:
return None
- if isinstance(res, (bool, str)):
+ if isinstance(res, str):
return res
elif isinstance(res, list):
return [self._holderify(x, permissive=permissive) for x in res]
@@ -684,29 +709,6 @@ class InterpreterBase:
kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]:
return [_unholder(x) for x in args], {k: _unholder(v) for k, v in kwargs.items()}
- @noKwargs
- def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int]:
- if method_name == 'to_string':
- if not posargs:
- if obj:
- return 'true'
- else:
- return 'false'
- elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str):
- if obj:
- return posargs[0]
- else:
- return posargs[1]
- else:
- raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
- elif method_name == 'to_int':
- if obj:
- return 1
- else:
- return 0
- else:
- raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
-
@staticmethod
def _get_one_string_posarg(posargs: T.List[TYPE_var], method_name: str) -> str:
if len(posargs) > 1:
@@ -830,7 +832,7 @@ class InterpreterBase:
if element == item:
return True
return False
- return check_contains([_unholder(x) for x in obj])
+ return self._holderify(check_contains([_unholder(x) for x in obj]))
elif method_name == 'length':
return self._holderify(len(obj))
elif method_name == 'get':
@@ -876,7 +878,7 @@ class InterpreterBase:
has_key = key in obj
if method_name == 'has_key':
- return has_key
+ return self._holderify(has_key)
if has_key:
return obj[key]