interpreterbase: Allow safely using mutable default values with typed_kwargs

It's really inconvenient to want a thing that is always a list, but not
be able to provide a default value of a list because of mutation. To
that end the typed_kwargs method now makes a shallow copy of the default
when using a `ContainerTypeInfo` as the type. This mean that using a
default of `[]` is perfectly safe.
pull/8838/head
Dylan Baker 4 years ago
parent 8890a62499
commit b107171307
  1. 15
      mesonbuild/interpreterbase.py
  2. 11
      run_unittests.py

@ -419,7 +419,9 @@ class KwargInfo(T.Generic[_T]):
:param listify: If true, then the argument will be listified before being
checked. This is useful for cases where the Meson DSL allows a scalar or
a container, but internally we only want to work with containers
:param default: A default value to use if this isn't set. defaults to None
:param default: A default value to use if this isn't set. defaults to None,
this may be safely set to a mutable type, as long as that type does not
itself contain mutable types, typed_kwargs will copy the default
:param since: Meson version in which this argument has been added. defaults to None
:param deprecated: Meson version in which this argument has been deprecated. defaults to None
"""
@ -444,6 +446,9 @@ def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]:
information. For non-required values it sets the value to a default, which
means the value will always be provided.
If type tyhpe is a :class:ContainerTypeInfo, then the default value will be
passed as an argument to the container initializer, making a shallow copy
:param name: the name of the function, including the object it's attached ot
(if applicable)
:param *types: KwargInfo entries for each keyword argument.
@ -491,7 +496,13 @@ def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]:
else:
# set the value to the default, this ensuring all kwargs are present
# This both simplifies the typing checking and the usage
kwargs[info.name] = info.default
# Create a shallow copy of the container (and do a type
# conversion if necessary). This allows mutable types to
# be used safely as default values
if isinstance(info.types, ContainerTypeInfo):
kwargs[info.name] = info.types.container(info.default)
else:
kwargs[info.name] = info.default
return f(*wrapped_args, **wrapped_kwargs)
return T.cast(TV_func, wrapper)

@ -1574,6 +1574,17 @@ class InternalTests(unittest.TestCase):
_(None, mock.Mock(), [], {'input': 'str'})
def test_typed_kwarg_container_default_copy(self) -> None:
default: T.List[str] = []
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=default),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None:
self.assertIsNot(kwargs['input'], default)
_(None, mock.Mock(), [], {})
def test_typed_kwarg_container_pairs(self) -> None:
@typed_kwargs(
'testfunc',

Loading…
Cancel
Save