diff options
| author | Paolo Bonzini <pbonzini@redhat.com> | 2025-01-08 12:26:07 +0100 |
|---|---|---|
| committer | Jussi Pakkanen <jussi.pakkanen@mailbox.org> | 2025-06-17 12:29:56 +0300 |
| commit | 2f47d0b4b15e27aa62f439c4b65282771149cc7c (patch) | |
| tree | d50002095ec899f5db9d037ff658e575e86c490d | |
| parent | f3366a7e543f69844bd3658db65c28295912db79 (diff) | |
| download | meson-2f47d0b4b15e27aa62f439c4b65282771149cc7c.tar.gz | |
interpreter: make operators per-class
Do not call update() and Enum.__hash__ a gazillion times; operators
are the same for every instance of the class. In order to access
the class for non-trivial operators, the operators are first marked
using a decorator, and then OPERATORS is built via __init_subclass__.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
| -rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 9 | ||||
| -rw-r--r-- | mesonbuild/interpreter/primitives/array.py | 11 | ||||
| -rw-r--r-- | mesonbuild/interpreter/primitives/boolean.py | 3 | ||||
| -rw-r--r-- | mesonbuild/interpreter/primitives/dict.py | 9 | ||||
| -rw-r--r-- | mesonbuild/interpreter/primitives/integer.py | 11 | ||||
| -rw-r--r-- | mesonbuild/interpreter/primitives/range.py | 7 | ||||
| -rw-r--r-- | mesonbuild/interpreter/primitives/string.py | 29 | ||||
| -rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 40 |
8 files changed, 57 insertions, 62 deletions
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index aeead5013..b249d02ab 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -15,7 +15,7 @@ from .. import mlog from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule, NewExtensionModule from ..backend.backends import TestProtocol from ..interpreterbase import ( - ContainerTypeInfo, KwargInfo, MesonOperator, + ContainerTypeInfo, KwargInfo, InterpreterObject, MesonOperator, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, FeatureNew, FeatureDeprecated, typed_pos_args, typed_kwargs, typed_operator, @@ -32,7 +32,7 @@ if T.TYPE_CHECKING: from . import kwargs from ..cmake.interpreter import CMakeInterpreter from ..envconfig import MachineInfo - from ..interpreterbase import FeatureCheckBase, InterpreterObject, SubProject, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs + from ..interpreterbase import FeatureCheckBase, SubProject, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs from .interpreter import Interpreter from typing_extensions import TypedDict @@ -1062,10 +1062,6 @@ class _CustomTargetHolder(ObjectHolder[_CT]): 'to_list': self.to_list_method, }) - self.operators.update({ - MesonOperator.INDEX: _CustomTargetHolder.op_index, - }) - def __repr__(self) -> str: r = '<{} {}: {}>' h = self.held_object @@ -1087,6 +1083,7 @@ class _CustomTargetHolder(ObjectHolder[_CT]): @noKwargs @typed_operator(MesonOperator.INDEX, int) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> build.CustomTargetIndex: try: return self.held_object[other] diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py index 088f1758b..0a116c3e4 100644 --- a/mesonbuild/interpreter/primitives/array.py +++ b/mesonbuild/interpreter/primitives/array.py @@ -5,9 +5,10 @@ from __future__ import annotations import typing as T from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, IterableObject, MesonOperator, + ObjectHolder, typed_operator, noKwargs, noPosargs, @@ -43,12 +44,6 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): 'get': self.get_method, }) - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.PLUS: ArrayHolder.op_plus, - MesonOperator.INDEX: ArrayHolder.op_index, - }) - def display_name(self) -> str: return 'array' @@ -93,6 +88,7 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): return self.held_object[index] @typed_operator(MesonOperator.PLUS, object) + @InterpreterObject.operator(MesonOperator.PLUS) def op_plus(self, other: TYPE_var) -> T.List[TYPE_var]: if not isinstance(other, list): if not isinstance(self.current_node, PlusAssignmentNode): @@ -102,6 +98,7 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): return self.held_object + other @typed_operator(MesonOperator.INDEX, int) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> TYPE_var: try: return self.held_object[other] diff --git a/mesonbuild/interpreter/primitives/boolean.py b/mesonbuild/interpreter/primitives/boolean.py index c5d6fe028..48e7aedb2 100644 --- a/mesonbuild/interpreter/primitives/boolean.py +++ b/mesonbuild/interpreter/primitives/boolean.py @@ -3,8 +3,9 @@ from __future__ import annotations from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, MesonOperator, + ObjectHolder, typed_pos_args, noKwargs, noPosargs, diff --git a/mesonbuild/interpreter/primitives/dict.py b/mesonbuild/interpreter/primitives/dict.py index 587a3015a..ffc1af27a 100644 --- a/mesonbuild/interpreter/primitives/dict.py +++ b/mesonbuild/interpreter/primitives/dict.py @@ -5,9 +5,10 @@ from __future__ import annotations import typing as T from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, IterableObject, MesonOperator, + ObjectHolder, typed_operator, noKwargs, noPosargs, @@ -45,11 +46,6 @@ class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): 'get': self.get_method, }) - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.INDEX: DictHolder.op_index, - }) - def display_name(self) -> str: return 'dict' @@ -83,6 +79,7 @@ class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): raise InvalidArguments(f'Key {args[0]!r} is not in the dictionary.') @typed_operator(MesonOperator.INDEX, str) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: str) -> TYPE_var: if other not in self.held_object: raise InvalidArguments(f'Key {other} is not in the dictionary.') diff --git a/mesonbuild/interpreter/primitives/integer.py b/mesonbuild/interpreter/primitives/integer.py index 48ada78ad..17abca3c7 100644 --- a/mesonbuild/interpreter/primitives/integer.py +++ b/mesonbuild/interpreter/primitives/integer.py @@ -3,7 +3,8 @@ from __future__ import annotations from ...interpreterbase import ( - FeatureBroken, InvalidArguments, MesonOperator, ObjectHolder, KwargInfo, + InterpreterObject, MesonOperator, ObjectHolder, + FeatureBroken, InvalidArguments, KwargInfo, noKwargs, noPosargs, typed_operator, typed_kwargs ) @@ -40,12 +41,6 @@ class IntegerHolder(ObjectHolder[int]): 'to_string': self.to_string_method, }) - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.DIV: IntegerHolder.op_div, - MesonOperator.MOD: IntegerHolder.op_mod, - }) - def display_name(self) -> str: return 'int' @@ -75,12 +70,14 @@ class IntegerHolder(ObjectHolder[int]): return str(self.held_object).zfill(kwargs['fill']) @typed_operator(MesonOperator.DIV, int) + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: int) -> int: if other == 0: raise InvalidArguments('Tried to divide by 0') return self.held_object // other @typed_operator(MesonOperator.MOD, int) + @InterpreterObject.operator(MesonOperator.MOD) def op_mod(self, other: int) -> int: if other == 0: raise InvalidArguments('Tried to divide by 0') diff --git a/mesonbuild/interpreter/primitives/range.py b/mesonbuild/interpreter/primitives/range.py index 432cfe1b2..1aceb6848 100644 --- a/mesonbuild/interpreter/primitives/range.py +++ b/mesonbuild/interpreter/primitives/range.py @@ -5,8 +5,9 @@ from __future__ import annotations import typing as T from ...interpreterbase import ( - MesonInterpreterObject, + InterpreterObject, IterableObject, + MesonInterpreterObject, MesonOperator, InvalidArguments, ) @@ -18,10 +19,8 @@ class RangeHolder(MesonInterpreterObject, IterableObject): def __init__(self, start: int, stop: int, step: int, *, subproject: 'SubProject') -> None: super().__init__(subproject=subproject) self.range = range(start, stop, step) - self.operators.update({ - MesonOperator.INDEX: RangeHolder.op_index, - }) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> int: try: return self.range[other] diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py index 673adcdc5..71efa3345 100644 --- a/mesonbuild/interpreter/primitives/string.py +++ b/mesonbuild/interpreter/primitives/string.py @@ -9,8 +9,9 @@ import typing as T from ...mesonlib import version_compare, version_compare_many from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, MesonOperator, + ObjectHolder, FeatureNew, typed_operator, noArgsFlattening, @@ -62,14 +63,6 @@ class StringHolder(ObjectHolder[str]): 'version_compare': self.version_compare_method, }) - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.DIV: StringHolder.op_div, - MesonOperator.INDEX: StringHolder.op_index, - MesonOperator.IN: StringHolder.op_in, - MesonOperator.NOT_IN: StringHolder.op_notin, - }) - def display_name(self) -> str: return 'str' @@ -181,10 +174,12 @@ class StringHolder(ObjectHolder[str]): @FeatureNew('/ with string arguments', '0.49.0') @typed_operator(MesonOperator.DIV, str) + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: str) -> str: return self._op_div(self.held_object, other) @typed_operator(MesonOperator.INDEX, int) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> str: try: return self.held_object[other] @@ -193,11 +188,13 @@ class StringHolder(ObjectHolder[str]): @FeatureNew('"in" string operator', '1.0.0') @typed_operator(MesonOperator.IN, str) + @InterpreterObject.operator(MesonOperator.IN) def op_in(self, other: str) -> bool: return other in self.held_object @FeatureNew('"not in" string operator', '1.0.0') @typed_operator(MesonOperator.NOT_IN, str) + @InterpreterObject.operator(MesonOperator.NOT_IN) def op_notin(self, other: str) -> bool: return other not in self.held_object @@ -221,12 +218,7 @@ class DependencyVariableString(str): pass class DependencyVariableStringHolder(StringHolder): - def __init__(self, obj: str, interpreter: Interpreter) -> None: - super().__init__(obj, interpreter) - self.operators.update({ - MesonOperator.DIV: DependencyVariableStringHolder.op_div, - }) - + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: str) -> T.Union[str, DependencyVariableString]: ret = super().op_div(other) if '..' in other: @@ -249,12 +241,7 @@ class OptionString(str): class OptionStringHolder(StringHolder): held_object: OptionString - def __init__(self, obj: str, interpreter: Interpreter) -> None: - super().__init__(obj, interpreter) - self.operators.update({ - MesonOperator.DIV: OptionStringHolder.op_div, - }) - + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: str) -> T.Union[str, OptionString]: ret = super().op_div(other) name = self._op_div(self.held_object.optname, other) diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 928d03861..ce961bdfb 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -43,37 +43,55 @@ class InterpreterObject: ] ] = {} + OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {} + def __init_subclass__(cls: T.Type[InterpreterObject], **kwargs: T.Any) -> None: super().__init_subclass__(**kwargs) saved_trivial_operators = cls.TRIVIAL_OPERATORS + + cls.OPERATORS = {} cls.TRIVIAL_OPERATORS = {} # Compute inherited operators according to the Python resolution order # Reverse the result of mro() because update() will overwrite operators # that are set by the superclass with those that are set by the subclass for superclass in reversed(cls.mro()[1:]): - if issubclass(superclass, InterpreterObject): + 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.OPERATORS.update(superclass.OPERATORS) cls.TRIVIAL_OPERATORS.update(superclass.TRIVIAL_OPERATORS) + for name, method in cls.__dict__.items(): + if hasattr(method, 'meson_operator'): + cls.OPERATORS[method.meson_operator] = method cls.TRIVIAL_OPERATORS.update(saved_trivial_operators) + @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, TYPE_op_func] = {} # 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.__class__.op_equals, - MesonOperator.NOT_EQUALS: self.__class__.op_not_equals, - }) - # The type of the object that can be printed to the user def display_name(self) -> str: return type(self).__name__ @@ -101,8 +119,8 @@ class InterpreterObject: 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](self, other) - if operator in self.operators: - return self.operators[operator](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.') @@ -165,12 +183,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, '!=') |
