summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2025-01-08 12:26:07 +0100
committerJussi Pakkanen <jussi.pakkanen@mailbox.org>2025-06-17 12:29:56 +0300
commit2f47d0b4b15e27aa62f439c4b65282771149cc7c (patch)
treed50002095ec899f5db9d037ff658e575e86c490d
parentf3366a7e543f69844bd3658db65c28295912db79 (diff)
downloadmeson-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.py9
-rw-r--r--mesonbuild/interpreter/primitives/array.py11
-rw-r--r--mesonbuild/interpreter/primitives/boolean.py3
-rw-r--r--mesonbuild/interpreter/primitives/dict.py9
-rw-r--r--mesonbuild/interpreter/primitives/integer.py11
-rw-r--r--mesonbuild/interpreter/primitives/range.py7
-rw-r--r--mesonbuild/interpreter/primitives/string.py29
-rw-r--r--mesonbuild/interpreterbase/baseobjects.py40
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, '!=')