aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2021-08-27 11:32:42 -0400
committerXavier Claessens <xclaesse@gmail.com>2021-10-09 18:13:34 -0400
commit709d151eb944d764b35008ca3275b02bd16a765d (patch)
treeed731fc59be4131eb3b8f2cc9e5951b4899f07a6 /mesonbuild
parent329d111709ab5c5140f75f29c7176c9546de5770 (diff)
downloadmeson-709d151eb944d764b35008ca3275b02bd16a765d.zip
meson-709d151eb944d764b35008ca3275b02bd16a765d.tar.gz
meson-709d151eb944d764b35008ca3275b02bd16a765d.tar.bz2
typed_kwargs: Fix when ContainerTypeInfo is used in a tuple
info.types could be a tuple like (str, ContainerTypeInfo()). That means we have to check types one by one and only print error if none of them matched. Also fix the case when default value is None for a container type, it should leave the value to None to be able to distinguish between unset and empty list.
Diffstat (limited to 'mesonbuild')
-rw-r--r--mesonbuild/interpreterbase/decorators.py87
1 files changed, 52 insertions, 35 deletions
diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py
index 4c8d824..54a4960 100644
--- a/mesonbuild/interpreterbase/decorators.py
+++ b/mesonbuild/interpreterbase/decorators.py
@@ -22,6 +22,7 @@ from ._unholder import _unholder
from functools import wraps
import abc
import itertools
+import copy
import typing as T
if T.TYPE_CHECKING:
from .. import mparser
@@ -302,28 +303,40 @@ class ContainerTypeInfo:
self.pairs = pairs
self.allow_empty = allow_empty
- def check(self, value: T.Any) -> T.Optional[str]:
+ def check(self, value: T.Any) -> bool:
"""Check that a value is valid.
:param value: A value to check
- :return: If there is an error then a string message, otherwise None
+ :return: True if it is valid, False otherwise
"""
if not isinstance(value, self.container):
- return f'container type was "{type(value).__name__}", but should have been "{self.container.__name__}"'
+ return False
iter_ = iter(value.values()) if isinstance(value, dict) else iter(value)
for each in iter_:
if not isinstance(each, self.contains):
- if isinstance(self.contains, tuple):
- shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in self.contains))
- else:
- shouldbe = f'"{self.contains.__name__}"'
- return f'contained a value of type "{type(each).__name__}" but should have been {shouldbe}'
+ return False
if self.pairs and len(value) % 2 != 0:
- return 'container should be of even length, but is not'
+ return False
if not value and not self.allow_empty:
- return 'container is empty, but not allowed to be'
- return None
+ return False
+ return True
+
+ def description(self) -> str:
+ """Human readable description of this container type.
+ :return: string to be printed
+ """
+ container = 'dict' if self.container is dict else 'list'
+ if isinstance(self.contains, tuple):
+ contains = ','.join([t.__name__ for t in self.contains])
+ else:
+ contains = self.contains.__name__
+ s = f'{container}[{contains}]'
+ if self.pairs:
+ s += ' that has even size'
+ if not self.allow_empty:
+ s += ' that cannot be empty'
+ return s
_T = T.TypeVar('_T')
@@ -365,8 +378,8 @@ class KwargInfo(T.Generic[_T]):
:param not_set_warning: A warning messsage that is logged if the kwarg is not
set by the user.
"""
-
- def __init__(self, name: str, types: T.Union[T.Type[_T], T.Tuple[T.Type[_T], ...], ContainerTypeInfo],
+ def __init__(self, name: str,
+ types: T.Union[T.Type[_T], T.Tuple[T.Union[T.Type[_T], ContainerTypeInfo], ...], ContainerTypeInfo],
*, required: bool = False, listify: bool = False,
default: T.Optional[_T] = None,
since: T.Optional[str] = None,
@@ -456,6 +469,25 @@ def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]:
raise InvalidArguments(f'{name} got unknown keyword arguments {ustr}')
for info in types:
+ types_tuple = info.types if isinstance(info.types, tuple) else (info.types,)
+ def check_value_type(value: T.Any) -> bool:
+ for t in types_tuple:
+ if isinstance(t, ContainerTypeInfo):
+ if t.check(value):
+ return True
+ elif isinstance(value, t):
+ return True
+ return False
+ def types_description() -> str:
+ candidates = []
+ for t in types_tuple:
+ if isinstance(t, ContainerTypeInfo):
+ candidates.append(t.description())
+ else:
+ candidates.append(t.__name__)
+ shouldbe = 'one of: ' if len(candidates) > 1 else ''
+ shouldbe += ', '.join(candidates)
+ return shouldbe
value = kwargs.get(info.name)
if value is not None:
if info.since:
@@ -466,17 +498,9 @@ def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]:
FeatureDeprecated.single_use(feature_name, info.deprecated, subproject)
if info.listify:
kwargs[info.name] = value = mesonlib.listify(value)
- if isinstance(info.types, ContainerTypeInfo):
- msg = info.types.check(value)
- if msg is not None:
- raise InvalidArguments(f'{name} keyword argument "{info.name}" {msg}')
- else:
- if not isinstance(value, info.types):
- if isinstance(info.types, tuple):
- shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in info.types))
- else:
- shouldbe = f'"{info.types.__name__}"'
- raise InvalidArguments(f'{name} keyword argument "{info.name}"" was of type "{type(value).__name__}" but should have been {shouldbe}')
+ if not check_value_type(value):
+ shouldbe = types_description()
+ raise InvalidArguments(f'{name} keyword argument {info.name!r} was of type {type(value).__name__!r} but should have been {shouldbe}')
if info.validator is not None:
msg = info.validator(value)
@@ -509,17 +533,10 @@ 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
- # 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):
- assert isinstance(info.default, info.types.container), f'In function {name} default value of {info.name} is not a valid type, got {type(info.default)}, expected {info.types.container}[{info.types.contains}]'
- for item in info.default:
- assert isinstance(item, info.types.contains), f'In function {name} default value of {info.name}, container has invalid value of {item}, which is of type {type(item)}, but should be {info.types.contains}'
- kwargs[info.name] = info.types.container(info.default)
- else:
- assert isinstance(info.default, info.types), f'In funcion {name} default value of {info.name} is not a valid type, got {type(info.default)} expected {info.types}'
- kwargs[info.name] = info.default
+ assert check_value_type(info.default), f'In funcion {name} default value of {info.name} is not a valid type, got {type(info.default)} expected {types_description()}'
+ # Create a shallow copy of the container. This allows mutable
+ # types to be used safely as default values
+ kwargs[info.name] = copy.copy(info.default)
if info.not_set_warning:
mlog.warning(info.not_set_warning)