diff options
Diffstat (limited to '')
-rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py new file mode 100644 index 0000000..820e091 --- /dev/null +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -0,0 +1,182 @@ +# Copyright 2013-2021 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +from .. import mparser +from .exceptions import InvalidCode, InvalidArguments +from .helpers import flatten, resolve_second_level_holders +from .operator import MesonOperator +from ..mesonlib import HoldableObject, MesonBugException +import textwrap + +import typing as T +from abc import ABCMeta + +if T.TYPE_CHECKING: + from typing_extensions import Protocol + + # 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_fw_var = T.Union[str, int, bool, list, dict, 'InterpreterObject'] +TV_fw_args = T.List[T.Union[mparser.BaseNode, TV_fw_var]] +TV_fw_kwargs = T.Dict[str, T.Union[mparser.BaseNode, TV_fw_var]] + +TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) + +TYPE_elementary = T.Union[str, int, bool, T.List[T.Any], T.Dict[str, T.Any]] +TYPE_var = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject'] +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] + +SubProject = T.NewType('SubProject', str) + +class InterpreterObject: + 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__ + + def method_call( + self, + method_name: str, + args: T.List[TYPE_var], + kwargs: TYPE_kwargs + ) -> TYPE_var: + 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) + 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}` 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) + 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: + # We use `type(...) == type(...)` here to enforce an *exact* match for comparison. We + # don't want comparisons to be possible where `isinstance(derived_obj, type(base_obj))` + # would pass because this comparison must never be true: `derived_obj == base_obj` + 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 ''' + +HoldableTypes = (HoldableObject, int, bool, str, list, dict) +TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject] +InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) + +class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): + def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None: + super().__init__(subproject=interpreter.subproject) + # This causes some type checkers to assume that obj is a base + # HoldableObject, not the specialized type, so only do this assert in + # non-type checking situations + if not T.TYPE_CHECKING: + 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 abstraction 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: + # See the comment from InterpreterObject why we are using `type()` here. + 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}>' + +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__}') |