Skip to content
5 changes: 3 additions & 2 deletions mesonbuild/interpreter/primitives/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/interpreter/primitives/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
typed_pos_args,

TYPE_var,
TYPE_elementary,

InvalidArguments,
)

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect, a dictionary can hold non-elementary types. InterpreterObjectTypeVar, as for ArrayHolder?

# Operators that only require type checks
TRIVIAL_OPERATORS = {
# Arithmetic
Expand Down
2 changes: 1 addition & 1 deletion mesonbuild/interpreter/primitives/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. It's super().operator_call that tightens the type on other, here it's still TYPE_var.

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.',
Expand Down
42 changes: 25 additions & 17 deletions mesonbuild/interpreterbase/baseobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand All @@ -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]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just moved but it seems wrong, because TYPE_var already includes HoldableObject, so this is the same as TYPE_var. Should the bound of InterpreterObjectTypeVar be just TYPE_var.

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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
21 changes: 7 additions & 14 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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],
Expand Down Expand Up @@ -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


Expand Down
Loading