From 9b3932abb3bcdd2dc5957e65a5097983efaab0f0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 27 Jan 2025 12:48:07 +0100 Subject: 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 --- mesonbuild/interpreter/primitives/array.py | 13 +++------- mesonbuild/interpreter/primitives/boolean.py | 11 ++------ mesonbuild/interpreter/primitives/dict.py | 13 +++------- mesonbuild/interpreter/primitives/integer.py | 13 +++------- mesonbuild/interpreter/primitives/string.py | 38 ++++++++++++--------------- mesonbuild/interpreterbase/baseobjects.py | 39 +++++++++++++++++++++++----- 6 files changed, 60 insertions(+), 67 deletions(-) (limited to 'mesonbuild') 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,14 +73,26 @@ 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 @@ -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: -- cgit v1.2.3