diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py index 86c1ce207e22..9e48ee43cb9d 100644 --- a/mesonbuild/interpreter/primitives/array.py +++ b/mesonbuild/interpreter/primitives/array.py @@ -22,12 +22,13 @@ InvalidArguments, ) +from ...interpreterbase.baseobjects import InterpreterObjectTypeVar from ...mparser import PlusAssignmentNode if T.TYPE_CHECKING: from ...interpreterbase import TYPE_kwargs -class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): +class ArrayHolder(ObjectHolder[T.List[InterpreterObjectTypeVar]], IterableObject): # Operators that only require type checks TRIVIAL_OPERATORS = { MesonOperator.EQUALS: (list, lambda obj, x: obj.held_object == x), @@ -53,7 +54,7 @@ def size(self) -> int: @typed_pos_args('array.contains', object) @InterpreterObject.method('contains') def contains_method(self, args: T.Tuple[object], kwargs: TYPE_kwargs) -> bool: - def check_contains(el: T.List[TYPE_var]) -> bool: + def check_contains(el: T.List[InterpreterObjectTypeVar]) -> bool: for element in el: if isinstance(element, list): found = check_contains(element) diff --git a/mesonbuild/interpreter/primitives/dict.py b/mesonbuild/interpreter/primitives/dict.py index 794c1505e053..6b7bbb6fac9e 100644 --- a/mesonbuild/interpreter/primitives/dict.py +++ b/mesonbuild/interpreter/primitives/dict.py @@ -17,6 +17,7 @@ typed_pos_args, TYPE_var, + TYPE_elementary, InvalidArguments, ) @@ -24,7 +25,7 @@ if T.TYPE_CHECKING: from ...interpreterbase import TYPE_kwargs -class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): +class DictHolder(ObjectHolder[dict[str, TYPE_elementary]], IterableObject): # Operators that only require type checks TRIVIAL_OPERATORS = { # Arithmetic diff --git a/mesonbuild/interpreter/primitives/integer.py b/mesonbuild/interpreter/primitives/integer.py index 443458c92eaa..6bbf8901f49d 100644 --- a/mesonbuild/interpreter/primitives/integer.py +++ b/mesonbuild/interpreter/primitives/integer.py @@ -41,7 +41,7 @@ class IntegerHolder(ObjectHolder[int]): def display_name(self) -> str: return 'int' - def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var: + def operator_call(self, operator: MesonOperator, other: int) -> int | bool: if isinstance(other, bool): FeatureBroken.single_use('int operations with non-int', '1.2.0', self.subproject, 'It is not commutative and only worked because of leaky Python abstractions.', diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 23407a42ff08..49aedecaabd3 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -19,6 +19,13 @@ # Object holders need the actual interpreter from ..interpreter import Interpreter + _TV_IntegerObject = T.TypeVar('_TV_IntegerObject', bound='InterpreterObject', contravariant=True) + _TV_ARG1 = T.TypeVar('_TV_ARG1', bound='TYPE_var', contravariant=True) + + class FN_Operator(T.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) + TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) @@ -28,20 +35,22 @@ TYPE_kwargs = T.Dict[str, TYPE_var] TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] + +HoldableTypes = (HoldableObject, int, bool, str, list, dict) +TYPE_HoldableTypes = T.Union[TYPE_var, HoldableObject] +InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) + 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_op_func: TypeAlias = T.Union[ + T.Callable[['InterpreterObject[TYPE_op_arg, InterpreterObjectTypeVar]', TYPE_op_arg], TYPE_op_arg], + T.Callable[['InterpreterObject[TYPE_op_arg, InterpreterObjectTypeVar]', TYPE_op_arg], bool] +] TYPE_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var] -class InterpreterObject: - TRIVIAL_OPERATORS: T.Dict[ - MesonOperator, - T.Tuple[ - T.Union[T.Type, T.Tuple[T.Type, ...]], - TYPE_op_func - ] - ] = {} +class InterpreterObject(T.Generic[TYPE_op_arg, InterpreterObjectTypeVar]): + TRIVIAL_OPERATORS: dict[MesonOperator, T.Tuple[type[TYPE_op_arg] | type[InterpreterObjectTypeVar] | type[InterpreterObjectTypeVar] | None, TYPE_op_func[TYPE_op_arg, InterpreterObjectTypeVar]]] = {} - OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {} + OPERATORS: T.Dict[MesonOperator, TYPE_op_func[TYPE_op_arg, InterpreterObjectTypeVar]] = {} METHODS: T.Dict[ str, @@ -90,10 +99,10 @@ def decorator(f: TV_func) -> TV_func: return decorator @staticmethod - def operator(op: MesonOperator) -> T.Callable[[TV_func], TV_func]: + def operator(op: MesonOperator) -> T.Callable[[TV_FN_Operator], TV_FN_Operator]: '''Decorator that tags a method as the implementation of an operator for the Meson interpreter''' - def decorator(f: TV_func) -> TV_func: + def decorator(f: TV_FN_Operator) -> TV_FN_Operator: f.meson_operator = op # type: ignore[attr-defined] return f return decorator @@ -129,7 +138,7 @@ def method_call( ustr += f' Did you mean "{close_matches[0]}"?' raise InvalidCode(ustr) - def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var: + def operator_call(self, operator: MesonOperator, other: TYPE_op_arg) -> TYPE_op_arg | bool: if operator in self.TRIVIAL_OPERATORS: op = self.TRIVIAL_OPERATORS[operator] if op[0] is None and other is not None: @@ -180,11 +189,10 @@ 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) -class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): +ContainerObjectTypeVar: TypeAlias = InterpreterObjectTypeVar + +class ObjectHolder(InterpreterObject[InterpreterObjectTypeVar, ContainerObjectTypeVar]): 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 diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index ff2848cc288e..008188d275ca 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -16,24 +16,17 @@ import typing as T if T.TYPE_CHECKING: - from typing_extensions import Protocol, TypeAlias, TypeIs + from typing_extensions import TypeAlias, TypeIs from .. import mparser from ..mesonlib import SubProject from ..modules import ModuleObject, ModuleState from ..mparser import FunctionNode from ..optinterpreter import OptionInterpreter - from .baseobjects import InterpreterObject, ObjectHolder, TV_func, TYPE_var, TYPE_kwargs + from .baseobjects import InterpreterObject, TV_func, TYPE_var, TYPE_kwargs, TV_FN_Operator, TYPE_op_arg, InterpreterObjectTypeVar from .interpreterbase import InterpreterBase from .operator import MesonOperator - _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) - CalleeArgs: TypeAlias = T.Tuple[mparser.BaseNode, T.Optional[T.List[TYPE_var]], T.Optional[TYPE_kwargs], SubProject] MesonVersionTarget = mesonlib.Range[mesonlib.Version] | mesonlib.NoProjectVersion | None @@ -48,7 +41,7 @@ def get_callee_args(wrapped_args: T.Tuple[InterpreterObject, T.List[TYPE_var], T @T.overload -def get_callee_args(wrapped_args: T.Tuple[ObjectHolder, object]) -> CalleeArgs: ... +def get_callee_args(wrapped_args: T.Tuple[InterpreterObject[TYPE_op_arg, InterpreterObjectTypeVar], TYPE_op_arg]) -> CalleeArgs: ... @T.overload @@ -65,7 +58,7 @@ def get_callee_args(wrapped_args: T.Tuple[OptionInterpreter, str, str, T.List[TY def get_callee_args(wrapped_args: T.Union[ T.Tuple[InterpreterObject, T.List[TYPE_var], TYPE_kwargs], - T.Tuple[ObjectHolder, object], + T.Tuple[InterpreterObject, TYPE_var], T.Tuple[InterpreterBase, FunctionNode, T.List[TYPE_var], TYPE_kwargs], T.Tuple[ModuleObject, ModuleState, T.List[TYPE_var], TYPE_kwargs], T.Tuple[OptionInterpreter, str, str, T.List[TYPE_var], TYPE_kwargs], @@ -137,19 +130,19 @@ def kwargs_get_close_matches(invalid_kwargs: T.Set[str], valid_kwargs: T.Set[str return with_close_matches def typed_operator(operator: MesonOperator, - types: T.Union[T.Type, T.Tuple[T.Type, ...]]) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']: + 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': + 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 T.cast('TV_FN_Operator', wrapper) return inner