From 31356eba4a868a095f9f25b6f3f76ce7c42059ac Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 16:17:51 +0000 Subject: [PATCH 1/8] interpreterbase: move TV_FN_Operator to baseobjects We're going to be using this more broadly, so put it in a broader place. --- mesonbuild/interpreterbase/baseobjects.py | 7 +++++++ mesonbuild/interpreterbase/decorators.py | 17 +++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 23407a42ff08..e774b3b3396a 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]) diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index ff2848cc288e..f8c38149b97a 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, ObjectHolder, TV_func, TYPE_var, TYPE_kwargs, TV_FN_Operator 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 @@ -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 From 0affabb3f3833a90262b6d599f80f3ea3b5890dc Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 16:23:13 +0000 Subject: [PATCH 2/8] interpreterbase: operators are not TV_func They are TV_FN_operator --- mesonbuild/interpreterbase/baseobjects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index e774b3b3396a..445b8c5013f4 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -97,10 +97,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 From 5076459fccd1474969abdfd851ccad71e1c115f1 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 16:27:29 +0000 Subject: [PATCH 3/8] interpreter/decorators: update callee args type --- mesonbuild/interpreterbase/decorators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index f8c38149b97a..801e6b3f5f33 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -23,7 +23,7 @@ from ..modules import ModuleObject, ModuleState from ..mparser import FunctionNode from ..optinterpreter import OptionInterpreter - from .baseobjects import InterpreterObject, ObjectHolder, TV_func, TYPE_var, TYPE_kwargs, TV_FN_Operator + from .baseobjects import InterpreterObject, TV_func, TYPE_var, TYPE_kwargs, TV_FN_Operator from .interpreterbase import InterpreterBase from .operator import MesonOperator @@ -41,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_var]) -> CalleeArgs: ... @T.overload @@ -58,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], From 93f30c9ba11d6046eda001b9b299d169ccbeb6c1 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 17:50:05 +0000 Subject: [PATCH 4/8] interpreterbase/baseobjects: trivial operators only accept a single type Not a tuple of types. --- mesonbuild/interpreterbase/baseobjects.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 445b8c5013f4..f2405fd907fd 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -40,13 +40,7 @@ def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> TYPE_var: ... 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 - ] - ] = {} + TRIVIAL_OPERATORS: dict[MesonOperator, T.Tuple[type, TYPE_op_func]] = {} OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {} From 3cbf3381b30d083a8bd6b2c3252db2875c3e2b4f Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 17:51:28 +0000 Subject: [PATCH 5/8] interpreterbase/baseobjects: op_funcs return TYPE_op_arg | bool --- mesonbuild/interpreterbase/baseobjects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index f2405fd907fd..d40fc469d48c 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -36,7 +36,7 @@ def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> 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_op_func: TypeAlias = T.Callable[[TYPE_op_arg, TYPE_op_arg], TYPE_op_arg] | T.Callable[[TYPE_op_arg, TYPE_op_arg], bool] TYPE_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var] class InterpreterObject: From 2780386c6bad99191ba34eae3dbb1eb7ddae1771 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 17:53:23 +0000 Subject: [PATCH 6/8] interperterbase/baseobjects: operators take an InterpreterObject --- mesonbuild/interpreterbase/baseobjects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index d40fc469d48c..6647b87bcbb8 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -36,7 +36,7 @@ def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> 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: TypeAlias = T.Callable[[TYPE_op_arg, TYPE_op_arg], TYPE_op_arg] | T.Callable[[TYPE_op_arg, TYPE_op_arg], bool] +TYPE_op_func: TypeAlias = T.Callable[['InterpreterObject', TYPE_op_arg], TYPE_op_arg] | T.Callable[[TYPE_op_arg, TYPE_op_arg], bool] TYPE_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var] class InterpreterObject: From 71d58c89cc3b107dea20b2494d04fcbd14a38949 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 17:54:41 +0000 Subject: [PATCH 7/8] interpreterbase/baseobjects: The first tuple memeber can be None --- mesonbuild/interpreterbase/baseobjects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 6647b87bcbb8..ae3baa8d72d6 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -40,7 +40,7 @@ def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> TYPE_var: ... TYPE_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var] class InterpreterObject: - TRIVIAL_OPERATORS: dict[MesonOperator, T.Tuple[type, TYPE_op_func]] = {} + TRIVIAL_OPERATORS: dict[MesonOperator, T.Tuple[type | None, TYPE_op_func]] = {} OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {} From 75e22aab97406294d55e8b232dc7c243081a1cb7 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 10 Jul 2026 18:05:29 +0000 Subject: [PATCH 8/8] interpreterbase/baseobjects: Fix types of trivial operators --- mesonbuild/interpreter/primitives/array.py | 5 ++-- mesonbuild/interpreter/primitives/dict.py | 3 ++- mesonbuild/interpreter/primitives/integer.py | 2 +- mesonbuild/interpreterbase/baseobjects.py | 25 +++++++++++++------- mesonbuild/interpreterbase/decorators.py | 4 ++-- 5 files changed, 24 insertions(+), 15 deletions(-) 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 ae3baa8d72d6..49aedecaabd3 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -35,14 +35,22 @@ def __call__(s, self: _TV_IntegerObject, other: _TV_ARG1) -> TYPE_var: ... 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: TypeAlias = T.Callable[['InterpreterObject', TYPE_op_arg], TYPE_op_arg] | T.Callable[[TYPE_op_arg, TYPE_op_arg], bool] +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: dict[MesonOperator, T.Tuple[type | None, 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, @@ -130,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: @@ -181,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 801e6b3f5f33..008188d275ca 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -23,7 +23,7 @@ from ..modules import ModuleObject, ModuleState from ..mparser import FunctionNode from ..optinterpreter import OptionInterpreter - from .baseobjects import InterpreterObject, TV_func, TYPE_var, TYPE_kwargs, TV_FN_Operator + 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 @@ -41,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[InterpreterObject, TYPE_var]) -> CalleeArgs: ... +def get_callee_args(wrapped_args: T.Tuple[InterpreterObject[TYPE_op_arg, InterpreterObjectTypeVar], TYPE_op_arg]) -> CalleeArgs: ... @T.overload