diff options
Diffstat (limited to 'mesonbuild/interpreterbase/baseobjects.py')
-rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 123 |
1 files changed, 91 insertions, 32 deletions
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index a5cccce..c756761 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -15,16 +15,11 @@ from abc import ABCMeta from contextlib import AbstractContextManager if T.TYPE_CHECKING: - from typing_extensions import Protocol, TypeAlias + from typing_extensions import TypeAlias # Object holders need the actual interpreter from ..interpreter import Interpreter - __T = T.TypeVar('__T', bound='TYPE_var', contravariant=True) - - class OperatorCall(Protocol[__T]): - def __call__(self, other: __T) -> 'TYPE_var': ... - TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) @@ -34,34 +29,85 @@ TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] TYPE_kwargs = T.Dict[str, TYPE_var] TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] +TYPE_op_arg = T.TypeVar('TYPE_op_arg', bound='TYPE_var', contravariant=True) +TYPE_op_func = T.Callable[[TYPE_op_arg, TYPE_op_arg], TYPE_var] +TYPE_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var] + SubProject = T.NewType('SubProject', str) class InterpreterObject: + TRIVIAL_OPERATORS: T.Dict[ + MesonOperator, + T.Tuple[ + T.Union[T.Type, T.Tuple[T.Type, ...]], + TYPE_op_func + ] + ] = {} + + OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {} + + METHODS: T.Dict[ + str, + TYPE_method_func, + ] = {} + + def __init_subclass__(cls: T.Type[InterpreterObject], **kwargs: T.Any) -> None: + super().__init_subclass__(**kwargs) + saved_trivial_operators = cls.TRIVIAL_OPERATORS + + cls.METHODS = {} + cls.OPERATORS = {} + cls.TRIVIAL_OPERATORS = {} + + # Compute inherited operators and methods according to the Python resolution + # order. Reverse the result of mro() because update() will overwrite entries + # that are set by the superclass with those that are set by the subclass. + for superclass in reversed(cls.mro()[1:]): + if superclass is InterpreterObject: + # InterpreterObject cannot use @InterpreterObject.operator because + # __init_subclass__ does not operate on InterpreterObject itself + cls.OPERATORS.update({ + MesonOperator.EQUALS: InterpreterObject.op_equals, + MesonOperator.NOT_EQUALS: InterpreterObject.op_not_equals + }) + + elif issubclass(superclass, InterpreterObject): + cls.METHODS.update(superclass.METHODS) + cls.OPERATORS.update(superclass.OPERATORS) + cls.TRIVIAL_OPERATORS.update(superclass.TRIVIAL_OPERATORS) + + for name, method in cls.__dict__.items(): + if hasattr(method, 'meson_method'): + cls.METHODS[method.meson_method] = method + if hasattr(method, 'meson_operator'): + cls.OPERATORS[method.meson_operator] = method + cls.TRIVIAL_OPERATORS.update(saved_trivial_operators) + + @staticmethod + def method(name: str) -> T.Callable[[TV_func], TV_func]: + '''Decorator that tags a Python method as the implementation of a method + for the Meson interpreter''' + def decorator(f: TV_func) -> TV_func: + f.meson_method = name # type: ignore[attr-defined] + return f + return decorator + + @staticmethod + def operator(op: MesonOperator) -> T.Callable[[TV_func], TV_func]: + '''Decorator that tags a method as the implementation of an operator + for the Meson interpreter''' + def decorator(f: TV_func) -> TV_func: + f.meson_operator = op # type: ignore[attr-defined] + return f + return decorator + def __init__(self, *, subproject: T.Optional['SubProject'] = 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.Union[T.Type, T.Tuple[T.Type, ...]], - '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 = subproject or SubProject('') - # 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__ @@ -72,25 +118,26 @@ class InterpreterObject: args: T.List[TYPE_var], kwargs: TYPE_kwargs ) -> TYPE_var: - if method_name in self.methods: - method = self.methods[method_name] + if method_name in self.METHODS: + method = self.METHODS[method_name] if not getattr(method, 'no-args-flattening', False): args = flatten(args) if not getattr(method, 'no-second-level-holder-flattening', False): args, kwargs = resolve_second_level_holders(args, kwargs) - return method(args, kwargs) + return method(self, 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 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}` operator of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})') - return op[1](other) - if operator in self.operators: - return self.operators[operator](other) + return op[1](self, other) + if operator in self.OPERATORS: + return self.OPERATORS[operator](self, other) + raise InvalidCode(f'Object {self} of type {self.display_name()} does not support the `{operator.value}` operator.') # Default comparison operator support @@ -121,6 +168,16 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' +class UnknownValue(MesonInterpreterObject): + '''This class is only used for the rewriter/static introspection tool and + indicates that a value cannot be determined statically, either because of + limitations in our code or because the value differs from machine to + machine.''' + +class UndefinedVariable(MesonInterpreterObject): + '''This class is only used for the rewriter/static introspection tool and + represents the `value` a meson-variable has if it was never written to.''' + HoldableTypes = (HoldableObject, int, bool, str, list, dict) TYPE_HoldableTypes = T.Union[TYPE_var, HoldableObject] InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) @@ -142,12 +199,14 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): return type(self.held_object).__name__ # Override default comparison operators for the held object + @InterpreterObject.operator(MesonOperator.EQUALS) def op_equals(self, other: TYPE_var) -> bool: # See the comment from InterpreterObject why we are using `type()` here. if type(self.held_object) is not type(other): self._throw_comp_exception(other, '==') return self.held_object == other + @InterpreterObject.operator(MesonOperator.NOT_EQUALS) def op_not_equals(self, other: TYPE_var) -> bool: if type(self.held_object) is not type(other): self._throw_comp_exception(other, '!=') |