diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2023-01-25 11:00:43 -0500 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2023-03-04 14:19:27 -0500 |
commit | 0f67913dee7d4173b1f7c5e95268d7bc4d4d858d (patch) | |
tree | 516cf931cba047f1f352b0b38ed8e0626b8a9fb8 | |
parent | 2d26c272c3225f99c945d2c510c61910c34a9377 (diff) | |
download | meson-0f67913dee7d4173b1f7c5e95268d7bc4d4d858d.zip meson-0f67913dee7d4173b1f7c5e95268d7bc4d4d858d.tar.gz meson-0f67913dee7d4173b1f7c5e95268d7bc4d4d858d.tar.bz2 |
typed_kwargs: Extend since_values and deprecated_values for types
-rw-r--r-- | mesonbuild/interpreter/type_checking.py | 9 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/decorators.py | 51 | ||||
-rw-r--r-- | mesonbuild/modules/rust.py | 4 | ||||
-rw-r--r-- | mesonbuild/optinterpreter.py | 8 | ||||
-rw-r--r-- | test cases/common/40 options/test.json | 2 | ||||
-rw-r--r-- | test cases/rust/12 bindgen/test.json | 2 | ||||
-rw-r--r-- | unittests/internaltests.py | 42 |
7 files changed, 81 insertions, 37 deletions
diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 7e1bd80..129668b 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -13,7 +13,6 @@ from ..build import (CustomTarget, BuildTarget, BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable) from ..coredata import UserFeatureOption from ..dependencies import Dependency, InternalDependency -from ..interpreterbase import FeatureNew from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo from ..mesonlib import (File, FileMode, MachineChoice, listify, has_path_sep, OptionKey, EnvironmentVariables) @@ -26,7 +25,6 @@ if T.TYPE_CHECKING: from typing_extensions import Literal from ..interpreterbase import TYPE_var - from ..interpreterbase.decorators import FeatureCheckBase from ..mesonlib import EnvInitValueType _FullEnvInitValueType = T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], EnvInitValueType, str, None] @@ -376,13 +374,6 @@ INCLUDE_DIRECTORIES: KwargInfo[T.List[T.Union[str, IncludeDirs]]] = KwargInfo( default=[], ) -def include_dir_string_new(val: T.List[T.Union[str, IncludeDirs]]) -> T.Iterable[FeatureCheckBase]: - strs = [v for v in val if isinstance(v, str)] - if strs: - str_msg = ", ".join(f"'{s}'" for s in strs) - yield FeatureNew('include_directories kwarg of type string', '1.0.0', - f'Use include_directories({str_msg}) instead') - # for cases like default_options and override_options DEFAULT_OPTIONS: KwargInfo[T.List[str]] = KwargInfo( 'default_options', diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index 7486915..7916072 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -313,15 +313,25 @@ class ContainerTypeInfo: if not isinstance(value, self.container): return False iter_ = iter(value.values()) if isinstance(value, dict) else iter(value) - for each in iter_: - if not isinstance(each, self.contains): - return False + if any(not isinstance(i, self.contains) for i in iter_): + return False if self.pairs and len(value) % 2 != 0: return False if not value and not self.allow_empty: return False return True + def check_any(self, value: T.Any) -> bool: + """Check a value should emit new/deprecated feature. + + :param value: A value to check + :return: True if any of the items in value matches, False otherwise + """ + if not isinstance(value, self.container): + return False + iter_ = iter(value.values()) if isinstance(value, dict) else iter(value) + return any(isinstance(i, self.contains) for i in iter_) + def description(self) -> str: """Human readable description of this container type. @@ -390,10 +400,10 @@ class KwargInfo(T.Generic[_T]): default: T.Optional[_T] = None, since: T.Optional[str] = None, since_message: T.Optional[str] = None, - since_values: T.Optional[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]]] = None, + since_values: T.Optional[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]]] = None, deprecated: T.Optional[str] = None, deprecated_message: T.Optional[str] = None, - deprecated_values: T.Optional[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]]] = None, + deprecated_values: T.Optional[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]]] = None, feature_validator: T.Optional[T.Callable[[_T], T.Iterable[FeatureCheckBase]]] = None, validator: T.Optional[T.Callable[[T.Any], T.Optional[str]]] = None, convertor: T.Optional[T.Callable[[_T], object]] = None, @@ -421,10 +431,10 @@ class KwargInfo(T.Generic[_T]): default: T.Union[_T, None, _NULL_T] = _NULL, since: T.Union[str, None, _NULL_T] = _NULL, since_message: T.Union[str, None, _NULL_T] = _NULL, - since_values: T.Union[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL, + since_values: T.Union[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL, deprecated: T.Union[str, None, _NULL_T] = _NULL, deprecated_message: T.Union[str, None, _NULL_T] = _NULL, - deprecated_values: T.Union[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL, + deprecated_values: T.Union[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL, feature_validator: T.Union[T.Callable[[_T], T.Iterable[FeatureCheckBase]], None, _NULL_T] = _NULL, validator: T.Union[T.Callable[[_T], T.Optional[str]], None, _NULL_T] = _NULL, convertor: T.Union[T.Callable[[_T], TYPE_var], None, _NULL_T] = _NULL) -> 'KwargInfo': @@ -512,23 +522,28 @@ def typed_kwargs(name: str, *types: KwargInfo, allow_unknown: bool = False) -> T def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], feature: T.Union[T.Type['FeatureDeprecated'], T.Type['FeatureNew']]) -> None: for n, version in values.items(): - warn = False if isinstance(version, tuple): version, msg = version else: msg = None - if n in {dict, list}: - assert isinstance(n, type), 'for mypy' + warning: T.Optional[str] = None + if isinstance(n, ContainerTypeInfo): + if n.check_any(value): + warning = f'of type {n.description()}' + elif isinstance(n, type): if isinstance(value, n): - feature.single_use(f'"{name}" keyword argument "{info.name}" of type {n.__name__}', version, subproject, msg, location=node) - elif isinstance(value, (dict, list)): - warn = n in value - else: - warn = n == value - - if warn: - feature.single_use(f'"{name}" keyword argument "{info.name}" value "{n}"', version, subproject, msg, location=node) + warning = f'of type {n.__name__}' + elif isinstance(value, list): + if n in value: + warning = f'value "{n}" in list' + elif isinstance(value, dict): + if n in value.keys(): + warning = f'value "{n}" in dict keys' + elif n == value: + warning = f'value "{n}"' + if warning: + feature.single_use(f'"{name}" keyword argument "{info.name}" {warning}', version, subproject, msg, location=node) node, _, _kwargs, subproject = get_callee_args(wrapped_args) # Cast here, as the convertor function may place something other than a TYPE_var in the kwargs diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 126b894..6c3200d 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -20,7 +20,7 @@ from . import ExtensionModule, ModuleReturnValue, ModuleInfo from .. import mlog from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, IncludeDirs, CustomTarget, StructuredSources from ..dependencies import Dependency, ExternalLibrary -from ..interpreter.type_checking import DEPENDENCIES_KW, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES, include_dir_string_new +from ..interpreter.type_checking import DEPENDENCIES_KW, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs from ..mesonlib import File @@ -172,7 +172,7 @@ class RustModule(ExtensionModule): listify=True, required=True, ), - INCLUDE_DIRECTORIES.evolve(feature_validator=include_dir_string_new), + INCLUDE_DIRECTORIES.evolve(since_values={ContainerTypeInfo(list, str): '1.0.0'}), OUTPUT_KW, DEPENDENCIES_KW.evolve(since='1.0.0'), ) diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index d165639..8377614 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -20,7 +20,7 @@ from . import coredata from . import mesonlib from . import mparser from . import mlog -from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo, FeatureDeprecated +from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo from .interpreter.type_checking import NoneType, in_set_validator if T.TYPE_CHECKING: @@ -182,7 +182,7 @@ class OptionInterpreter: (bool, str, ContainerTypeInfo(dict, str), ContainerTypeInfo(list, str)), default=False, since='0.60.0', - feature_validator=lambda x: [FeatureNew('string value to "deprecated" keyword argument', '0.63.0')] if isinstance(x, str) else [] + since_values={str: '0.63.0'}, ), KwargInfo('yield', bool, default=coredata.DEFAULT_YIELDING, since='0.45.0'), allow_unknown=True, @@ -223,7 +223,7 @@ class OptionInterpreter: (bool, str), default=True, validator=lambda x: None if isinstance(x, bool) or x in {'true', 'false'} else 'boolean options must have boolean values', - deprecated_values={'true': ('1.1.0', 'use a boolean, not a string'), 'false': ('1.1.0', 'use a boolean, not a string')}, + deprecated_values={str: ('1.1.0', 'use a boolean, not a string')}, ), ) def boolean_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> coredata.UserOption: @@ -247,7 +247,7 @@ class OptionInterpreter: 'value', (int, str), default=True, - feature_validator=lambda x: [FeatureDeprecated('number values as strings', '1.1.0', 'use a raw number instead')] if isinstance(x, str) else [], + deprecated_values={str: ('1.1.0', 'use an integer, not a string')}, convertor=int, ), KwargInfo('min', (int, NoneType)), diff --git a/test cases/common/40 options/test.json b/test cases/common/40 options/test.json index d1800c6..3b34c44 100644 --- a/test cases/common/40 options/test.json +++ b/test cases/common/40 options/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": " * 1.1.0: {'\"boolean option\" keyword argument \"value\" value \"false\"', '\"boolean option\" keyword argument \"value\" value \"true\"', 'number values as strings'}" + "line": " * 1.1.0: {'\"boolean option\" keyword argument \"value\" of type str', '\"boolean option\" keyword argument \"value\" of type str', '\"integer option\" keyword argument \"value\" of type str'}" } ] } diff --git a/test cases/rust/12 bindgen/test.json b/test cases/rust/12 bindgen/test.json index 11cefc3..ceedcc6 100644 --- a/test cases/rust/12 bindgen/test.json +++ b/test cases/rust/12 bindgen/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/rust/12 bindgen/meson.build:30: WARNING: Project targets '>= 0.63' but uses feature introduced in '1.0.0': include_directories kwarg of type string. Use include_directories('include') instead" + "line": "test cases/rust/12 bindgen/meson.build:30: WARNING: Project targets '>= 0.63' but uses feature introduced in '1.0.0': \"rust.bindgen\" keyword argument \"include_directories\" of type array[str]." } ] } diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 79a3217..b29df77 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -1415,6 +1415,11 @@ class InternalTests(unittest.TestCase): since_values={list: '1.9'}), KwargInfo('new_dict', (ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str)), default={}, since_values={dict: '1.1'}), + KwargInfo('foo', (str, int, ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str), ContainerTypeInfo(list, int)), default={}, + 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'}), + KwargInfo('tuple', (ContainerTypeInfo(list, (str, int))), default=[], listify=True, + since_values={ContainerTypeInfo(list, str): '1.1', ContainerTypeInfo(list, int): '1.2'}), ) def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: pass @@ -1433,7 +1438,7 @@ class InternalTests(unittest.TestCase): with self.subTest('deprecated dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out: _(None, mock.Mock(subproject=''), [], {'output': {'foo2': 'a'}}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo2". dont use it.*""") + self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo2" in dict keys. dont use it.*""") with self.subTest('new dict string value'), mock.patch('sys.stdout', io.StringIO()) as out: _(None, mock.Mock(subproject=''), [], {'output': {'bar': 'b'}}) @@ -1441,7 +1446,40 @@ class InternalTests(unittest.TestCase): with self.subTest('new dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out: _(None, mock.Mock(subproject=''), [], {'output': {'bar2': 'a'}}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar2". use this.*""") + self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar2" in dict keys. use this.*""") + + with self.subTest('new string type'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'foo': 'foo'}) + self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "foo" of type str.*""") + + with self.subTest('new array of string type'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'foo': ['foo']}) + self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.2': "testfunc" keyword argument "foo" of type array\[str\].*""") + + with self.subTest('new dict of string type'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'foo': {'plop': 'foo'}}) + self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.3': "testfunc" keyword argument "foo" of type dict\[str\].*""") + + with self.subTest('deprecated int value'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'foo': 1}) + self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.8': "testfunc" keyword argument "foo" of type int.*""") + + with self.subTest('deprecated array int value'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'foo': [1]}) + self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "foo" of type array\[int\].*""") + + with self.subTest('new list[str] value'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'tuple': ['foo', 42]}) + self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "tuple" of type array\[str\].*""") + self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.2': "testfunc" keyword argument "tuple" of type array\[int\].*""") + + with self.subTest('deprecated array string value'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'input': 'foo'}) + self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""") + + with self.subTest('new array string value'), mock.patch('sys.stdout', io.StringIO()) as out: + _(None, mock.Mock(subproject=''), [], {'input': 'bar'}) + self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""") with self.subTest('non string union'), mock.patch('sys.stdout', io.StringIO()) as out: _(None, mock.Mock(subproject=''), [], {'install_dir': False}) |