diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 404b247cd9fc..b16c2f41e2f6 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -315,7 +315,7 @@ def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionD elif isinstance(opt, options.UserComboOption): optdict['choices'] = opt.printable_choices() typestr = 'combo' - elif isinstance(opt, options.UserIntegerOption): + elif isinstance(opt, (options.UserIntegerOption, options.UserUmaskOption)): typestr = 'integer' elif isinstance(opt, options.UserStringArrayOption): typestr = 'array' @@ -323,7 +323,7 @@ def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionD if c: optdict['choices'] = c else: - raise RuntimeError("Unknown option type") + raise RuntimeError("Unknown option type: ", type(opt)) optdict['type'] = typestr optdict['description'] = opt.description optlist.append(optdict) diff --git a/mesonbuild/options.py b/mesonbuild/options.py index ae923fec2f63..48c2569ab9b1 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -29,7 +29,7 @@ from . import mlog if T.TYPE_CHECKING: - from typing_extensions import TypeAlias, TypedDict + from typing_extensions import Literal, TypeAlias, TypedDict DeprecatedType: TypeAlias = T.Union[bool, str, T.Dict[str, str], T.List[str]] @@ -319,13 +319,16 @@ def validate_value(self, value: T.Any) -> bool: return False raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') -@dataclasses.dataclass -class UserIntegerOption(UserOption[int]): - min_value: T.Optional[int] = None - max_value: T.Optional[int] = None +class _UserIntegerBase(UserOption[_T]): + + min_value: T.Optional[int] + max_value: T.Optional[int] + + if T.TYPE_CHECKING: + def toint(self, v: str) -> int: ... - def __post_init__(self, value_: int) -> None: + def __post_init__(self, value_: _T) -> None: super().__post_init__(value_) choices: T.List[str] = [] if self.min_value is not None: @@ -337,16 +340,23 @@ def __post_init__(self, value_: int) -> None: def printable_choices(self) -> T.Optional[T.List[str]]: return [self.__choices] - def validate_value(self, value: T.Any) -> int: + def validate_value(self, value: T.Any) -> _T: if isinstance(value, str): - value = self.toint(value) + value = T.cast('_T', self.toint(value)) if not isinstance(value, int): raise MesonException(f'Value {value!r} for option "{self.name}" is not an integer.') if self.min_value is not None and value < self.min_value: raise MesonException(f'Value {value} for option "{self.name}" is less than minimum value {self.min_value}.') if self.max_value is not None and value > self.max_value: raise MesonException(f'Value {value} for option "{self.name}" is more than maximum value {self.max_value}.') - return value + return T.cast('_T', value) + + +@dataclasses.dataclass +class UserIntegerOption(_UserIntegerBase[int]): + + min_value: T.Optional[int] = None + max_value: T.Optional[int] = None def toint(self, valuestring: str) -> int: try: @@ -354,6 +364,7 @@ def toint(self, valuestring: str) -> int: except ValueError: raise MesonException(f'Value string "{valuestring}" for option "{self.name}" is not convertible to an integer.') + class OctalInt(int): # NinjaBackend.get_user_option_args uses str() to converts it to a command line option # UserUmaskOption.toint() uses int(str, 8) to convert it to an integer @@ -361,28 +372,30 @@ class OctalInt(int): def __str__(self) -> str: return oct(int(self)) + @dataclasses.dataclass -class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]): +class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]]): min_value: T.Optional[int] = dataclasses.field(default=0, init=False) max_value: T.Optional[int] = dataclasses.field(default=0o777, init=False) def printable_value(self) -> str: - if self.value == 'preserve': - return self.value - return format(self.value, '04o') + if isinstance(self.value, int): + return format(self.value, '04o') + return self.value - def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]: + def validate_value(self, value: T.Any) -> T.Union[Literal['preserve'], OctalInt]: if value == 'preserve': return 'preserve' return OctalInt(super().validate_value(value)) - def toint(self, valuestring: T.Union[str, OctalInt]) -> int: + def toint(self, valuestring: str) -> int: try: return int(valuestring, 8) except ValueError as e: raise MesonException(f'Invalid mode for option "{self.name}" {e}') + @dataclasses.dataclass class UserComboOption(EnumeratedUserOption[str]): @@ -580,7 +593,7 @@ def argparse_name_to_arg(name: str) -> str: return '--' + name.replace('_', '-') def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: - if self.opt_type in [UserComboOption, UserIntegerOption]: + if self.opt_type in {UserComboOption, UserIntegerOption, UserUmaskOption}: return self.default try: return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix] diff --git a/run_mypy.py b/run_mypy.py index f72e96b3d3a2..18b154835993 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -75,6 +75,7 @@ 'mesonbuild/msetup.py', 'mesonbuild/mtest.py', 'mesonbuild/optinterpreter.py', + 'mesonbuild/options.py', 'mesonbuild/programs.py', ] additional = [