aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mensinger <daniel@mensinger-ka.de>2021-08-29 13:06:56 +0200
committerDaniel Mensinger <daniel@mensinger-ka.de>2021-08-31 23:01:21 +0200
commit86f70c873a46c63e7a6e12de562ad8c907eabbc5 (patch)
treed97c30ec996328ca7945e8fe0032bf0390bbf314
parenta6c9a151d3ce51d53fc7f34722a7422c3c0faff4 (diff)
downloadmeson-86f70c873a46c63e7a6e12de562ad8c907eabbc5.zip
meson-86f70c873a46c63e7a6e12de562ad8c907eabbc5.tar.gz
meson-86f70c873a46c63e7a6e12de562ad8c907eabbc5.tar.bz2
interpreter: Introduce operators support for InterpreterObjects
-rw-r--r--mesonbuild/interpreterbase/__init__.py4
-rw-r--r--mesonbuild/interpreterbase/baseobjects.py83
-rw-r--r--mesonbuild/interpreterbase/decorators.py44
-rw-r--r--mesonbuild/interpreterbase/interpreterbase.py42
-rw-r--r--mesonbuild/interpreterbase/operator.py34
5 files changed, 202 insertions, 5 deletions
diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py
index f7b12a5..c3605b2 100644
--- a/mesonbuild/interpreterbase/__init__.py
+++ b/mesonbuild/interpreterbase/__init__.py
@@ -20,6 +20,8 @@ __all__ = [
'MesonVersionString',
'MutableInterpreterObject',
+ 'MesonOperator',
+
'Disabler',
'is_disabled',
@@ -43,6 +45,8 @@ __all__ = [
'permissive_unholder_return',
'disablerIfNotFound',
'permittedKwargs',
+ 'typed_operator',
+ 'unary_operator',
'typed_pos_args',
'ContainerTypeInfo',
'KwargInfo',
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py
index ddfd4be..b28654a 100644
--- a/mesonbuild/interpreterbase/baseobjects.py
+++ b/mesonbuild/interpreterbase/baseobjects.py
@@ -13,9 +13,11 @@
# limitations under the License.
from .. import mparser
-from .exceptions import InvalidCode
+from .exceptions import InvalidCode, InvalidArguments
from .helpers import flatten, resolve_second_level_holders
-from ..mesonlib import HoldableObject
+from .operator import MesonOperator
+from ..mesonlib import HoldableObject, MesonBugException
+import textwrap
import typing as T
@@ -36,17 +38,41 @@ TYPE_kwargs = T.Dict[str, TYPE_var]
TYPE_nkwargs = T.Dict[str, TYPE_nvar]
TYPE_key_resolver = T.Callable[[mparser.BaseNode], str]
+if T.TYPE_CHECKING:
+ from typing_extensions import Protocol
+ __T = T.TypeVar('__T', bound=TYPE_var, contravariant=True)
+ class OperatorCall(Protocol[__T]):
+ def __call__(self, other: __T) -> TYPE_var: ...
+
class InterpreterObject:
def __init__(self, *, subproject: T.Optional[str] = None) -> None:
self.methods: T.Dict[
str,
T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var]
] = {}
+ self.operators: T.Dict[MesonOperator, 'OperatorCall'] = {}
+ self.trivial_operators: T.Dict[
+ MesonOperator,
+ T.Tuple[
+ T.Type[T.Union[TYPE_var, T.Tuple[TYPE_var, ...]]],
+ 'OperatorCall'
+ ]
+ ] = {}
# Current node set during a method call. This can be used as location
# when printing a warning message during a method call.
self.current_node: mparser.BaseNode = None
self.subproject: str = subproject or ''
+ # Some default operators supported by all objects
+ self.operators.update({
+ MesonOperator.EQUALS: self.op_equals,
+ MesonOperator.NOT_EQUALS: self.op_not_equals,
+ })
+
+ # The type of the object that can be printed to the user
+ def display_name(self) -> str:
+ return type(self).__name__
+
def method_call(
self,
method_name: str,
@@ -62,13 +88,47 @@ class InterpreterObject:
return method(args, kwargs)
raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.')
+ def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var:
+ if operator in self.trivial_operators:
+ op = self.trivial_operators[operator]
+ if op[0] is None and other is not None:
+ raise MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}')
+ if op[0] is not None and not isinstance(other, op[0]):
+ raise InvalidArguments(f'The `{operator.value}` of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})')
+ return op[1](other)
+ if operator in self.operators:
+ return self.operators[operator](other)
+ raise InvalidCode(f'Object {self} of type {self.display_name()} does not support the `{operator.value}` operator.')
+
+
+ # Default comparison operator support
+ def _throw_comp_exception(self, other: TYPE_var, opt_type: str) -> T.NoReturn:
+ raise InvalidArguments(textwrap.dedent(
+ f'''
+ Trying to compare values of different types ({self.display_name()}, {type(other).__name__}) using {opt_type}.
+ This was deprecated and undefined behavior previously and is as of 0.60.0 a hard error.
+ '''
+ ))
+
+ def op_equals(self, other: TYPE_var) -> bool:
+ if type(self) != type(other):
+ self._throw_comp_exception(other, '==')
+ return self == other
+
+ def op_not_equals(self, other: TYPE_var) -> bool:
+ if type(self) != type(other):
+ self._throw_comp_exception(other, '!=')
+ return self != other
+
class MesonInterpreterObject(InterpreterObject):
''' All non-elementary objects and non-object-holders should be derived from this '''
class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable '''
-InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=HoldableObject)
+HoldableTypes = (HoldableObject, int)
+TYPE_HoldableTypes = T.Union[HoldableObject, int]
+InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)
class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None:
@@ -77,11 +137,26 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
# HoldableObject, not the specialized type, so only do this assert in
# non-type checking situations
if not T.TYPE_CHECKING:
- assert isinstance(obj, HoldableObject), f'This is a bug: Trying to hold object of type `{type(obj).__name__}` that is not an `HoldableObject`'
+ assert isinstance(obj, HoldableTypes), f'This is a bug: Trying to hold object of type `{type(obj).__name__}` that is not in `{HoldableTypes}`'
self.held_object = obj
self.interpreter = interpreter
self.env = self.interpreter.environment
+ # Hide the object holder abstrction from the user
+ def display_name(self) -> str:
+ return type(self.held_object).__name__
+
+ # Override default comparison operators for the held object
+ def op_equals(self, other: TYPE_var) -> bool:
+ if type(self.held_object) != type(other):
+ self._throw_comp_exception(other, '==')
+ return self.held_object == other
+
+ def op_not_equals(self, other: TYPE_var) -> bool:
+ if type(self.held_object) != type(other):
+ self._throw_comp_exception(other, '!=')
+ return self.held_object != other
+
def __repr__(self) -> str:
return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>'
diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py
index 33892be..717853f 100644
--- a/mesonbuild/interpreterbase/decorators.py
+++ b/mesonbuild/interpreterbase/decorators.py
@@ -17,6 +17,7 @@ from .baseobjects import TV_func, TYPE_var, TYPE_kwargs
from .disabler import Disabler
from .exceptions import InterpreterException, InvalidArguments
from .helpers import check_stringlist
+from .operator import MesonOperator
from ._unholder import _unholder
from functools import wraps
@@ -110,6 +111,49 @@ class permittedKwargs:
return f(*wrapped_args, **wrapped_kwargs)
return T.cast(TV_func, wrapped)
+if T.TYPE_CHECKING:
+ from .baseobjects import InterpreterObject
+ from typing_extensions import Protocol
+
+ _TV_IntegerObject = T.TypeVar('_TV_IntegerObject', bound=InterpreterObject, contravariant=True)
+ _TV_ARG1 = T.TypeVar('_TV_ARG1', bound=TYPE_var, contravariant=True)
+
+ class FN_Operator(Protocol[_TV_IntegerObject, _TV_ARG1]):
+ def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> TYPE_var: ...
+ _TV_FN_Operator = T.TypeVar('_TV_FN_Operator', bound=FN_Operator)
+
+def typed_operator(operator: MesonOperator,
+ types: T.Union[T.Type, T.Tuple[T.Type, ...]]) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']:
+ """Decorator that does type checking for operator calls.
+
+ The principle here is similar to typed_pos_args, however much simpler
+ since only one other object ever is passed
+ """
+ def inner(f: '_TV_FN_Operator') -> '_TV_FN_Operator':
+ @wraps(f)
+ def wrapper(self: 'InterpreterObject', other: TYPE_var) -> TYPE_var:
+ if not isinstance(other, types):
+ raise InvalidArguments(f'The `{operator.value}` of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})')
+ return f(self, other)
+ return T.cast('_TV_FN_Operator', wrapper)
+ return inner
+
+def unary_operator(operator: MesonOperator) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']:
+ """Decorator that does type checking for operator calls.
+
+ This decorator is for unary operators that do not take any other objects.
+ It should be impossible for a user to accidentally break this. Triggering
+ this check always indicates a bug in the Meson interpreter.
+ """
+ def inner(f: '_TV_FN_Operator') -> '_TV_FN_Operator':
+ @wraps(f)
+ def wrapper(self: 'InterpreterObject', other: TYPE_var) -> TYPE_var:
+ if other is not None:
+ raise mesonlib.MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}')
+ return f(self, other)
+ return T.cast('_TV_FN_Operator', wrapper)
+ return inner
+
def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None,
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
index 289e1d7..b6e7d0d 100644
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -43,6 +43,7 @@ from .exceptions import (
from .decorators import FeatureNew, noKwargs
from .disabler import Disabler, is_disabled
from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
+from .operator import MesonOperator
from ._unholder import _unholder
import os, copy, re, pathlib
@@ -290,13 +291,37 @@ class InterpreterBase:
raise InvalidArguments('rvalue of "in" operator must be an array or a dict')
return val1 in val2
- def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[bool, Disabler]:
+ def evaluate_comparison(self, node: mparser.ComparisonNode) -> T.Union[TYPE_var, InterpreterObject]:
val1 = self.evaluate_statement(node.left)
if isinstance(val1, Disabler):
return val1
val2 = self.evaluate_statement(node.right)
if isinstance(val2, Disabler):
return val2
+
+ # New code based on InterpreterObjects
+ operator = {
+ 'in': MesonOperator.IN,
+ 'notin': MesonOperator.NOT_IN,
+ '==': MesonOperator.EQUALS,
+ '!=': MesonOperator.NOT_EQUALS,
+ '>': MesonOperator.GREATER,
+ '<': MesonOperator.LESS,
+ '>=': MesonOperator.GREATER_EQUALS,
+ '<=': MesonOperator.LESS_EQUALS,
+ }[node.ctype]
+
+ # Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`)
+ if operator in (MesonOperator.IN, MesonOperator.NOT_IN) and isinstance(val2, InterpreterObject):
+ return self._holderify(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)))
+
+ # 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)
@@ -404,6 +429,21 @@ class InterpreterBase:
if isinstance(r, Disabler):
return r
+ # New code based on InterpreterObjects
+ if isinstance(l, InterpreterObject):
+ mapping: T.Dict[str, MesonOperator] = {
+ 'add': MesonOperator.PLUS,
+ 'sub': MesonOperator.MINUS,
+ 'mul': MesonOperator.TIMES,
+ 'div': MesonOperator.DIV,
+ 'mod': MesonOperator.MOD,
+ }
+ res = l.operator_call(mapping[cur.operation], _unholder(r))
+ return self._holderify(res)
+
+ # OLD CODE, based on the builtin types -- remove once we have switched
+ # over to all ObjectHolders.
+
if cur.operation == 'add':
if isinstance(l, dict) and isinstance(r, dict):
return {**l, **r}
diff --git a/mesonbuild/interpreterbase/operator.py b/mesonbuild/interpreterbase/operator.py
new file mode 100644
index 0000000..bf9b6b0
--- /dev/null
+++ b/mesonbuild/interpreterbase/operator.py
@@ -0,0 +1,34 @@
+# SPDX-license-identifier: Apache-2.0
+
+from enum import Enum
+
+class MesonOperator(Enum):
+ # Arithmetic
+ PLUS = '+'
+ MINUS = '-'
+ TIMES = '*'
+ DIV = '/'
+ MOD = '%'
+
+ UMINUS = 'uminus'
+
+ # Logic
+ NOT = 'not'
+ AND = 'and'
+ OR = 'or'
+
+ # Should return the boolsche interpretation of the value (`'' == false` for instance)
+ BOOL = 'bool()'
+
+ # Comparision
+ EQUALS = '=='
+ NOT_EQUALS = '!='
+ GREATER = '>'
+ LESS = '<'
+ GREATER_EQUALS = '>='
+ LESS_EQUALS = '<='
+
+ # Container
+ IN = 'in'
+ NOT_IN = 'not in'
+ INDEX = '[]'