summaryrefslogtreecommitdiff
path: root/mesonbuild
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2025-01-27 12:48:07 +0100
committerJussi Pakkanen <jussi.pakkanen@mailbox.org>2025-06-17 12:29:56 +0300
commit9b3932abb3bcdd2dc5957e65a5097983efaab0f0 (patch)
tree78924c3529af4f33fb24e6cf64f170f049ffbd0c /mesonbuild
parent2f47d0b4b15e27aa62f439c4b65282771149cc7c (diff)
downloadmeson-9b3932abb3bcdd2dc5957e65a5097983efaab0f0.tar.gz
interpreter: make methods per-class for primitives
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, just mark the methods using a decorator and build METHODS later using __init_subclass__. Non-primitive objects are not converted yet to keep the patch small. They are created a lot less than other objects, especially strings and booleans. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'mesonbuild')
-rw-r--r--mesonbuild/interpreter/primitives/array.py13
-rw-r--r--mesonbuild/interpreter/primitives/boolean.py11
-rw-r--r--mesonbuild/interpreter/primitives/dict.py13
-rw-r--r--mesonbuild/interpreter/primitives/integer.py13
-rw-r--r--mesonbuild/interpreter/primitives/string.py38
-rw-r--r--mesonbuild/interpreterbase/baseobjects.py39
6 files changed, 60 insertions, 67 deletions
diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py
index 0a116c3e4..ff520a280 100644
--- a/mesonbuild/interpreter/primitives/array.py
+++ b/mesonbuild/interpreter/primitives/array.py
@@ -23,8 +23,6 @@ from ...interpreterbase import (
from ...mparser import PlusAssignmentNode
if T.TYPE_CHECKING:
- # Object holders need the actual interpreter
- from ...interpreter import Interpreter
from ...interpreterbase import TYPE_kwargs
class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject):
@@ -36,14 +34,6 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject):
MesonOperator.NOT_IN: (object, lambda obj, x: x not in obj.held_object),
}
- def __init__(self, obj: T.List[TYPE_var], interpreter: 'Interpreter') -> None:
- super().__init__(obj, interpreter)
- self.methods.update({
- 'contains': self.contains_method,
- 'length': self.length_method,
- 'get': self.get_method,
- })
-
def display_name(self) -> str:
return 'array'
@@ -59,6 +49,7 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject):
@noArgsFlattening
@noKwargs
@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:
for element in el:
@@ -73,12 +64,14 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject):
@noKwargs
@noPosargs
+ @InterpreterObject.method('length')
def length_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
return len(self.held_object)
@noArgsFlattening
@noKwargs
@typed_pos_args('array.get', int, optargs=[object])
+ @InterpreterObject.method('get')
def get_method(self, args: T.Tuple[int, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var:
index = args[0]
if index < -len(self.held_object) or index >= len(self.held_object):
diff --git a/mesonbuild/interpreter/primitives/boolean.py b/mesonbuild/interpreter/primitives/boolean.py
index 48e7aedb2..eb01b9ff2 100644
--- a/mesonbuild/interpreter/primitives/boolean.py
+++ b/mesonbuild/interpreter/primitives/boolean.py
@@ -16,8 +16,6 @@ from ...interpreterbase import (
import typing as T
if T.TYPE_CHECKING:
- # Object holders need the actual interpreter
- from ...interpreter import Interpreter
from ...interpreterbase import TYPE_var, TYPE_kwargs
class BooleanHolder(ObjectHolder[bool]):
@@ -28,23 +26,18 @@ class BooleanHolder(ObjectHolder[bool]):
MesonOperator.NOT_EQUALS: (bool, lambda obj, x: obj.held_object != x),
}
- def __init__(self, obj: bool, interpreter: 'Interpreter') -> None:
- super().__init__(obj, interpreter)
- self.methods.update({
- 'to_int': self.to_int_method,
- 'to_string': self.to_string_method,
- })
-
def display_name(self) -> str:
return 'bool'
@noKwargs
@noPosargs
+ @InterpreterObject.method('to_int')
def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
return 1 if self.held_object else 0
@noKwargs
@typed_pos_args('bool.to_string', optargs=[str, str])
+ @InterpreterObject.method('to_string')
def to_string_method(self, args: T.Tuple[T.Optional[str], T.Optional[str]], kwargs: TYPE_kwargs) -> str:
true_str = args[0] or 'true'
false_str = args[1] or 'false'
diff --git a/mesonbuild/interpreter/primitives/dict.py b/mesonbuild/interpreter/primitives/dict.py
index ffc1af27a..d641fa88f 100644
--- a/mesonbuild/interpreter/primitives/dict.py
+++ b/mesonbuild/interpreter/primitives/dict.py
@@ -21,8 +21,6 @@ from ...interpreterbase import (
)
if T.TYPE_CHECKING:
- # Object holders need the actual interpreter
- from ...interpreter import Interpreter
from ...interpreterbase import TYPE_kwargs
class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject):
@@ -38,14 +36,6 @@ class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject):
MesonOperator.NOT_IN: (str, lambda obj, x: x not in obj.held_object),
}
- def __init__(self, obj: T.Dict[str, TYPE_var], interpreter: 'Interpreter') -> None:
- super().__init__(obj, interpreter)
- self.methods.update({
- 'has_key': self.has_key_method,
- 'keys': self.keys_method,
- 'get': self.get_method,
- })
-
def display_name(self) -> str:
return 'dict'
@@ -60,17 +50,20 @@ class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject):
@noKwargs
@typed_pos_args('dict.has_key', str)
+ @InterpreterObject.method('has_key')
def has_key_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
return args[0] in self.held_object
@noKwargs
@noPosargs
+ @InterpreterObject.method('keys')
def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]:
return sorted(self.held_object)
@noArgsFlattening
@noKwargs
@typed_pos_args('dict.get', str, optargs=[object])
+ @InterpreterObject.method('get')
def get_method(self, args: T.Tuple[str, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var:
if args[0] in self.held_object:
return self.held_object[args[0]]
diff --git a/mesonbuild/interpreter/primitives/integer.py b/mesonbuild/interpreter/primitives/integer.py
index 17abca3c7..c59ea6e22 100644
--- a/mesonbuild/interpreter/primitives/integer.py
+++ b/mesonbuild/interpreter/primitives/integer.py
@@ -11,8 +11,6 @@ from ...interpreterbase import (
import typing as T
if T.TYPE_CHECKING:
- # Object holders need the actual interpreter
- from ...interpreter import Interpreter
from ...interpreterbase import TYPE_var, TYPE_kwargs
class IntegerHolder(ObjectHolder[int]):
@@ -33,14 +31,6 @@ class IntegerHolder(ObjectHolder[int]):
MesonOperator.LESS_EQUALS: (int, lambda obj, x: obj.held_object <= x),
}
- def __init__(self, obj: int, interpreter: 'Interpreter') -> None:
- super().__init__(obj, interpreter)
- self.methods.update({
- 'is_even': self.is_even_method,
- 'is_odd': self.is_odd_method,
- 'to_string': self.to_string_method,
- })
-
def display_name(self) -> str:
return 'int'
@@ -53,11 +43,13 @@ class IntegerHolder(ObjectHolder[int]):
@noKwargs
@noPosargs
+ @InterpreterObject.method('is_even')
def is_even_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
return self.held_object % 2 == 0
@noKwargs
@noPosargs
+ @InterpreterObject.method('is_odd')
def is_odd_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
return self.held_object % 2 != 0
@@ -66,6 +58,7 @@ class IntegerHolder(ObjectHolder[int]):
KwargInfo('fill', int, default=0, since='1.3.0')
)
@noPosargs
+ @InterpreterObject.method('to_string')
def to_string_method(self, args: T.List[TYPE_var], kwargs: T.Dict[str, T.Any]) -> str:
return str(self.held_object).zfill(kwargs['fill'])
diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py
index 71efa3345..49dd71660 100644
--- a/mesonbuild/interpreter/primitives/string.py
+++ b/mesonbuild/interpreter/primitives/string.py
@@ -25,8 +25,6 @@ from ...interpreterbase import (
if T.TYPE_CHECKING:
- # Object holders need the actual interpreter
- from ...interpreter import Interpreter
from ...interpreterbase import TYPE_var, TYPE_kwargs
class StringHolder(ObjectHolder[str]):
@@ -43,47 +41,31 @@ class StringHolder(ObjectHolder[str]):
MesonOperator.LESS_EQUALS: (str, lambda obj, x: obj.held_object <= x),
}
- def __init__(self, obj: str, interpreter: 'Interpreter') -> None:
- super().__init__(obj, interpreter)
- self.methods.update({
- 'contains': self.contains_method,
- 'startswith': self.startswith_method,
- 'endswith': self.endswith_method,
- 'format': self.format_method,
- 'join': self.join_method,
- 'replace': self.replace_method,
- 'split': self.split_method,
- 'splitlines': self.splitlines_method,
- 'strip': self.strip_method,
- 'substring': self.substring_method,
- 'to_int': self.to_int_method,
- 'to_lower': self.to_lower_method,
- 'to_upper': self.to_upper_method,
- 'underscorify': self.underscorify_method,
- 'version_compare': self.version_compare_method,
- })
-
def display_name(self) -> str:
return 'str'
@noKwargs
@typed_pos_args('str.contains', str)
+ @InterpreterObject.method('contains')
def contains_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
return self.held_object.find(args[0]) >= 0
@noKwargs
@typed_pos_args('str.startswith', str)
+ @InterpreterObject.method('startswith')
def startswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
return self.held_object.startswith(args[0])
@noKwargs
@typed_pos_args('str.endswith', str)
+ @InterpreterObject.method('endswith')
def endswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
return self.held_object.endswith(args[0])
@noArgsFlattening
@noKwargs
@typed_pos_args('str.format', varargs=object)
+ @InterpreterObject.method('format')
def format_method(self, args: T.Tuple[T.List[TYPE_var]], kwargs: TYPE_kwargs) -> str:
arg_strings: T.List[str] = []
for arg in args[0]:
@@ -104,27 +86,32 @@ class StringHolder(ObjectHolder[str]):
@noKwargs
@noPosargs
@FeatureNew('str.splitlines', '1.2.0')
+ @InterpreterObject.method('splitlines')
def splitlines_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]:
return self.held_object.splitlines()
@noKwargs
@typed_pos_args('str.join', varargs=str)
+ @InterpreterObject.method('join')
def join_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> str:
return self.held_object.join(args[0])
@noKwargs
@FeatureNew('str.replace', '0.58.0')
@typed_pos_args('str.replace', str, str)
+ @InterpreterObject.method('replace')
def replace_method(self, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> str:
return self.held_object.replace(args[0], args[1])
@noKwargs
@typed_pos_args('str.split', optargs=[str])
+ @InterpreterObject.method('split')
def split_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]:
return self.held_object.split(args[0])
@noKwargs
@typed_pos_args('str.strip', optargs=[str])
+ @InterpreterObject.method('strip')
def strip_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> str:
if args[0]:
FeatureNew.single_use('str.strip with a positional argument', '0.43.0', self.subproject, location=self.current_node)
@@ -133,6 +120,7 @@ class StringHolder(ObjectHolder[str]):
@noKwargs
@FeatureNew('str.substring', '0.56.0')
@typed_pos_args('str.substring', optargs=[int, int])
+ @InterpreterObject.method('substring')
def substring_method(self, args: T.Tuple[T.Optional[int], T.Optional[int]], kwargs: TYPE_kwargs) -> str:
start = args[0] if args[0] is not None else 0
end = args[1] if args[1] is not None else len(self.held_object)
@@ -140,6 +128,7 @@ class StringHolder(ObjectHolder[str]):
@noKwargs
@noPosargs
+ @InterpreterObject.method('to_int')
def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
try:
return int(self.held_object)
@@ -148,20 +137,24 @@ class StringHolder(ObjectHolder[str]):
@noKwargs
@noPosargs
+ @InterpreterObject.method('to_lower')
def to_lower_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self.held_object.lower()
@noKwargs
@noPosargs
+ @InterpreterObject.method('to_upper')
def to_upper_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self.held_object.upper()
@noKwargs
@noPosargs
+ @InterpreterObject.method('underscorify')
def underscorify_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return re.sub(r'[^a-zA-Z0-9]', '_', self.held_object)
@noKwargs
+ @InterpreterObject.method('version_compare')
@typed_pos_args('str.version_compare', varargs=str, min_varargs=1)
def version_compare_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> bool:
if len(args[0]) > 1:
@@ -205,6 +198,7 @@ class MesonVersionString(str):
class MesonVersionStringHolder(StringHolder):
@noKwargs
@typed_pos_args('str.version_compare', str)
+ @InterpreterObject.method('version_compare')
def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool:
self.interpreter.tmp_meson_version = args[0]
return version_compare(self.held_object, args[0])
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py
index ce961bdfb..44fc871fd 100644
--- a/mesonbuild/interpreterbase/baseobjects.py
+++ b/mesonbuild/interpreterbase/baseobjects.py
@@ -31,6 +31,8 @@ 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_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var]
+
SubProject = T.NewType('SubProject', str)
@@ -45,16 +47,22 @@ class InterpreterObject:
OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {}
+ METHODS: T.Dict[
+ str,
+ TYPE_method_func,
+ ] = {}
+
def __init_subclass__(cls: T.Type[InterpreterObject], **kwargs: T.Any) -> None:
super().__init_subclass__(**kwargs)
saved_trivial_operators = cls.TRIVIAL_OPERATORS
+ cls.METHODS = {}
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
+ # Compute inherited operators and methods according to the Python resolution
+ # order. Reverse the result of mro() because update() will overwrite entries
+ # that are set by the superclass with those that are set by the subclass.
for superclass in reversed(cls.mro()[1:]):
if superclass is InterpreterObject:
# InterpreterObject cannot use @InterpreterObject.operator because
@@ -65,15 +73,27 @@ class InterpreterObject:
})
elif issubclass(superclass, InterpreterObject):
+ cls.METHODS.update(superclass.METHODS)
cls.OPERATORS.update(superclass.OPERATORS)
cls.TRIVIAL_OPERATORS.update(superclass.TRIVIAL_OPERATORS)
for name, method in cls.__dict__.items():
+ if hasattr(method, 'meson_method'):
+ cls.METHODS[method.meson_method] = method
if hasattr(method, 'meson_operator'):
cls.OPERATORS[method.meson_operator] = method
cls.TRIVIAL_OPERATORS.update(saved_trivial_operators)
@staticmethod
+ def method(name: str) -> T.Callable[[TV_func], TV_func]:
+ '''Decorator that tags a Python method as the implementation of a method
+ for the Meson interpreter'''
+ def decorator(f: TV_func) -> TV_func:
+ f.meson_method = name # type: ignore[attr-defined]
+ return f
+ return decorator
+
+ @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'''
@@ -102,13 +122,20 @@ class InterpreterObject:
args: T.List[TYPE_var],
kwargs: TYPE_kwargs
) -> TYPE_var:
- if method_name in self.methods:
- method = self.methods[method_name]
+ if method_name in self.METHODS:
+ method = self.METHODS[method_name]
if not getattr(method, 'no-args-flattening', False):
args = flatten(args)
if not getattr(method, 'no-second-level-holder-flattening', False):
args, kwargs = resolve_second_level_holders(args, kwargs)
- return method(args, kwargs)
+ return method(self, args, kwargs)
+ if method_name in self.methods:
+ bound_method = self.methods[method_name]
+ if not getattr(bound_method, 'no-args-flattening', False):
+ args = flatten(args)
+ if not getattr(bound_method, 'no-second-level-holder-flattening', False):
+ args, kwargs = resolve_second_level_holders(args, kwargs)
+ return bound_method(args, kwargs)
raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.')
def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var: