diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 9eee8ca..28b4057 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -35,9 +35,9 @@ Members, NewProtocol, Param, + Slice, SpecialFormEllipsis, StrConcat, - StrSlice, Uppercase, ) @@ -240,12 +240,12 @@ def test_type_strings_4(): def test_type_strings_5(): - d = eval_typing(StrSlice[Literal["abcd"], Literal[0], Literal[1]]) + d = eval_typing(Slice[Literal["abcd"], Literal[0], Literal[1]]) assert d == Literal["a"] def test_type_strings_6(): - d = eval_typing(StrSlice[Literal["abcd"], Literal[1], Literal[None]]) + d = eval_typing(Slice[Literal["abcd"], Literal[1], Literal[None]]) assert d == Literal["bcd"] @@ -1043,6 +1043,43 @@ def test_eval_length_01(): assert d == Literal[None] +def test_eval_slice_01(): + t = tuple[Literal[0], Literal[1], Literal[2], Literal[3], Literal[4]] + d = eval_typing(Slice[t, Literal[1], Literal[3]]) + assert d == tuple[Literal[1], Literal[2]] + d = eval_typing(Slice[t, Literal[1], Literal[None]]) + assert d == tuple[Literal[1], Literal[2], Literal[3], Literal[4]] + d = eval_typing(Slice[t, Literal[None], Literal[3]]) + assert d == tuple[Literal[0], Literal[1], Literal[2]] + d = eval_typing(Slice[t, Literal[None], Literal[None]]) + assert ( + d == tuple[Literal[0], Literal[1], Literal[2], Literal[3], Literal[4]] + ) + d = eval_typing(Slice[t, Literal[1], Literal[1]]) + assert d == tuple[()] + + +def test_eval_slice_02(): + t = Literal["abcde"] + d = eval_typing(Slice[t, Literal[1], Literal[3]]) + assert d == Literal["bc"] + d = eval_typing(Slice[t, Literal[1], Literal[None]]) + assert d == Literal["bcde"] + d = eval_typing(Slice[t, Literal[None], Literal[3]]) + assert d == Literal["abc"] + d = eval_typing(Slice[t, Literal[None], Literal[None]]) + assert d == Literal["abcde"] + d = eval_typing(Slice[t, Literal[1], Literal[1]]) + assert d == Literal[""] + + +def test_eval_slice_03(): + d = eval_typing(Slice[int, Literal[1], Literal[2]]) + assert d == Never + d = eval_typing(Slice[dict[int, str], Literal[1], Literal[2]]) + assert d == Never + + def test_eval_literal_idempotent_01(): t = Literal[int] for _ in range(5): diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 49a60ab..4b642b4 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -35,9 +35,9 @@ Members, NewProtocol, Param, + Slice, SpecialFormEllipsis, StrConcat, - StrSlice, Uncapitalize, Uppercase, ) @@ -896,6 +896,24 @@ def _eval_Length(tp, *, ctx) -> typing.Any: raise TypeError(f"Invalid type argument to Length: {tp} is not a tuple") +@type_eval.register_evaluator(Slice) +@_lift_over_unions +def _eval_Slice(tp, start, end, *, ctx): + tp = _eval_types(tp, ctx) + start = _eval_literal(start, ctx) + end = _eval_literal(end, ctx) + if _typing_inspect.is_generic_alias(tp) and tp.__origin__ is tuple: + return tp.__origin__[tp.__args__[start:end]] + elif ( + _typing_inspect.is_generic_alias(tp) + and tp.__origin__ is typing.Literal + and isinstance(tp.__args__[0], str) + ): + return tp.__origin__[tp.__args__[0][start:end]] + else: + return typing.Never + + # String literals @@ -912,7 +930,6 @@ def func(*args, ctx): _string_literal_op(Capitalize, op=str.capitalize) _string_literal_op(Uncapitalize, op=lambda s: s[0:1].lower() + s[1:]) _string_literal_op(StrConcat, op=lambda s, t: s + t) -_string_literal_op(StrSlice, op=lambda s, start, end: s[start:end]) ################################################################## diff --git a/typemap/typing.py b/typemap/typing.py index 021483d..1c5d283 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -147,6 +147,10 @@ class Length[S: tuple]: pass +class Slice[S: str | tuple, Start: int | None, End: int | None]: + pass + + class Uppercase[S: str]: pass @@ -167,10 +171,6 @@ class StrConcat[S: str, T: str]: pass -class StrSlice[S: str, Start: int | None, End: int | None]: - pass - - class NewProtocol[*T]: pass