diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2024-04-05 08:55:20 -0700 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2024-04-14 19:40:02 +0300 |
commit | 9f02d0a3e5a5ffc82256391c244b1af38e41ef78 (patch) | |
tree | abd84b53919405a6ea3de5494720e88df7ae23c1 /mesonbuild | |
parent | 1dcffb635f3ba3dae63e6aa1b61d3cf56c9cc49b (diff) | |
download | meson-9f02d0a3e5a5ffc82256391c244b1af38e41ef78.zip meson-9f02d0a3e5a5ffc82256391c244b1af38e41ef78.tar.gz meson-9f02d0a3e5a5ffc82256391c244b1af38e41ef78.tar.bz2 |
Clarify mutable objects usage
Only Environment and ConfigurationData are mutable. However, only
ConfigurationData becomes immutable after first use which is
inconsistent.
This deprecates modification after first use of Environment object and
clarify documentation.
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 26 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/_unholder.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 21 |
3 files changed, 40 insertions, 11 deletions
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 5f712a5..edf785c 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -284,6 +284,7 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu def __init__(self, obj: mesonlib.EnvironmentVariables, interpreter: 'Interpreter'): super().__init__(obj, interpreter) + MutableInterpreterObject.__init__(self) self.methods.update({'set': self.set_method, 'unset': self.unset_method, 'append': self.append_method, @@ -308,12 +309,14 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu @typed_kwargs('environment.set', ENV_SEPARATOR_KW) def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: name, values = args + self.check_used(self.interpreter, fatal=False) self.held_object.set(name, values, kwargs['separator']) @FeatureNew('environment.unset', '1.4.0') @typed_pos_args('environment.unset', str) @noKwargs def unset_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> None: + self.check_used(self.interpreter, fatal=False) self.held_object.unset(args[0]) @typed_pos_args('environment.append', str, varargs=str, min_varargs=1) @@ -321,6 +324,7 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: name, values = args self.warn_if_has_name(name) + self.check_used(self.interpreter, fatal=False) self.held_object.append(name, values, kwargs['separator']) @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1) @@ -328,6 +332,7 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: name, values = args self.warn_if_has_name(name) + self.check_used(self.interpreter, fatal=False) self.held_object.prepend(name, values, kwargs['separator']) @@ -338,6 +343,7 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte def __init__(self, obj: build.ConfigurationData, interpreter: 'Interpreter'): super().__init__(obj, interpreter) + MutableInterpreterObject.__init__(self) self.methods.update({'set': self.set_method, 'set10': self.set10_method, 'set_quoted': self.set_quoted_method, @@ -349,32 +355,31 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte }) def __deepcopy__(self, memo: T.Dict) -> 'ConfigurationDataHolder': - return ConfigurationDataHolder(copy.deepcopy(self.held_object), self.interpreter) - - def is_used(self) -> bool: - return self.held_object.used - - def __check_used(self) -> None: + obj = ConfigurationDataHolder(copy.deepcopy(self.held_object), self.interpreter) if self.is_used(): - raise InterpreterException("Can not set values on configuration object that has been used.") + # Copies of used ConfigurationData used to be immutable. It is now + # allowed but we need this flag to print a FeatureNew warning if + # that happens. + obj.mutable_feature_new = True + return obj @typed_pos_args('configuration_data.set', str, (str, int, bool)) @typed_kwargs('configuration_data.set', _CONF_DATA_SET_KWS) def set_method(self, args: T.Tuple[str, T.Union[str, int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None: - self.__check_used() + self.check_used(self.interpreter) self.held_object.values[args[0]] = (args[1], kwargs['description']) @typed_pos_args('configuration_data.set_quoted', str, str) @typed_kwargs('configuration_data.set_quoted', _CONF_DATA_SET_KWS) def set_quoted_method(self, args: T.Tuple[str, str], kwargs: 'kwargs.ConfigurationDataSet') -> None: - self.__check_used() + self.check_used(self.interpreter) escaped_val = '\\"'.join(args[1].split('"')) self.held_object.values[args[0]] = (f'"{escaped_val}"', kwargs['description']) @typed_pos_args('configuration_data.set10', str, (int, bool)) @typed_kwargs('configuration_data.set10', _CONF_DATA_SET_KWS) def set10_method(self, args: T.Tuple[str, T.Union[int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None: - self.__check_used() + self.check_used(self.interpreter) # bool is a subclass of int, so we need to check for bool explicitly. # We already have typed_pos_args checking that this is either a bool or # an int. @@ -437,6 +442,7 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @noKwargs def merge_from_method(self, args: T.Tuple[build.ConfigurationData], kwargs: TYPE_kwargs) -> None: from_object = args[0] + self.check_used(self.interpreter) self.held_object.values.update(from_object.values) diff --git a/mesonbuild/interpreterbase/_unholder.py b/mesonbuild/interpreterbase/_unholder.py index c62aafe..e32b77f 100644 --- a/mesonbuild/interpreterbase/_unholder.py +++ b/mesonbuild/interpreterbase/_unholder.py @@ -5,7 +5,7 @@ from __future__ import annotations import typing as T -from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, HoldableTypes +from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, HoldableTypes, MutableInterpreterObject from .exceptions import InvalidArguments from ..mesonlib import HoldableObject, MesonBugException @@ -13,6 +13,8 @@ if T.TYPE_CHECKING: from .baseobjects import TYPE_var def _unholder(obj: InterpreterObject) -> TYPE_var: + if isinstance(obj, MutableInterpreterObject): + obj.mark_used() if isinstance(obj, ObjectHolder): assert isinstance(obj.held_object, HoldableTypes) return obj.held_object diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 9a119a9..c6864a4 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -120,6 +120,27 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' + def __init__(self) -> None: + self.used = False + self.mutable_feature_new = False + + def mark_used(self) -> None: + self.used = True + + def is_used(self) -> bool: + return self.used + + def check_used(self, interpreter: Interpreter, fatal: bool = True) -> None: + from .decorators import FeatureDeprecated, FeatureNew + if self.is_used(): + if fatal: + raise InvalidArguments('Can not modify object after it has been used.') + FeatureDeprecated.single_use('Modify object after it has been used', '1.5.0', + interpreter.subproject, location=interpreter.current_node) + elif self.mutable_feature_new: + FeatureNew.single_use('Modify a copy of an immutable object', '1.5.0', + interpreter.subproject, location=interpreter.current_node) + self.mutable_feature_new = False HoldableTypes = (HoldableObject, int, bool, str, list, dict) TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject] |