aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2023-01-03 16:13:28 -0800
committerEli Schwartz <eschwartz93@gmail.com>2023-01-20 00:18:42 -0500
commitf5eaebb4b46a7e87e6c91a81da93ec28d4362546 (patch)
treed08b05086142552c5ebbb37aabd27a15c0ff2a87
parenteaf365cb3ef4f1c2ba66e07237d86a44089aff4f (diff)
downloadmeson-f5eaebb4b46a7e87e6c91a81da93ec28d4362546.zip
meson-f5eaebb4b46a7e87e6c91a81da93ec28d4362546.tar.gz
meson-f5eaebb4b46a7e87e6c91a81da93ec28d4362546.tar.bz2
use typed_kwargs for the various option subparsers
We make use of allow_unknown=True here, which allows us to only look at the common arguments in the main option parser, and then look at the specific options in the dispatched parsers. This allows us to do more specific checking on a per overload basis.
-rw-r--r--mesonbuild/optinterpreter.py195
-rw-r--r--test cases/failing/59 bad option argument/test.json2
2 files changed, 123 insertions, 74 deletions
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index 00f63e1..de7ba04 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
import re
import typing as T
@@ -19,11 +20,13 @@ from . import coredata
from . import mesonlib
from . import mparser
from . import mlog
-from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo, permittedKwargs
+from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo
+from .interpreter.type_checking import NoneType, in_set_validator
if T.TYPE_CHECKING:
from .interpreterbase import TYPE_var, TYPE_kwargs
from .interpreterbase import SubProject
- from typing_extensions import TypedDict
+ from typing_extensions import TypedDict, Literal
+
FuncOptionArgs = TypedDict('FuncOptionArgs', {
'type': str,
'description': str,
@@ -34,13 +37,29 @@ if T.TYPE_CHECKING:
'max': T.Optional[int],
'deprecated': T.Union[bool, str, T.Dict[str, str], T.List[str]],
})
- ParserArgs = TypedDict('ParserArgs', {
- 'yield': bool,
- 'choices': T.Optional[T.List[str]],
- 'value': object,
- 'min': T.Optional[int],
- 'max': T.Optional[int],
- })
+
+ class StringArgs(TypedDict):
+ value: str
+
+ class BooleanArgs(TypedDict):
+ value: bool
+
+ class ComboArgs(TypedDict):
+ value: str
+ choices: T.List[str]
+
+ class IntegerArgs(TypedDict):
+ value: int
+ min: T.Optional[int]
+ max: T.Optional[int]
+
+ class StringArrayArgs(TypedDict):
+ value: T.Optional[T.Union[str, T.List[str]]]
+ choices: T.List[str]
+
+ class FeatureArgs(TypedDict):
+ value: Literal['enabled', 'disabled', 'auto']
+ choices: T.List[str]
class OptionException(mesonlib.MesonException):
@@ -54,13 +73,14 @@ class OptionInterpreter:
def __init__(self, subproject: 'SubProject') -> None:
self.options: 'coredata.MutableKeyedOptionDictType' = {}
self.subproject = subproject
- self.option_types = {'string': self.string_parser,
- 'boolean': self.boolean_parser,
- 'combo': self.combo_parser,
- 'integer': self.integer_parser,
- 'array': self.string_array_parser,
- 'feature': self.feature_parser,
- }
+ self.option_types: T.Dict[str, T.Callable[..., coredata.UserOption]] = {
+ 'string': self.string_parser,
+ 'boolean': self.boolean_parser,
+ 'combo': self.combo_parser,
+ 'integer': self.integer_parser,
+ 'array': self.string_array_parser,
+ 'feature': self.feature_parser,
+ }
def process(self, option_file: str) -> None:
try:
@@ -145,17 +165,25 @@ class OptionInterpreter:
(posargs, kwargs) = self.reduce_arguments(node.args)
self.func_option(posargs, kwargs)
- @typed_kwargs('option',
- KwargInfo('type', str, required=True),
- KwargInfo('description', str, default=''),
- KwargInfo('yield', bool, default=coredata.DEFAULT_YIELDING, since='0.45.0'),
- KwargInfo('choices', (ContainerTypeInfo(list, str), type(None))),
- KwargInfo('value', object),
- KwargInfo('min', (int, type(None))),
- KwargInfo('max', (int, type(None))),
- KwargInfo('deprecated', (bool, str, ContainerTypeInfo(dict, str), ContainerTypeInfo(list, str)),
- default=False, since='0.60.0')
- )
+ @typed_kwargs(
+ 'option',
+ KwargInfo(
+ 'type',
+ str,
+ required=True,
+ validator=in_set_validator({'string', 'boolean', 'integer', 'combo', 'array', 'feature'})
+ ),
+ KwargInfo('description', str, default=''),
+ KwargInfo(
+ 'deprecated',
+ (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 []
+ ),
+ KwargInfo('yield', bool, default=coredata.DEFAULT_YIELDING, since='0.45.0'),
+ allow_unknown=True,
+ )
@typed_pos_args('option', str)
def func_option(self, args: T.Tuple[str], kwargs: 'FuncOptionArgs') -> None:
opt_name = args[0]
@@ -166,60 +194,81 @@ class OptionInterpreter:
raise OptionException('Option name %s is reserved.' % opt_name)
opt_type = kwargs['type']
- parser = self.option_types.get(opt_type)
- if not parser:
- raise OptionException(f'Unknown type {opt_type}.')
+ parser = self.option_types[opt_type]
description = kwargs['description'] or opt_name
- # Only keep in kwargs arguments that are used by option type's parser
- # because they use @permittedKwargs().
- known_parser_kwargs = {'value', 'choices', 'yield', 'min', 'max'}
- parser_kwargs = {k: v for k, v in kwargs.items() if k in known_parser_kwargs and v is not None}
- opt = parser(description, T.cast('ParserArgs', parser_kwargs))
+ # Drop the arguments we've already consumed
+ n_kwargs = {k: v for k, v in kwargs.items()
+ if k not in {'type', 'description', 'deprecated', 'yield'}}
+
+ opt = parser(description, kwargs['yield'], n_kwargs)
opt.deprecated = kwargs['deprecated']
- if isinstance(opt.deprecated, str):
- FeatureNew.single_use('String value to "deprecated" keyword argument', '0.63.0', self.subproject)
if key in self.options:
mlog.deprecation(f'Option {opt_name} already exists.')
self.options[key] = opt
- @permittedKwargs({'value', 'yield'})
- def string_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value', '')
- return coredata.UserStringOption(description, value, kwargs['yield'])
-
- @permittedKwargs({'value', 'yield'})
- def boolean_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value', True)
- return coredata.UserBooleanOption(description, value, kwargs['yield'])
-
- @permittedKwargs({'value', 'yield', 'choices'})
- def combo_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- choices = kwargs.get('choices')
- if not choices:
- raise OptionException('Combo option missing "choices" keyword.')
- value = kwargs.get('value', choices[0])
- return coredata.UserComboOption(description, choices, value, kwargs['yield'])
-
- @permittedKwargs({'value', 'min', 'max', 'yield'})
- def integer_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value')
+ @typed_kwargs(
+ 'string option',
+ KwargInfo('value', str, default=''),
+ )
+ def string_parser(self, description: str, yield_: bool, kwargs: StringArgs) -> coredata.UserOption:
+ return coredata.UserStringOption(description, kwargs['value'], yield_)
+
+ @typed_kwargs(
+ 'boolean option',
+ KwargInfo(
+ 'value',
+ (bool, str),
+ default=True,
+ validator=lambda x: None if isinstance(x, bool) or x in {'true', 'false'} else 'boolean options must have boolean values',
+ ),
+ )
+ def boolean_parser(self, description: str, yield_: bool, kwargs: BooleanArgs) -> coredata.UserOption:
+ return coredata.UserBooleanOption(description, kwargs['value'], yield_)
+
+ @typed_kwargs(
+ 'combo option',
+ KwargInfo('value', (str, NoneType)),
+ KwargInfo('choices', ContainerTypeInfo(list, str, allow_empty=False), required=True),
+ )
+ def combo_parser(self, description: str, kwargs: ComboArgs, yield_: bool) -> coredata.UserOption:
+ choices = kwargs['choices']
+ value = kwargs['value']
if value is None:
- raise OptionException('Integer option must contain value argument.')
- inttuple = (kwargs.get('min'), kwargs.get('max'), value)
- return coredata.UserIntegerOption(description, inttuple, kwargs['yield'])
-
- @permittedKwargs({'value', 'yield', 'choices'})
- def string_array_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- choices = kwargs.get('choices', [])
- value = kwargs.get('value', choices)
- if not isinstance(value, list):
- raise OptionException('Array choices must be passed as an array.')
+ value = kwargs['choices'][0]
+ return coredata.UserComboOption(description, choices, value, yield_)
+
+ @typed_kwargs(
+ 'integer option',
+ KwargInfo(
+ 'value',
+ (int, str),
+ default=True,
+ convertor=int,
+ ),
+ KwargInfo('min', (int, NoneType)),
+ KwargInfo('max', (int, NoneType)),
+ )
+ def integer_parser(self, description: str, yield_: bool, kwargs: IntegerArgs) -> coredata.UserOption:
+ value = kwargs['value']
+ inttuple = (kwargs['min'], kwargs['max'], value)
+ return coredata.UserIntegerOption(description, inttuple, yield_)
+
+ @typed_kwargs(
+ 'string array option',
+ KwargInfo('value', (ContainerTypeInfo(list, str), str, NoneType)),
+ KwargInfo('choices', ContainerTypeInfo(list, str), default=[]),
+ )
+ def string_array_parser(self, description: str, yield_: bool, kwargs: StringArrayArgs) -> coredata.UserOption:
+ choices = kwargs['choices']
+ value = kwargs['value'] if kwargs['value'] is not None else choices
return coredata.UserArrayOption(description, value,
choices=choices,
- yielding=kwargs['yield'])
+ yielding=yield_)
- @permittedKwargs({'value', 'yield'})
- def feature_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value', 'auto')
- return coredata.UserFeatureOption(description, value, kwargs['yield'])
+ @typed_kwargs(
+ 'feature option',
+ KwargInfo('value', str, default='auto', validator=in_set_validator({'auto', 'enabled', 'disabled'})),
+ )
+ def feature_parser(self, description: str, yield_: bool, kwargs: FeatureArgs) -> coredata.UserOption:
+ return coredata.UserFeatureOption(description, kwargs['value'], yield_)
diff --git a/test cases/failing/59 bad option argument/test.json b/test cases/failing/59 bad option argument/test.json
index 3c5df1b..28aa0ca 100644
--- a/test cases/failing/59 bad option argument/test.json
+++ b/test cases/failing/59 bad option argument/test.json
@@ -1,7 +1,7 @@
{
"stdout": [
{
- "line": "test cases/failing/59 bad option argument/meson_options.txt:1:0: ERROR: option got unknown keyword arguments \"vaule\""
+ "line": "test cases/failing/59 bad option argument/meson_options.txt:1:0: ERROR: string option got unknown keyword arguments \"vaule\""
}
]
}