mirror of
https://github.com/mesonbuild/meson.git
synced 2026-11-06 05:16:42 +00:00
interpreterbase: Add support for positional arguments to TypedArgs
This commit is contained in:
1 parent
d8d074ad60
commit
e31a5b78f7
5 files changed
+692
-54
No files matched your search
@@ -14,7 +14,10 @@ from ..build import (CustomTarget, BuildTarget,
|
||||
from ..options import OptionKey
|
||||
from ..dependencies import Dependency, DependencyMethods, InternalDependency
|
||||
from ..interpreterbase import Feature
|
||||
from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo, FeatureBroken, FeatureDeprecated, FeatureNew
|
||||
from ..interpreterbase.decorators import (
|
||||
KwargInfo, ContainerTypeInfo, FeatureBroken, FeatureDeprecated, FeatureNew,
|
||||
PosArgInfo, OptArgInfo, VarArgInfo,
|
||||
)
|
||||
from ..mesonlib import (File, FileMode, MachineChoice, has_path_sep, listify, stringlistify,
|
||||
EnvironmentVariables)
|
||||
from ..programs import Program, ExternalProgram
|
||||
@@ -1157,3 +1160,23 @@ DEPENDENCY_KWS: T.List[KwargInfo] = [
|
||||
KwargInfo('static', (bool, NoneType)),
|
||||
KwargInfo('version', ContainerTypeInfo(list, str), listify=True, default=[]),
|
||||
]
|
||||
|
||||
STR_PARG = PosArgInfo(str)
|
||||
STR_FILE_PARG = PosArgInfo((str, File))
|
||||
INT_PARG = PosArgInfo(int)
|
||||
BOOL_PARG = PosArgInfo(bool)
|
||||
OBJ_PARG = PosArgInfo(object)
|
||||
|
||||
STR_OARG = OptArgInfo(str)
|
||||
INT_OARG = OptArgInfo(int)
|
||||
BOOL_OARG = OptArgInfo(bool)
|
||||
OBJ_OARG = OptArgInfo(object)
|
||||
|
||||
STR_VARG = VarArgInfo(str)
|
||||
STR_VARG_1 = VarArgInfo(str, min_args=1)
|
||||
STR_FILE_VARG = VarArgInfo((str, File))
|
||||
INT_VARG = VarArgInfo(int)
|
||||
BOOL_VARG = VarArgInfo(bool)
|
||||
OBJ_VARG = VarArgInfo(object)
|
||||
SRC_VARG = VarArgInfo(SOURCES_VARARGS)
|
||||
TGT_VARG = VarArgInfo((str, File, CustomTarget, CustomTargetIndex, GeneratedList))
|
||||
@@ -39,11 +39,13 @@ __all__ = [
|
||||
'ContainerTypeInfo',
|
||||
'KwargInfo',
|
||||
'TypedArgs',
|
||||
'PosArgInfo',
|
||||
'OptArgInfo',
|
||||
'VarArgInfo',
|
||||
'FeatureCheckBase',
|
||||
'FeatureNew',
|
||||
'FeatureDeprecated',
|
||||
'FeatureBroken',
|
||||
'TypedArgs',
|
||||
|
||||
'InterpreterBase',
|
||||
|
||||
@@ -97,6 +99,9 @@ from .decorators import (
|
||||
FeatureDeprecated,
|
||||
FeatureBroken,
|
||||
TypedArgs,
|
||||
PosArgInfo,
|
||||
OptArgInfo,
|
||||
VarArgInfo,
|
||||
)
|
||||
|
||||
from .exceptions import (
|
||||
|
||||
@@ -63,6 +63,32 @@ if T.TYPE_CHECKING:
|
||||
extra_types: T.Mapping[type, T.Callable[[object], str]] | None
|
||||
as_default: list[tuple[object, str | tuple[str, str]]] | None
|
||||
|
||||
class _PosArgKWs(T.TypedDict, total=False):
|
||||
|
||||
since: str | None
|
||||
since_message: str
|
||||
since_values: _FeatureValues | None
|
||||
deprecated: str | None
|
||||
deprecated_message: str
|
||||
deprecated_values: _FeatureValues | None
|
||||
feature_validator: T.Callable[[T.Any], T.Iterable[FeatureCheckBase]] | None
|
||||
validator: T.Callable[[T.Any], str | None] | None
|
||||
convertor: T.Callable[[T.Any], object] | None
|
||||
listify: bool
|
||||
|
||||
class _OptArgKWs(_PosArgKWs, total=False):
|
||||
|
||||
default: TYPE_var | None
|
||||
optional_since: str | None
|
||||
optional_since_message: str
|
||||
|
||||
class _VarArgKWs(_PosArgKWs, total=False):
|
||||
|
||||
min_args: int
|
||||
max_args: int
|
||||
variadic_since: str | None
|
||||
variadic_since_message: str
|
||||
|
||||
|
||||
def is_module(obj: object) -> TypeIs[ModuleObject]:
|
||||
return not hasattr(obj, 'current_node')
|
||||
@@ -492,10 +518,60 @@ class KwargInfo(T.Generic[_T]):
|
||||
return dataclasses.replace(self, **kwargs)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True, eq=False)
|
||||
class _PosArgInfoBase:
|
||||
|
||||
types: type | T.Tuple[type | ContainerTypeInfo, ...] | ContainerTypeInfo
|
||||
since: str | None = dataclasses.field(default=None, kw_only=True)
|
||||
since_message: str = dataclasses.field(default='', kw_only=True)
|
||||
since_values: _FeatureValues | None = dataclasses.field(default=None, kw_only=True)
|
||||
deprecated: str | None = dataclasses.field(default=None, kw_only=True)
|
||||
deprecated_message: str = dataclasses.field(default='', kw_only=True)
|
||||
deprecated_values: _FeatureValues | None = dataclasses.field(default=None, kw_only=True)
|
||||
feature_validator: T.Callable[[T.Any], T.Iterable[FeatureCheckBase]] | None = \
|
||||
dataclasses.field(default=None, kw_only=True)
|
||||
validator: T.Optional[T.Callable[[T.Any], str | None]] = dataclasses.field(default=None, kw_only=True)
|
||||
convertor: T.Optional[T.Callable[[T.Any], object]] = dataclasses.field(default=None, kw_only=True)
|
||||
listify: bool = dataclasses.field(default=False, kw_only=True)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True, eq=False)
|
||||
class PosArgInfo(_PosArgInfoBase):
|
||||
|
||||
def evolve(self, **kwargs: Unpack[_PosArgKWs]) -> PosArgInfo:
|
||||
return dataclasses.replace(self, **kwargs)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True, eq=False)
|
||||
class OptArgInfo(_PosArgInfoBase):
|
||||
|
||||
default: TYPE_var | None = dataclasses.field(default=None, kw_only=True)
|
||||
optional_since: str | None = dataclasses.field(default=None, kw_only=True)
|
||||
optional_since_message: str = dataclasses.field(default='', kw_only=True)
|
||||
|
||||
def evolve(self, **kwargs: Unpack[_OptArgKWs]) -> OptArgInfo:
|
||||
return dataclasses.replace(self, **kwargs)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True, eq=False)
|
||||
class VarArgInfo(_PosArgInfoBase):
|
||||
|
||||
min_args: int = dataclasses.field(default=0, kw_only=True)
|
||||
max_args: int = dataclasses.field(default=0, kw_only=True)
|
||||
variadic_since: str | None = dataclasses.field(default=None, kw_only=True)
|
||||
variadic_since_message: str = dataclasses.field(default='', kw_only=True)
|
||||
|
||||
def evolve(self, **kwargs: Unpack[_VarArgKWs]) -> VarArgInfo:
|
||||
return dataclasses.replace(self, **kwargs)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True, eq=False)
|
||||
class TypedArgs:
|
||||
|
||||
name: str
|
||||
pos_types: list[PosArgInfo] | None = dataclasses.field(default=None, kw_only=True)
|
||||
opt_types: list[OptArgInfo] = dataclasses.field(default_factory=list, kw_only=True)
|
||||
var_types: VarArgInfo | None = dataclasses.field(default=None, kw_only=True)
|
||||
kw_types: list[KwargInfo] = dataclasses.field(default_factory=list, kw_only=True)
|
||||
unknown_kwargs: bool = dataclasses.field(default=False, kw_only=True)
|
||||
|
||||
@@ -533,7 +609,7 @@ class TypedArgs:
|
||||
def _process_kwargs(self, node: mparser.BaseNode | None, _kwargs: TYPE_kwargs, subproject: SubProject) -> None:
|
||||
# Cast here, as the convertor function may place something other than a TYPE_var in the kwargs
|
||||
kwargs = T.cast('T.Dict[str, object]', _kwargs)
|
||||
make_err = partial(InvalidArguments.from_node, node=node) if node else InvalidArguments
|
||||
make_err = partial(InvalidArguments.from_node, node=node) if node is not None else InvalidArguments
|
||||
|
||||
if not self.unknown_kwargs:
|
||||
all_names = {t.name for t in self.kw_types}
|
||||
@@ -623,13 +699,166 @@ class TypedArgs:
|
||||
if info.convertor:
|
||||
kwargs[info.name] = info.convertor(kwargs[info.name])
|
||||
|
||||
def _pw_emit_feature_change(self, value: object, values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]],
|
||||
feature: T.Type[FeatureDeprecated | FeatureNew],
|
||||
subproject: SubProject, node: mparser.BaseNode | None,
|
||||
index: int) -> None:
|
||||
for n, version in values.items():
|
||||
if isinstance(version, tuple):
|
||||
version, msg = version
|
||||
else:
|
||||
msg = ''
|
||||
|
||||
warning: T.Optional[str] = None
|
||||
if isinstance(n, ContainerTypeInfo):
|
||||
if n.check_any(value):
|
||||
d, extra = n.description()
|
||||
warning = f'of type "{d}"'
|
||||
if extra:
|
||||
warning = f'{warning} {extra}'
|
||||
elif isinstance(n, (type, tuple)):
|
||||
if isinstance(value, n):
|
||||
warning = f'of type "{type(value).__name__}"'
|
||||
elif isinstance(value, list):
|
||||
if n in value:
|
||||
warning = f'value "{n}" in list'
|
||||
elif isinstance(value, dict):
|
||||
if n in value:
|
||||
warning = f'value "{n}" in dict keys'
|
||||
elif n == value:
|
||||
warning = f'value "{n}"'
|
||||
if warning:
|
||||
feature.single_use(f'"{self.name}" positional argument "{index}" {warning}', version, subproject, msg, location=node)
|
||||
|
||||
def _process_args(self, node: mparser.BaseNode | None, args: list[TYPE_var], subproject: SubProject) -> tuple[object, ...] | None:
|
||||
if self.pos_types is None and not (self.opt_types or self.var_types):
|
||||
return None
|
||||
|
||||
assert not (self.opt_types and self.var_types), \
|
||||
'Cannot use optional arguments and variadic arguments together due to ambiguity'
|
||||
|
||||
nargs = T.cast('list[object]', args).copy()
|
||||
num_args = len(args)
|
||||
num_types = len(self.pos_types or [])
|
||||
types: tuple[_PosArgInfoBase, ...] = tuple(self.pos_types or [])
|
||||
make_err = partial(InvalidArguments.from_node, node=node) if node is not None else InvalidArguments
|
||||
|
||||
if self.var_types:
|
||||
min_args = num_types + self.var_types.min_args
|
||||
max_args = num_types + self.var_types.max_args
|
||||
if self.var_types.max_args == 0 and num_args < min_args:
|
||||
raise make_err(f'"{self.name}" takes at least {min_args} arguments, but got {num_args}.')
|
||||
elif self.var_types.max_args != 0 and (num_args < min_args or num_args > max_args):
|
||||
raise make_err(f'"{self.name}" takes between {min_args} and {max_args} arguments, but got {num_args}.')
|
||||
elif self.opt_types:
|
||||
if num_args < num_types:
|
||||
raise make_err(f'"{self.name}" takes at least {num_types} arguments, but got {num_args}.')
|
||||
|
||||
num_types_tot = num_types + len(self.opt_types)
|
||||
if num_args > num_types_tot:
|
||||
raise make_err(f'"{self.name}" takes at most {num_types_tot} arguments, but got {num_args}.')
|
||||
|
||||
# The number of unset optional arguments we need to use the default values for
|
||||
unset_opt_args = num_args - num_types
|
||||
# Add the types to cover optional arguments that have been passed
|
||||
types = tuple(list(types) + list(self.opt_types[:unset_opt_args]))
|
||||
assert len(types) == num_args, (len(types), num_args)
|
||||
elif num_args != num_types:
|
||||
raise make_err(f'"{self.name}" takes exactly {num_types} arguments, but got {num_args}.')
|
||||
|
||||
for i, (value, info) in enumerate(itertools.zip_longest(args, types, fillvalue=self.var_types), start=1):
|
||||
assert info is not None, 'We should never get a None info'
|
||||
types_tuple = info.types if isinstance(info.types, tuple) else (info.types,)
|
||||
|
||||
if isinstance(value, DefaultObject) and DefaultObject not in types_tuple:
|
||||
if isinstance(info, PosArgInfo):
|
||||
raise make_err('default() objects are not allowed for required positional arguments')
|
||||
elif isinstance(info, OptArgInfo):
|
||||
FeatureNew.single_use('default() object for optional positional arguments', '1.13.0',
|
||||
subproject, location=node)
|
||||
# Replace the DefaultObject with the default value, but skip further validation.
|
||||
# If the default is None, and we don't have None in the types_tuple (common),
|
||||
# then we'll fail validation later.
|
||||
nargs[i - 1] = value = copy.copy(info.default)
|
||||
continue
|
||||
else:
|
||||
raise make_err('default() objects are not allowed for variadic arguments')
|
||||
|
||||
if info.since:
|
||||
feature_name = f'positional argument "{i}" in {self.name}'
|
||||
FeatureNew.single_use(feature_name, info.since, subproject, info.since_message, location=node)
|
||||
|
||||
if info.deprecated:
|
||||
feature_name = f'positional argument "{i}" in {self.name}'
|
||||
FeatureDeprecated.single_use(feature_name, info.deprecated, subproject, info.deprecated_message, location=node)
|
||||
|
||||
if info.listify:
|
||||
nargs[i - 1] = value = mesonlib.listify(value)
|
||||
|
||||
if not _check_value_type(types_tuple, value):
|
||||
raise make_err(_shouldbe_format(self.name, 'positional', str(i), value, types_tuple))
|
||||
|
||||
if info.validator is not None:
|
||||
msg = info.validator(value)
|
||||
if msg is not None:
|
||||
raise make_err(f'"{self.name}" positional argument "{i}" {msg}')
|
||||
|
||||
if info.feature_validator is not None:
|
||||
for each in info.feature_validator(value):
|
||||
each.use(subproject, node)
|
||||
|
||||
if info.deprecated_values is not None:
|
||||
self._pw_emit_feature_change(
|
||||
value, info.deprecated_values, FeatureDeprecated, subproject, node, i)
|
||||
|
||||
if info.since_values is not None:
|
||||
self._pw_emit_feature_change(
|
||||
value, info.since_values, FeatureNew, subproject, node, i)
|
||||
|
||||
if info.convertor:
|
||||
nargs[i - 1] = info.convertor(value)
|
||||
|
||||
if self.opt_types:
|
||||
for i, info in enumerate(self.opt_types[unset_opt_args:], start=num_args):
|
||||
if info.optional_since:
|
||||
feature_name = f'positional argument "{i}" in {self.name} as optional'
|
||||
FeatureNew.single_use(feature_name, info.optional_since, subproject,
|
||||
info.optional_since_message, node)
|
||||
|
||||
nargs.append(copy.copy(info.default))
|
||||
elif self.var_types:
|
||||
# if we have varargs we need to split them into a separate
|
||||
# tuple, as python's typing doesn't understand tuples with
|
||||
# fixed elements and variadic elements, only one or the other.
|
||||
# so in that case we need T.Tuple[int, str, float, T.Tuple[str, ...]]
|
||||
pos = nargs[:len(types)]
|
||||
var = list(nargs[len(types):])
|
||||
nargs = pos
|
||||
nargs.append(var)
|
||||
|
||||
if self.var_types.variadic_since is not None and len(var) > 1:
|
||||
FeatureNew.single_use(f'"{self.name}": More than one variadic argument',
|
||||
self.var_types.variadic_since,
|
||||
subproject, location=node)
|
||||
|
||||
return tuple(nargs)
|
||||
|
||||
# TODO: need to use two different types here to avoid passing the original type through
|
||||
def __call__(self, f: TV_func) -> T.Callable[..., T.Any]:
|
||||
@wraps(f)
|
||||
def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
|
||||
node, _, _kwargs, subproject = get_callee_args(wrapped_args)
|
||||
node, _args, _kwargs, subproject = get_callee_args(wrapped_args)
|
||||
|
||||
assert _args is not None, 'for mypy'
|
||||
assert _kwargs is not None, 'for mypy'
|
||||
|
||||
self._process_kwargs(node, _kwargs, subproject)
|
||||
args = self._process_args(node, _args, subproject)
|
||||
if args is not None:
|
||||
w = list(wrapped_args)
|
||||
i = w.index(_args)
|
||||
w[i] = args
|
||||
wrapped_args = tuple(w)
|
||||
|
||||
return f(*wrapped_args, **wrapped_kwargs)
|
||||
return T.cast('T.Callable[..., T.Any]', wrapper)
|
||||
|
||||
@@ -34,10 +34,6 @@ testcase expect_error('default() objects are not allowed for variadic arguments'
|
||||
add_languages(default())
|
||||
endtestcase
|
||||
|
||||
testcase expect_error('default() objects are not allowed for optional positional arguments')
|
||||
assert(true, default())
|
||||
endtestcase
|
||||
|
||||
testcase expect_error('"install_symlink" got a default() value for the required keyword argument "install_dir". default() may not be used for required keyword arguments.')
|
||||
install_symlink('foo', pointing_to : '/foo', install_dir : default())
|
||||
endtestcase
|
||||
@@ -46,3 +42,5 @@ foo = default()
|
||||
get_variable('foo')
|
||||
unset_variable('foo')
|
||||
set_variable('var', default())
|
||||
|
||||
assert(true, default())
|
||||
+429
-46
@@ -37,15 +37,20 @@ from mesonbuild.compilers.d import DmdDCompiler, LLVMDCompiler
|
||||
from mesonbuild.compilers.detect import detect_c_compiler
|
||||
from mesonbuild.compilers.mixins.visualstudio import MSVCCompiler, ClangClCompiler
|
||||
from mesonbuild.linkers import linkers
|
||||
from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, ObjectHolder
|
||||
from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, TypedArgs, ContainerTypeInfo, KwargInfo
|
||||
from mesonbuild.interpreterbase import (
|
||||
ObjectHolder, DefaultObject, InvalidArguments, TypedArgs, ContainerTypeInfo,
|
||||
KwargInfo, PosArgInfo, VarArgInfo,
|
||||
)
|
||||
from mesonbuild.mesonlib import (
|
||||
LibType, MachineChoice, PerMachine, SimpleABC, Version, is_windows, is_osx,
|
||||
is_cygwin, is_openbsd, search_version, MesonException, EnvironmentException, python_command,
|
||||
version_check_to_range,
|
||||
)
|
||||
from mesonbuild.options import OptionKey
|
||||
from mesonbuild.interpreter.type_checking import in_set_validator, NoneType
|
||||
from mesonbuild.interpreter.type_checking import (
|
||||
STR_PARG, INT_PARG, BOOL_PARG, STR_OARG, INT_OARG, STR_VARG,
|
||||
in_set_validator, NoneType,
|
||||
)
|
||||
from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface, PkgConfigCLI
|
||||
from mesonbuild.programs import ExternalProgram
|
||||
import mesonbuild.modules.pkgconfig
|
||||
@@ -1509,39 +1514,43 @@ Thread model: posix'''), '21.9.0')
|
||||
self.assertFalse(errors)
|
||||
|
||||
def test_typed_pos_args_types(self) -> None:
|
||||
@typed_pos_args('foo', str, int, bool)
|
||||
@TypedArgs('foo', pos_types=[STR_PARG, INT_PARG, BOOL_PARG])
|
||||
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
|
||||
self.assertIsInstance(args, tuple)
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertIsInstance(args[1], int)
|
||||
self.assertIsInstance(args[2], bool)
|
||||
|
||||
_(None, mock.Mock(), ['string', 1, False], None)
|
||||
_(None, mock.Mock(), ['string', 1, False], {})
|
||||
|
||||
def test_typed_pos_args_types_invalid(self) -> None:
|
||||
@typed_pos_args('foo', str, int, bool)
|
||||
@TypedArgs('foo', pos_types=[STR_PARG, INT_PARG, BOOL_PARG])
|
||||
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 1.0, False], None)
|
||||
_(None, mock.Mock(), ['string', 1.0, False], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" positional argument "2" was of type "float" but should have been "int"')
|
||||
|
||||
def test_typed_pos_args_types_wrong_number(self) -> None:
|
||||
@typed_pos_args('foo', str, int, bool)
|
||||
@TypedArgs('foo', pos_types=[STR_PARG, INT_PARG, BOOL_PARG])
|
||||
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 1], None)
|
||||
_(None, mock.Mock(), ['string', 1], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes exactly 3 arguments, but got 2.')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 1, True, True], None)
|
||||
_(None, mock.Mock(), ['string', 1, True, True], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes exactly 3 arguments, but got 4.')
|
||||
|
||||
def test_typed_pos_args_varargs(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG,
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.assertIsInstance(args, tuple)
|
||||
self.assertIsInstance(args[0], str)
|
||||
@@ -1549,38 +1558,54 @@ Thread model: posix'''), '21.9.0')
|
||||
self.assertIsInstance(args[1][0], str)
|
||||
self.assertIsInstance(args[1][1], str)
|
||||
|
||||
_(None, mock.Mock(), ['string', 'var', 'args'], None)
|
||||
_(None, mock.Mock(), ['string', 'var', 'args'], {})
|
||||
|
||||
def test_typed_pos_args_varargs_not_given(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG,
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.assertIsInstance(args, tuple)
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertIsInstance(args[1], list)
|
||||
self.assertEqual(args[1], [])
|
||||
|
||||
_(None, mock.Mock(), ['string'], None)
|
||||
_(None, mock.Mock(), ['string'], {})
|
||||
|
||||
def test_typed_pos_args_varargs_invalid(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG,
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
|
||||
_(None, mock.Mock(), ['string', 'var', 'args', 0], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" positional argument "4" was of type "int" but should have been "str"')
|
||||
|
||||
def test_typed_pos_args_varargs_invalid_multiple_types(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=(str, list))
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=VarArgInfo((str, list)),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
|
||||
_(None, mock.Mock(), ['string', 'var', 'args', 0], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" positional argument "4" was of type "int" but should have been one of: "str", "list"')
|
||||
|
||||
def test_typed_pos_args_max_varargs(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str, max_varargs=5)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG.evolve(max_args=5),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.assertIsInstance(args, tuple)
|
||||
self.assertIsInstance(args[0], str)
|
||||
@@ -1588,95 +1613,135 @@ Thread model: posix'''), '21.9.0')
|
||||
self.assertIsInstance(args[1][0], str)
|
||||
self.assertIsInstance(args[1][1], str)
|
||||
|
||||
_(None, mock.Mock(), ['string', 'var', 'args'], None)
|
||||
_(None, mock.Mock(), ['string', 'var', 'args'], {})
|
||||
|
||||
def test_typed_pos_args_max_varargs_exceeded(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str, max_varargs=1)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG.evolve(max_args=1),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 'var', 'args'], None)
|
||||
_(None, mock.Mock(), ['string', 'var', 'args'], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes between 1 and 2 arguments, but got 3.')
|
||||
|
||||
def test_typed_pos_args_min_varargs(self) -> None:
|
||||
@typed_pos_args('foo', varargs=str, max_varargs=2, min_varargs=1)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
var_types=STR_VARG.evolve(max_args=2, min_args=1),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.assertIsInstance(args, tuple)
|
||||
self.assertIsInstance(args[0], list)
|
||||
self.assertIsInstance(args[0][0], str)
|
||||
self.assertIsInstance(args[0][1], str)
|
||||
|
||||
_(None, mock.Mock(), ['string', 'var'], None)
|
||||
_(None, mock.Mock(), ['string', 'var'], {})
|
||||
|
||||
def test_typed_pos_args_min_varargs_not_met(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str, min_varargs=1)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG.evolve(min_args=1),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string'], None)
|
||||
_(None, mock.Mock(), ['string'], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes at least 2 arguments, but got 1.')
|
||||
|
||||
def test_typed_pos_args_min_and_max_varargs_exceeded(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG.evolve(min_args=1, max_args=2),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', 'var', 'args', 'bar'], None)
|
||||
_(None, mock.Mock(), ['string', 'var', 'args', 'bar'], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes between 2 and 3 arguments, but got 4.')
|
||||
|
||||
def test_typed_pos_args_min_and_max_varargs_not_met(self) -> None:
|
||||
@typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
var_types=STR_VARG.evolve(min_args=1, max_args=2),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string'], None)
|
||||
_(None, mock.Mock(), ['string'], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes between 2 and 3 arguments, but got 1.')
|
||||
|
||||
def test_typed_pos_args_variadic_and_optional(self) -> None:
|
||||
@typed_pos_args('foo', str, optargs=[str], varargs=str, min_varargs=0)
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
opt_types=[STR_OARG],
|
||||
var_types=STR_VARG.evolve(min_args=0),
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(AssertionError) as cm:
|
||||
_(None, mock.Mock(), ['string'], None)
|
||||
_(None, mock.Mock(), ['string'], {})
|
||||
self.assertEqual(
|
||||
str(cm.exception),
|
||||
'varargs and optargs not supported together as this would be ambiguous')
|
||||
'Cannot use optional arguments and variadic arguments together due to ambiguity')
|
||||
|
||||
def test_typed_pos_args_min_optargs_not_met(self) -> None:
|
||||
@typed_pos_args('foo', str, str, optargs=[str])
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG, STR_PARG],
|
||||
opt_types=[STR_OARG],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string'], None)
|
||||
_(None, mock.Mock(), ['string'], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes at least 2 arguments, but got 1.')
|
||||
|
||||
def test_typed_pos_args_min_optargs_max_exceeded(self) -> None:
|
||||
@typed_pos_args('foo', str, optargs=[str])
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
opt_types=[STR_OARG],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
||||
self.fail('Should not be reachable')
|
||||
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), ['string', '1', '2'], None)
|
||||
_(None, mock.Mock(), ['string', '1', '2'], {})
|
||||
self.assertEqual(str(cm.exception), '"foo" takes at most 2 arguments, but got 3.')
|
||||
|
||||
def test_typed_pos_args_optargs_not_given(self) -> None:
|
||||
@typed_pos_args('foo', str, optargs=[str])
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
opt_types=[STR_OARG],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
||||
self.assertEqual(len(args), 2)
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertEqual(args[0], 'string')
|
||||
self.assertIsNone(args[1])
|
||||
|
||||
_(None, mock.Mock(), ['string'], None)
|
||||
_(None, mock.Mock(), ['string'], {})
|
||||
|
||||
def test_typed_pos_args_optargs_some_given(self) -> None:
|
||||
@typed_pos_args('foo', str, optargs=[str, int])
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
opt_types=[STR_OARG, INT_OARG],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Optional[str], T.Optional[int]], kwargs) -> None:
|
||||
self.assertEqual(len(args), 3)
|
||||
self.assertIsInstance(args[0], str)
|
||||
@@ -1685,17 +1750,334 @@ Thread model: posix'''), '21.9.0')
|
||||
self.assertEqual(args[1], '1')
|
||||
self.assertIsNone(args[2])
|
||||
|
||||
_(None, mock.Mock(), ['string', '1'], None)
|
||||
_(None, mock.Mock(), ['string', '1'], {})
|
||||
|
||||
def test_typed_pos_args_optargs_all_given(self) -> None:
|
||||
@typed_pos_args('foo', str, optargs=[str])
|
||||
@TypedArgs(
|
||||
'foo',
|
||||
pos_types=[STR_PARG],
|
||||
opt_types=[STR_OARG],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
||||
self.assertEqual(len(args), 2)
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertEqual(args[0], 'string')
|
||||
self.assertIsInstance(args[1], str)
|
||||
|
||||
_(None, mock.Mock(), ['string', '1'], None)
|
||||
_(None, mock.Mock(), ['string', '1'], {})
|
||||
|
||||
def test_typed_pos_args_since(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
STR_PARG.evolve(
|
||||
since='1.0',
|
||||
since_message='It\'s awesome, use it',
|
||||
deprecated='2.0',
|
||||
deprecated_message='It\'s terrible, don\'t use it'),
|
||||
],
|
||||
)
|
||||
def _(obj, node, args: tuple[str], kwargs: dict) -> None:
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertEqual(args[0], 'foo')
|
||||
|
||||
with self.subTest('use before available'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=0.1'])}):
|
||||
# With Meson 0.1 it should trigger the "introduced" warning but not the "deprecated" warning
|
||||
_(None, mock.Mock(subproject=''), ['foo'], {})
|
||||
self.assertRegex(out.getvalue(), r'WARNING:.*introduced.*positional argument "\d" in testfunc. It\'s awesome, use it')
|
||||
self.assertNotRegex(out.getvalue(), r'WARNING:.*deprecated.*argument "\d" in testfunc. It\'s terrible, don\'t use it')
|
||||
|
||||
with self.subTest('no warnings should be triggered'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=1.5'])}):
|
||||
# With Meson 1.5 it shouldn't trigger any warning
|
||||
_(None, mock.Mock(subproject=''), ['foo'], {})
|
||||
self.assertNotRegex(out.getvalue(), r'WARNING:.*')
|
||||
|
||||
with self.subTest('use after deprecated'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=2.0'])}):
|
||||
# With Meson 2.0 it should trigger the "deprecated" warning but not the "introduced" warning
|
||||
_(None, mock.Mock(subproject=''), ['foo'], {})
|
||||
self.assertRegex(out.getvalue(), r'WARNING:.*deprecated.*positional argument "\d" in testfunc. It\'s terrible, don\'t use it')
|
||||
self.assertNotRegex(out.getvalue(), r'WARNING:.*introduced.*positional argument "\d" in testfunc. It\'s awesome, use it')
|
||||
|
||||
def test_typed_pos_args_optional_since(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
opt_types=[
|
||||
STR_OARG.evolve(
|
||||
default='foo',
|
||||
optional_since='1.0',
|
||||
optional_since_message='Woo!',
|
||||
)
|
||||
],
|
||||
)
|
||||
def _(obj, node, args: tuple[str], kwargs: dict) -> None:
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertEqual(args[0], 'foo')
|
||||
|
||||
with self.subTest('use before available'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=0.1'])}):
|
||||
# With Meson 0.1 it should trigger the "introduced" warning but not the "deprecated" warning
|
||||
_(None, mock.Mock(subproject=''), [], {})
|
||||
self.assertIn('WARNING: Project targets \'>= 0.1\' but uses feature introduced in \'1.0\': positional argument "0" in testfunc as optional. Woo!',
|
||||
out.getvalue())
|
||||
|
||||
with self.subTest('no warnings should be triggered'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=1.5'])}):
|
||||
# With Meson 1.5 it shouldn't trigger any warning
|
||||
_(None, mock.Mock(subproject=''), [], {})
|
||||
self.assertNotRegex(out.getvalue(), r'WARNING:.*')
|
||||
|
||||
with self.subTest('use after deprecated'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=2.0'])}):
|
||||
# With Meson 2.0 it should trigger the "deprecated" warning but not the "introduced" warning
|
||||
_(None, mock.Mock(subproject=''), [], {})
|
||||
self.assertNotIn('WARNING: Project targets \'>= 0.1\' but uses feature introduced in \'1.0\': positional argument "0" in testfunc as optional. Woo!',
|
||||
out.getvalue())
|
||||
|
||||
def test_typed_pos_args_container(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[PosArgInfo(ContainerTypeInfo(list, str))],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[list[str]], kwargs: dict) -> None:
|
||||
self.assertEqual(args[0], ['str'])
|
||||
|
||||
with self.subTest('valid'):
|
||||
_(None, mock.Mock(), [['str']], {})
|
||||
|
||||
with self.subTest('invalid'):
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), [0], {})
|
||||
self.assertEqual(str(cm.exception), '"testfunc" positional argument "1" was of type "int" but should have been "array[str]"')
|
||||
|
||||
def test_typed_pos_args_contained(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[PosArgInfo(ContainerTypeInfo(dict, str))],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.Dict[str, str]]) -> None:
|
||||
self.assertEqual(args[0], {'key': 'value'})
|
||||
|
||||
with self.subTest('valid'):
|
||||
_(None, mock.Mock(), [{'key': 'value'}], {})
|
||||
|
||||
with self.subTest('invalid'):
|
||||
with self.assertRaises(InvalidArguments) as cm:
|
||||
_(None, mock.Mock(), [{'key': 0}], {})
|
||||
self.assertEqual(str(cm.exception), '"testfunc" positional argument "1" was of type "dict[int]" but should have been "dict[str]"')
|
||||
|
||||
def test_typed_pos_args_validator(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
STR_PARG.evolve(validator=lambda x: 'invalid!' if x != 'foo' else None)
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
# Should be valid
|
||||
_(None, mock.Mock(), ['foo'], {})
|
||||
|
||||
with self.assertRaises(MesonException) as cm:
|
||||
_(None, mock.Mock(), ['bar'], {})
|
||||
self.assertEqual(str(cm.exception), "\"testfunc\" positional argument \"1\" invalid!")
|
||||
|
||||
def test_typed_pos_args_convertor(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
BOOL_PARG.evolve(convertor=lambda n: MachineChoice.BUILD if n else MachineChoice.HOST)
|
||||
],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[MachineChoice], kwargs: dict) -> None:
|
||||
assert isinstance(args[0], MachineChoice)
|
||||
|
||||
_(None, mock.Mock(), [True], {})
|
||||
|
||||
@mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=1.0'])})
|
||||
def test_typed_pos_args_since_values(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo(ContainerTypeInfo(list, str), listify=True, deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('deprecated array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [['foo']], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.9': "testfunc" positional argument "1" value "foo".*""")
|
||||
|
||||
with self.subTest('new array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [['bar']], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" value "bar".*""")
|
||||
|
||||
with self.subTest('deprecated array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), ['foo'], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.9': "testfunc" positional argument "1" value "foo".*""")
|
||||
|
||||
with self.subTest('new array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), ['bar'], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" value "bar".*""")
|
||||
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo(ContainerTypeInfo(dict, str), deprecated_values={'foo': '0.9', 'foo2': ('0.9', 'don\'t use it')}, since_values={'bar': '1.1', 'bar2': ('1.1', 'use this')}),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('deprecated dict string value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [{'foo': 'a'}], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.9': "testfunc" positional argument "1" value "foo".*""")
|
||||
|
||||
with self.subTest('deprecated dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [{'foo2': 'a'}], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.9': "testfunc" positional argument "1" value "foo2" in dict keys. don't use it.*""")
|
||||
|
||||
with self.subTest('new dict string value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [{'bar': 'a'}], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" value "bar".*""")
|
||||
|
||||
with self.subTest('new dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [{'bar2': 'a'}], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" value "bar2" in dict keys. use this.*""")
|
||||
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo(
|
||||
(str, int, ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str), ContainerTypeInfo(list, int)),
|
||||
since_values={str: '1.1', ContainerTypeInfo(list, str): '1.2', ContainerTypeInfo(dict, str): '1.3'},
|
||||
deprecated_values={int: '0.8', ContainerTypeInfo(list, int): '0.9'},
|
||||
),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('new string type'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), ['foo'], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" of type "str".*""")
|
||||
|
||||
with self.subTest('new array of string type'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [['foo']], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '>= 1.0'.*introduced in '1.2': "testfunc" positional argument "1" of type "array\[str\]".*""")
|
||||
|
||||
with self.subTest('new dict of string type'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [{'plop': 'foo'}], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '>= 1.0'.*introduced in '1.3': "testfunc" positional argument "1" of type "dict\[str\]".*""")
|
||||
|
||||
with self.subTest('deprecated int value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [1], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.8': "testfunc" positional argument "1" of type "int".*""")
|
||||
|
||||
with self.subTest('deprecated array int value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [[1]], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.9': "testfunc" positional argument "1" of type "array\[int\]".*""")
|
||||
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo(
|
||||
(ContainerTypeInfo(list, (str, int))),
|
||||
listify=True,
|
||||
since_values={ContainerTypeInfo(list, str): '1.1', ContainerTypeInfo(list, int): '1.2'},
|
||||
),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('new list[str] value'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [['foo', 42]], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" of type "array\[str\]".*""")
|
||||
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '>= 1.0'.*introduced in '1.2': "testfunc" positional argument "1" of type "array\[int\]".*""")
|
||||
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo((bool, str, NoneType), deprecated_values={False: '0.9'}),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('non string union'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [False], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '0.9': "testfunc" positional argument "1" value "False".*""")
|
||||
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo(
|
||||
(str, type(None)),
|
||||
validator=in_set_validator({'clean', 'build', 'rebuild', 'deprecated', 'since'}),
|
||||
deprecated_values={'deprecated': '1.0'},
|
||||
since_values={'since': '1.1'},
|
||||
),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('deprecated string union'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), ['deprecated'], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*deprecated since '1.0': "testfunc" positional argument "1" value "deprecated".*""")
|
||||
|
||||
with self.subTest('new string union'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), ['since'], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*introduced in '1.1': "testfunc" positional argument "1" value "since".*""")
|
||||
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[
|
||||
PosArgInfo(
|
||||
(ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str)),
|
||||
since_values={list: '1.9'},
|
||||
),
|
||||
]
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
pass
|
||||
|
||||
with self.subTest('new container'), mock.patch('sys.stdout', io.StringIO()) as out:
|
||||
_(None, mock.Mock(subproject=''), [['a=b']], {})
|
||||
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '>= 1.0'.*introduced in '1.9': "testfunc" positional argument "1" of type "list".*""")
|
||||
|
||||
def test_typed_pos_args_default(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
opt_types=[STR_OARG.evolve(default='foo')],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple[str], kwargs: dict) -> None:
|
||||
self.assertEqual(len(args), 1)
|
||||
self.assertIsInstance(args[0], str)
|
||||
self.assertEqual(args[0], 'foo')
|
||||
|
||||
with self.subTest('default object before available'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=1.0'])}):
|
||||
_(None, mock.Mock(subproject=''), [DefaultObject()], {})
|
||||
self.assertRegex(out.getvalue(), r'WARNING: Project targets \'>= 1.0\' but uses feature introduced in \'1.13.0\': default\(\) object for optional')
|
||||
|
||||
with self.subTest('default object after available'), \
|
||||
mock.patch('sys.stdout', io.StringIO()) as out, \
|
||||
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=1.13'])}):
|
||||
_(None, mock.Mock(subproject=''), [DefaultObject()], {})
|
||||
self.assertNotRegex(out.getvalue(), r'WARNING: Project targets \'>= 1.0\' but uses feature introduced in \'1.13.0\': default\(\) object for optional')
|
||||
|
||||
def test_typed_kwarg_basic(self) -> None:
|
||||
@TypedArgs(
|
||||
@@ -1853,10 +2235,10 @@ Thread model: posix'''), '21.9.0')
|
||||
pass
|
||||
|
||||
# Should be valid
|
||||
_(None, mock.Mock(), tuple(), dict(input='foo'))
|
||||
_(None, mock.Mock(), [], dict(input='foo'))
|
||||
|
||||
with self.assertRaises(MesonException) as cm:
|
||||
_(None, mock.Mock(), tuple(), dict(input='bar'))
|
||||
_(None, mock.Mock(), [], dict(input='bar'))
|
||||
self.assertEqual(str(cm.exception), "\"testfunc\" keyword argument \"input\" invalid!")
|
||||
|
||||
def test_typed_kwarg_convertor(self) -> None:
|
||||
@@ -1867,7 +2249,7 @@ Thread model: posix'''), '21.9.0')
|
||||
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, MachineChoice]) -> None:
|
||||
assert isinstance(kwargs['native'], MachineChoice)
|
||||
|
||||
_(None, mock.Mock(), tuple(), dict(native=True))
|
||||
_(None, mock.Mock(), [], dict(native=True))
|
||||
|
||||
@mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': version_check_to_range(['>=1.0'])})
|
||||
def test_typed_kwarg_since_values(self) -> None:
|
||||
@@ -2039,6 +2421,7 @@ Thread model: posix'''), '21.9.0')
|
||||
def test_typed_kwarg_container_in_tuple(self) -> None:
|
||||
@TypedArgs(
|
||||
'testfunc',
|
||||
pos_types=[PosArgInfo(object)],
|
||||
kw_types=[KwargInfo('input', (str, ContainerTypeInfo(list, str)))],
|
||||
)
|
||||
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
|
||||
|
||||
Reference in new issue
Block a user