Skip to content

Commit

Permalink
wip: options: Use a Range type for IntegerOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
dcbaker committed Sep 3, 2024
1 parent ac1ed46 commit 00ae5b5
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 45 deletions.
12 changes: 12 additions & 0 deletions mesonbuild/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,15 @@ def count(self, item: T) -> int: ...
def index(self, item: T) -> int: ...

def copy(self) -> typing.List[T]: ...


class Orderable(Protocol[T]):

"""A protocol for orderable types."""

def __eq__(self, other: typing.Any) -> bool: ...
def __ne__(self, other: typing.Any) -> bool: ...
def __le__(self, other: typing.Any) -> bool: ...
def __lt__(self, other: typing.Any) -> bool: ...
def __gt__(self, other: typing.Any) -> bool: ...
def __ge__(self, other: typing.Any) -> bool: ...
107 changes: 62 additions & 45 deletions mesonbuild/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
if T.TYPE_CHECKING:
from typing_extensions import Literal, TypeAlias, TypedDict

from ._typing import Orderable

DeprecatedType: TypeAlias = T.Union[bool, str, T.Dict[str, str], T.List[str]]

class ArgparseKWs(TypedDict, total=False):
Expand All @@ -44,6 +46,7 @@ class ArgparseKWs(TypedDict, total=False):

# Can't bind this near the class method it seems, sadly.
_T = T.TypeVar('_T')
_O = T.TypeVar('_O', bound='Orderable')

backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none']
genvslitelist = ['vs2022']
Expand Down Expand Up @@ -320,49 +323,55 @@ def validate_value(self, value: T.Any) -> bool:
raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).')


class _UserIntegerBase(UserOption[_T]):
@dataclasses.dataclass
class Range(T.Generic[_O]):

min_value: T.Optional[int]
max_value: T.Optional[int]
min: T.Optional[_O] = None
max: T.Optional[_O] = None

if T.TYPE_CHECKING:
def toint(self, v: str) -> int: ...
def __contains__(self, obj: _O) -> bool:
if self.min is not None and self.max is not None:
return self.min <= obj <= self.max
if self.min is not None:
return self.min <= obj
if self.max is not None:
return obj <= self.max
return True

def __post_init__(self, value_: _T) -> None:
super().__post_init__(value_)
def __str__(self) -> str:
choices: T.List[str] = []
if self.min_value is not None:
choices.append(f'>= {self.min_value!s}')
if self.max_value is not None:
choices.append(f'<= {self.max_value!s}')
self.__choices: str = ', '.join(choices)
if self.min is not None:
choices.append(f'>= {self.min!s}')
if self.max is not None:
choices.append(f'<= {self.max!s}')
return ', '.join(choices)

def printable_choices(self) -> T.Optional[T.List[str]]:
return [self.__choices]

def validate_value(self, value: T.Any) -> _T:
@dataclasses.dataclass
class UserIntegerOption(UserOption[int]):

min_value: dataclasses.InitVar[T.Optional[int]] = None
max_value: dataclasses.InitVar[T.Optional[int]] = None

def __post_init__(self, value_: int, min_value: T.Optional[int], max_value: T.Optional[int]) -> None:
self.range: Range[T.Optional[int]] = Range(min_value, max_value)
super().__post_init__(value_)

def validate_value(self, value: T.Any) -> int:
if isinstance(value, str):
value = T.cast('_T', self.toint(value))
try:
value = int(value)
except ValueError:
raise MesonException(f'Value string "{value}" for option "{self.name}" is not convertible to an integer.')
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 T.cast('_T', value)

if value not in self.range:
raise MesonException(f'Value {value} for option "{self.name}" is not within the range: {self.range!s}')

@dataclasses.dataclass
class UserIntegerOption(_UserIntegerBase[int]):
return value

min_value: T.Optional[int] = None
max_value: T.Optional[int] = None

def toint(self, valuestring: str) -> int:
try:
return int(valuestring)
except ValueError:
raise MesonException(f'Value string "{valuestring}" for option "{self.name}" is not convertible to an integer.')
def printable_choices(self) -> T.List[str] | None:
return [str(self.range)]


class OctalInt(int):
Expand All @@ -374,26 +383,34 @@ def __str__(self) -> str:


@dataclasses.dataclass
class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]]):
class UserUmaskOption(UserOption[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)
range: Range[OctalInt] = dataclasses.field(default_factory=lambda: Range(OctalInt(0), OctalInt(0o777)), init=False)

def printable_value(self) -> str:
if isinstance(self.value, int):
if isinstance(self.value, OctalInt):
return format(self.value, '04o')
return self.value

def validate_value(self, value: T.Any) -> T.Union[Literal['preserve'], OctalInt]:
if value == 'preserve':
return 'preserve'
return OctalInt(super().validate_value(value))
if isinstance(value, str):
try:
value = OctalInt(value, 8)
except ValueError as e:
raise MesonException(f'Invalid mode for option "{self.name}" {e}')
elif isinstance(value, int):
value = OctalInt(value)
if not isinstance(value, OctalInt):
raise MesonException(f'Value {value!r} for option "{self.name}" is not an integer.')
if value not in self.range:
raise MesonException(f'Value {value} for option "{self.name}" is not within the range: {self.range!s}')

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}')
return OctalInt(value)

def printable_choices(self) -> T.List[str] | None:
return [str(self.range)]


@dataclasses.dataclass
Expand Down Expand Up @@ -501,9 +518,9 @@ def choices_are_different(a: _U, b: _U) -> bool:
# If this isn't hte case the above failed somehow
assert isinstance(b, UserArrayOption), 'for mypy'
return a.choices != b.choices
elif isinstance(a, _UserIntegerBase):
assert isinstance(b, _UserIntegerBase), 'for mypy'
return a.max_value != b.max_value or a.min_value != b.min_value
elif isinstance(a, (UserIntegerOption, UserUmaskOption)):
assert isinstance(b, (UserIntegerOption, UserUmaskOption)), 'for mypy'
return a.range != b.range

return False

Expand Down

0 comments on commit 00ae5b5

Please sign in to comment.