Skip to content

Commit 61da06b

Browse files
committed
Add COLS, MAPS and MUT_MAPS to .pyih to be DRY and not lazy
1 parent 85149fa commit 61da06b

6 files changed

Lines changed: 530 additions & 123 deletions

File tree

funcy/colls.pyi

Lines changed: 393 additions & 41 deletions
Large diffs are not rendered by default.

funcy/colls.pyih

Lines changed: 30 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ _T = TypeVar('_T')
2020
# Non-callable types accepted by the extended function protocol (funcmakers.make_func):
2121
# int/slice for itemgetter, str/bytes/re.Pattern for regex,
2222
# Mapping for lookup, Set for membership, None for identity/bool
23-
# FIX: should include Callable into _Func, it's unied anyway all the time
23+
# FIX: should include Callable into _Func, it's unied anyway all the time, also name it _XFunc
2424
_Func = int | slice | str | bytes | re.Pattern[str] | Mapping[Any, Any] | AbstractSet[Any] | None
2525
_ReResult: TypeAlias = str | tuple[str, ...] | dict[str, str]
2626

@@ -43,75 +43,48 @@ def merge_with(f: Callable[[list[Any]], _V], *dicts: Mapping[_K, Any]) -> dict[_
4343

4444
### Walk / Select
4545

46-
# 1-param collections × XFunc (quadratic expansion)
47-
# FIX: add abstract collections like MutableMapping, Mapping, Sequence, Set, Iterator, Iterable
48-
# drop catch all stuff
49-
# RES: Sequence, Set, Iterator, Iterable not added - constructors not guaranteed to accept iterables,
50-
# and Set/frozenset already covered by concrete expansion above
51-
# FIX: the above RES is untrue, walk will work with all of the above
52-
def walk[C: (list, set, frozenset)](f: XFunc[[_T], _V], coll: C[_T]) -> C[_V]: ...
5346
# dict/MutableMapping/Mapping: typed Callable with pair→pair signature
54-
# FIX: this also works with collections of pairs, add a test and fix it here
55-
# also might need to reorder. Relying on last catch all is not the best solution!
56-
# We loose type checking of arguments, i.e. that element types match callable
57-
def walk(f: Callable[[tuple[_K, _V]], tuple[_K2, _V2]], coll: dict[_K, _V]) -> dict[_K2, _V2]: ...
58-
def walk(f: Callable[[tuple[_K, _V]], tuple[_K2, _V2]], coll: MutableMapping[_K, _V]) -> dict[_K2, _V2]: ...
59-
def walk(f: Callable[[tuple[_K, _V]], tuple[_K2, _V2]], coll: Mapping[_K, _V]) -> dict[_K2, _V2]: ...
47+
# (must come before COLLS — dict is Iterable[K], so Iterable overload would shadow)
48+
def walk[C: MAPS](f: Callable[[tuple[_K, _V]], tuple[_K2, _V2]], coll: C[_K, _V]) -> C[_K2, _V2]: ...
6049
# catch-alls for _Func and other collection types
61-
def walk(f: Callable[..., Any] | _Func, coll: dict[Any, Any]) -> dict[Any, Any]: ...
50+
def walk[C: MAPS](f: Callable[..., Any] | _Func, coll: C[Any, Any]) -> C[Any, Any]: ...
51+
# 1-param collections × XFunc (quadratic expansion)
52+
def walk[C: COLLS](f: XFunc[[_T], _V], coll: C[_T]) -> C[_V]: ...
53+
# catch-all for other collection types
6254
def walk(f: Callable[..., Any] | _Func, coll: _T) -> _T: ...
6355

6456
# walk_keys: collection of pairs × XFunc
6557
# xfunc_skip: slice
66-
def walk_keys[C: (list, set, frozenset)](f: XFunc[[_K], _K2], coll: C[tuple[_K, _V]]) -> C[tuple[_K2, _V]]: ...
67-
# walk_keys: dict/MutableMapping/Mapping always returns dict
68-
# xfunc_skip: slice
69-
def walk_keys(f: XFunc[[_K], _K2], coll: dict[_K, _V]) -> dict[_K2, _V]: ...
58+
def walk_keys[C: COLLS](f: XFunc[[_K], _K2], coll: C[tuple[_K, _V]]) -> C[tuple[_K2, _V]]: ...
7059
# xfunc_skip: slice
71-
def walk_keys(f: XFunc[[_K], _K2], coll: MutableMapping[_K, _V]) -> dict[_K2, _V]: ...
72-
# xfunc_skip: slice
73-
def walk_keys(f: XFunc[[_K], _K2], coll: Mapping[_K, _V]) -> dict[_K2, _V]: ...
60+
def walk_keys[C: MAPS](f: XFunc[[_K], _K2], coll: C[_K, _V]) -> C[_K2, _V]: ...
7461

75-
# walk_values: collection of pairs × XFunc
76-
def walk_values[C: (list, set, frozenset)](f: XFunc[[_V], _V2], coll: C[tuple[_K, _V]]) -> C[tuple[_K, _V2]]: ...
77-
# walk_values: dict/MutableMapping/Mapping always returns dict
78-
def walk_values(f: XFunc[[_V], _V2], coll: dict[_K, _V]) -> dict[_K, _V2]: ...
79-
def walk_values(f: XFunc[[_V], _V2], coll: MutableMapping[_K, _V]) -> dict[_K, _V2]: ...
80-
def walk_values(f: XFunc[[_V], _V2], coll: Mapping[_K, _V]) -> dict[_K, _V2]: ...
62+
def walk_values[C: COLLS](f: XFunc[[_V], _V2], coll: C[tuple[_K, _V]]) -> C[tuple[_K, _V2]]: ...
63+
def walk_values[C: MAPS](f: XFunc[[_V], _V2], coll: C[_K, _V]) -> C[_K, _V2]: ...
8164

82-
# 1-param collections × XPred
83-
def select[C: (list, set, frozenset)](pred: XPred[_T], coll: C[_T]) -> C[_T]: ...
8465
# dict/Mapping: pred receives (key, value) pairs, only Callable is meaningful
66+
# (must come before COLLS — dict is Iterable[K], so Iterable overload would shadow these)
8567
# FIX: should use coll parametrization here
86-
def select(pred: Callable[[tuple[_K, _V]], Any], coll: dict[_K, _V]) -> dict[_K, _V]: ...
87-
def select(pred: Callable[[tuple[_K, _V]], Any], coll: MutableMapping[_K, _V]) -> MutableMapping[_K, _V]: ...
88-
def select(pred: Callable[[tuple[_K, _V]], Any], coll: Mapping[_K, _V]) -> Mapping[_K, _V]: ...
68+
def select[C: MAPS](pred: Callable[[tuple[_K, _V]], Any], coll: C[_K, _V]) -> C[_K, _V]: ...
69+
# 1-param collections × XPred
70+
def select[C: COLLS](pred: XPred[_T], coll: C[_T]) -> C[_T]: ...
8971
# catch-all for other collection types
9072
def select(pred: Callable[..., Any] | _Func, coll: _T) -> _T: ...
9173
# select_keys: collection of pairs × XPred
92-
def select_keys[C: (list, set, frozenset)](pred: XPred[_K], coll: C[tuple[_K, _V]]) -> C[tuple[_K, _V]]: ...
74+
def select_keys[C: COLLS](pred: XPred[_K], coll: C[tuple[_K, _V]]) -> C[tuple[_K, _V]]: ...
9375
# select_keys: dict/MutableMapping/Mapping
94-
def select_keys[C: (dict, MutableMapping, Mapping)](pred: XPred[_K], coll: C[_K, _V]) -> C[_K, _V]: ...
95-
96-
# FIX0: looks like we are getting lazy everywhere, i.e. here it should be not (list, set, frozenset)
97-
# but (list, tuple[T, ...], set, frozenset, Sequence, Iterator, Iterable)
98-
# and this is repeating patterns, so let's come up with a systemic decision, we'll add aliases
99-
# like ALL_SEQUENCES, ALL_MAPPINGS, ALL_CONTAINERS - these should be global find a good place
100-
# for those maybe a separate pyih file like global.pyih or defines.pyih. Then use those aliases
101-
# instead of literal listing collection types. Update our pyih translator to support this.
102-
#
103-
# Maybe move other global stuff there too. Could be imports?
76+
def select_keys[C: MAPS](pred: XPred[_K], coll: C[_K, _V]) -> C[_K, _V]: ...
77+
10478
# select_values: collection of pairs × XPred
105-
def select_values[C: (list, set, frozenset)](pred: XPred[_V], coll: C[tuple[_K, _V]]) -> C[tuple[_K, _V]]: ...
79+
def select_values[C: COLLS](pred: XPred[_V], coll: C[tuple[_K, _V]]) -> C[tuple[_K, _V]]: ...
10680
# select_values: dict/MutableMapping/Mapping
107-
def select_values[C: (dict, MutableMapping, Mapping)](pred: XPred[_V], coll: C[_K, _V]) -> C[_K, _V]: ...
81+
def select_values[C: MAPS](pred: XPred[_V], coll: C[_K, _V]) -> C[_K, _V]: ...
10882

10983
def split_keys(pred: XPred[_K], coll: Mapping[_K, _V]) -> tuple[dict[_K, _V], dict[_K, _V]]: ...
11084

111-
def compact[C: (list, set, frozenset)](coll: C[_T]) -> C[_T]: ...
112-
def compact(coll: dict[_K, _V]) -> dict[_K, _V]: ...
113-
def compact(coll: MutableMapping[_K, _V]) -> MutableMapping[_K, _V]: ...
114-
def compact(coll: Mapping[_K, _V]) -> Mapping[_K, _V]: ...
85+
# (dict/Mapping must come before COLLS — dict is Iterable[K], so Iterable overload would shadow)
86+
def compact[C: MAPS](coll: C[_K, _V]) -> C[_K, _V]: ...
87+
def compact[C: COLLS](coll: C[_T]) -> C[_T]: ...
11588
def compact(coll: _T) -> _T: ...
11689

11790
### Content tests
@@ -137,10 +110,10 @@ def some(pred: XPred[_T], seq: Iterable[_T]) -> _T | None: ...
137110
### Dict utilities
138111

139112
def zipdict(keys: Iterable[_K], vals: Iterable[_V]) -> dict[_K, _V]: ...
140-
def flip[C: (dict, MutableMapping, Mapping)](mapping: C[_K, _V]) -> C[_V, _K]: ...
141-
def flip[C: (list, set, frozenset)](coll: C[tuple[_K, _V]]) -> C[tuple[_V, _K]]: ...
142-
def project[C: (dict, MutableMapping, Mapping)](mapping: C[_K, _V], keys: Iterable[_K]) -> C[_K, _V]: ...
143-
def omit[C: (dict, MutableMapping, Mapping)](mapping: C[_K, _V], keys: Iterable[_K]) -> C[_K, _V]: ...
113+
def flip[C: MAPS](mapping: C[_K, _V]) -> C[_V, _K]: ...
114+
def flip[C: COLLS](coll: C[tuple[_K, _V]]) -> C[tuple[_V, _K]]: ...
115+
def project[C: MAPS](mapping: C[_K, _V], keys: Iterable[_K]) -> C[_K, _V]: ...
116+
def omit[C: MAPS](mapping: C[_K, _V], keys: Iterable[_K]) -> C[_K, _V]: ...
144117

145118
def zip_values(*dicts: Mapping[_K, _V]) -> Iterator[tuple[_V, ...]]: ...
146119
def zip_dicts(*dicts: Mapping[_K, _V]) -> Iterator[tuple[_K, tuple[_V, ...]]]: ...
@@ -149,11 +122,11 @@ def zip_dicts(*dicts: Mapping[_K, _V]) -> Iterator[tuple[_K, tuple[_V, ...]]]: .
149122

150123
def get_in(coll: Mapping[Any, Any] | Sequence[Any], path: Iterable[Any], default: Any = ...) -> Any: ...
151124
def get_lax(coll: Mapping[Any, Any] | Sequence[Any], path: Iterable[Any], default: Any = ...) -> Any: ...
152-
def set_in[C: (dict, MutableMapping)](coll: C[_K, _V], path: Sequence[Any], value: Any) -> C[_K, _V]: ...
125+
def set_in[C: MUT_MAPS](coll: C[_K, _V], path: Sequence[Any], value: Any) -> C[_K, _V]: ...
153126
def set_in(coll: _T, path: Sequence[Any], value: Any) -> _T: ...
154-
def update_in[C: (dict, MutableMapping)](coll: C[_K, _V], path: Sequence[Any], update: Callable[[Any], Any], default: Any = ...) -> C[_K, _V]: ...
127+
def update_in[C: MUT_MAPS](coll: C[_K, _V], path: Sequence[Any], update: Callable[[Any], Any], default: Any = ...) -> C[_K, _V]: ...
155128
def update_in(coll: _T, path: Sequence[Any], update: Callable[[Any], Any], default: Any = ...) -> _T: ...
156-
def del_in[C: (dict, MutableMapping)](coll: C[_K, _V], path: Sequence[Any]) -> C[_K, _V]: ...
129+
def del_in[C: MUT_MAPS](coll: C[_K, _V], path: Sequence[Any]) -> C[_K, _V]: ...
157130
def del_in(coll: _T, path: Sequence[Any]) -> _T: ...
158131
def has_path(coll: Mapping[Any, Any] | Sequence[Any], path: Iterable[Any]) -> bool: ...
159132

mypy.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[mypy]
2+
# Intentional: more-specific overloads (e.g. dict before Iterable) overlap by design
3+
disable_error_code = overload-overlap

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ basepython = python3.13
3838
deps =
3939
mypy
4040
commands =
41-
python -m mypy.stubtest funcy.types funcy.strings funcy.tree funcy.colls funcy.seqs funcy.funcs funcy.funcolls funcy.flow funcy.calc funcy.debug funcy.objects funcy.decorators --allowlist stubtest_allowlist.txt
41+
python -m mypy.stubtest funcy.types funcy.strings funcy.tree funcy.colls funcy.seqs funcy.funcs funcy.funcolls funcy.flow funcy.calc funcy.debug funcy.objects funcy.decorators --allowlist stubtest_allowlist.txt --mypy-config-file {toxinidir}/mypy.ini
4242

4343
[testenv:lint]
4444
basepython = python3.10

translate_pyih.py

100644100755
Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424

2525
FUNCY_DIR = Path(__file__).parent / "funcy"
2626

27+
# Collection type aliases for use in .pyih files as [C: ALIAS_NAME]
28+
COLL_ALIASES = {
29+
'COLLS': ['list', 'tuple', 'set', 'frozenset', 'Sequence', 'Iterator', 'Iterable'],
30+
'MAPS': ['dict', 'MutableMapping', 'Mapping'],
31+
'MUT_MAPS': ['dict', 'MutableMapping'],
32+
}
33+
2734
# Header for generated files
2835
GENERATED_HEADER = "# THIS FILE IS AUTOGENERATED by translate_pyih.py from {source}. DO NOT EDIT.\n"
2936

@@ -150,10 +157,15 @@ def parse_func_node(node: ast.FunctionDef, xfunc_skip: set) -> dict:
150157
coll_var = None
151158
coll_types = None
152159
for tp in node.type_params:
153-
if isinstance(tp, ast.TypeVar) and tp.bound is not None and isinstance(tp.bound, ast.Tuple):
154-
coll_var = tp.name
155-
coll_types = [annotation_to_str(e) for e in tp.bound.elts]
156-
break
160+
if isinstance(tp, ast.TypeVar) and tp.bound is not None:
161+
if isinstance(tp.bound, ast.Tuple):
162+
coll_var = tp.name
163+
coll_types = [annotation_to_str(e) for e in tp.bound.elts]
164+
break
165+
elif isinstance(tp.bound, ast.Name) and tp.bound.id in COLL_ALIASES:
166+
coll_var = tp.name
167+
coll_types = COLL_ALIASES[tp.bound.id]
168+
break
157169

158170
# Build params list, including defaults as part of the type string ("type = default")
159171
args = node.args
@@ -495,10 +507,44 @@ def expand_collection_types(overload: dict) -> list[dict]:
495507

496508

497509
def substitute_coll_type(type_str: str, coll_var: str, ctype: str) -> str:
498-
"""Replace C[X] or C[X, Y] with concrete_type[X] or concrete_type[X, Y]."""
510+
"""Replace C[X] or C[X, Y] with concrete_type[X] or concrete_type[X, Y].
511+
512+
For tuple, C[X] becomes tuple[X, ...] (variable-length homogeneous tuple).
513+
"""
514+
if ctype == 'tuple':
515+
# Replace C[...] with tuple[..., ...] using bracket-aware matching
516+
result = _substitute_tuple_type(type_str, coll_var)
517+
return result
499518
return re.sub(r'\b' + re.escape(coll_var) + r'\b', ctype, type_str)
500519

501520

521+
def _substitute_tuple_type(type_str: str, coll_var: str) -> str:
522+
"""Replace C[X] with tuple[X, ...] for variable-length tuple semantics."""
523+
# FIX: this looks way too complicated, but may go away if we go away from using strings
524+
# Find C[ and then match brackets to find the closing ]
525+
pattern = re.compile(r'\b' + re.escape(coll_var) + r'\[')
526+
result = []
527+
pos = 0
528+
for m in pattern.finditer(type_str):
529+
result.append(type_str[pos:m.start()])
530+
# Find matching closing bracket
531+
bracket_start = m.end() - 1 # position of '['
532+
depth = 1
533+
i = bracket_start + 1
534+
while i < len(type_str) and depth > 0:
535+
if type_str[i] == '[':
536+
depth += 1
537+
elif type_str[i] == ']':
538+
depth -= 1
539+
i += 1
540+
# Extract inner content (between [ and ])
541+
inner = type_str[bracket_start + 1:i - 1]
542+
result.append(f'tuple[{inner}, ...]')
543+
pos = i
544+
result.append(type_str[pos:])
545+
return ''.join(result)
546+
547+
502548
# ---------------------------------------------------------------------------
503549
# Output Generation
504550
# ---------------------------------------------------------------------------

type_tests/test_colls.py

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
from typing import Any, assert_type
22
from collections.abc import Iterable, Iterator, Mapping, MutableMapping, Sequence
33

4+
from funcy import (
5+
empty, iteritems, itervalues,
6+
join, merge, join_with, merge_with,
7+
walk, walk_keys, walk_values, select, select_keys, select_values,
8+
split_keys, compact,
9+
is_distinct, zipdict, flip, project, omit, zip_values, zip_dicts,
10+
where, pluck, pluck_attr, invoke,
11+
lwhere, lpluck, lpluck_attr, linvoke,
12+
get_in, get_lax, set_in, update_in, del_in, has_path,
13+
)
14+
from funcy.colls import all, any, none, one, some # shadow builtins
15+
416
# Real abstract-type implementations (not concrete types cast to abstract — checkers see through that)
517
class StrIntMapping(Mapping[str, int]):
618
def __getitem__(self, k: str) -> int: return 0
@@ -13,17 +25,11 @@ def __setitem__(self, k: str, v: int) -> None: pass
1325
def __delitem__(self, k: str) -> None: pass
1426
def __iter__(self) -> Iterator[str]: return iter([])
1527
def __len__(self) -> int: return 0
16-
from funcy import (
17-
empty, iteritems, itervalues,
18-
join, merge, join_with, merge_with,
19-
walk, walk_keys, walk_values, select, select_keys, select_values,
20-
split_keys, compact,
21-
is_distinct, zipdict, flip, project, omit, zip_values, zip_dicts,
22-
where, pluck, pluck_attr, invoke,
23-
lwhere, lpluck, lpluck_attr, linvoke,
24-
get_in, get_lax, set_in, update_in, del_in, has_path,
25-
)
26-
from funcy.colls import all, any, none, one, some # shadow builtins
28+
29+
class IntSequence(Sequence[int]):
30+
def __getitem__(self, index: int) -> int: return 0 # type: ignore[override]
31+
def __len__(self) -> int: return 0
32+
2733

2834
_ReResult = str | tuple[str, ...] | dict[str, str]
2935

@@ -122,10 +128,10 @@ def swap_pair(pair: tuple[str, int]) -> tuple[int, str]: return (pair[1], str(pa
122128
real_mapping = StrIntMapping()
123129
real_mutable_mapping = StrIntMutableMapping()
124130
# walk: Mapping with typed pair function returns dict
125-
reveal_type(walk(swap_pair, real_mapping)) # R: dict[int, str]
131+
reveal_type(walk(swap_pair, real_mapping)) # R: Mapping[int, str]
126132
# walk: MutableMapping with typed pair function returns dict
127-
reveal_type(walk(swap_pair, StrIntMutableMapping())) # R: dict[int, str]
128-
reveal_type(walk_keys(str_key_to_int, real_mapping)) # R: dict[int, int]
133+
reveal_type(walk(swap_pair, StrIntMutableMapping())) # R: MutableMapping[int, str]
134+
reveal_type(walk_keys(str_key_to_int, real_mapping)) # R: Mapping[int, int]
129135
# walk: collection of pairs (list[tuple[K, V]]) — handled by list XFunc overload
130136
str_int_pairs: list[tuple[str, int]] = [("a", 1), ("b", 2)]
131137
def transform_pair(p: tuple[str, int]) -> tuple[int, str]: return (p[1], str(p[0]))
@@ -134,8 +140,8 @@ def transform_pair(p: tuple[str, int]) -> tuple[int, str]: return (p[1], str(p[0
134140
reveal_type(walk(str, str_int_pairs)) # R: list[str] # XFAIL[ty]: TypeVar inference gives list[tuple[str, int]]
135141

136142
# -- walk_keys: MutableMapping returns dict --
137-
reveal_type(walk_keys(str_key_to_int, real_mutable_mapping)) # R: dict[int, int]
138-
reveal_type(walk_keys(None, real_mutable_mapping)) # R: dict[str, int]
143+
reveal_type(walk_keys(str_key_to_int, real_mutable_mapping)) # R: MutableMapping[int, int]
144+
reveal_type(walk_keys(None, real_mutable_mapping)) # R: MutableMapping[str, int]
139145
# -- walk_keys: collection of pairs preserves collection type --
140146
reveal_type(walk_keys(str_key_to_int, str_int_pairs)) # R: list[tuple[int, int]]
141147
reveal_type(walk_keys(str.upper, str_int_pairs)) # R: list[tuple[str, int]]
@@ -149,10 +155,10 @@ def transform_pair(p: tuple[str, int]) -> tuple[int, str]: return (p[1], str(p[0
149155

150156
# -- walk_values: always returns dict --
151157
reveal_type(walk_values(int_to_str, si_dict)) # R: dict[str, str]
152-
reveal_type(walk_values(int_to_str, real_mapping)) # R: dict[str, str]
158+
reveal_type(walk_values(int_to_str, real_mapping)) # R: Mapping[str, str]
153159
# -- walk_values: MutableMapping returns dict --
154-
reveal_type(walk_values(int_to_str, real_mutable_mapping)) # R: dict[str, str]
155-
reveal_type(walk_values(None, real_mutable_mapping)) # R: dict[str, int]
160+
reveal_type(walk_values(int_to_str, real_mutable_mapping)) # R: MutableMapping[str, str]
161+
reveal_type(walk_values(None, real_mutable_mapping)) # R: MutableMapping[str, int]
156162
# -- walk_values: collection of pairs preserves collection type --
157163
reveal_type(walk_values(int_to_str, str_int_pairs)) # R: list[tuple[str, str]]
158164
reveal_type(walk_values(None, str_int_pairs)) # R: list[tuple[str, int]]
@@ -363,6 +369,33 @@ def add_all(xs: list[int]) -> int: return 0
363369
reveal_type(walk_keys(None, si_dict)) # R: dict[str, int]
364370
reveal_type(walk_values(None, si_dict)) # R: dict[str, int]
365371

372+
# -- New collection types: tuple --
373+
int_tuple: tuple[int, ...] = tuple(int_list)
374+
reveal_type(walk(int_to_str, int_tuple)) # R: tuple[str, ...]
375+
reveal_type(walk(None, int_tuple)) # R: tuple[int, ...]
376+
reveal_type(select(pred_gt0, int_tuple)) # R: tuple[int, ...]
377+
reveal_type(select(None, int_tuple)) # R: tuple[int, ...]
378+
reveal_type(compact(int_tuple)) # R: tuple[int, ...]
379+
str_int_tuple_pairs: tuple[tuple[str, int], ...] = tuple(str_int_pairs)
380+
reveal_type(walk_keys(str_key_to_int, str_int_tuple_pairs)) # R: tuple[tuple[int, int], ...]
381+
reveal_type(walk_keys(None, str_int_tuple_pairs)) # R: tuple[tuple[str, int], ...]
382+
reveal_type(walk_values(int_to_str, str_int_tuple_pairs)) # R: tuple[tuple[str, str], ...]
383+
reveal_type(select_keys(pred_ne_c, str_int_tuple_pairs)) # R: tuple[tuple[str, int], ...]
384+
reveal_type(select_values(pred_gt1, str_int_tuple_pairs)) # R: tuple[tuple[str, int], ...]
385+
reveal_type(flip(str_int_tuple_pairs)) # R: tuple[tuple[int, str], ...]
386+
387+
# -- New collection types: Sequence --
388+
real_sequence = IntSequence()
389+
reveal_type(walk(int_to_str, real_sequence)) # R: Sequence[str]
390+
reveal_type(walk(None, real_sequence)) # R: Sequence[int]
391+
reveal_type(select(pred_gt0, real_sequence)) # R: Sequence[int]
392+
reveal_type(compact(real_sequence)) # R: Sequence[int]
393+
394+
# -- New collection types: Iterator --
395+
int_iter: Iterator[int] = iter([1, 2, 3])
396+
reveal_type(walk(int_to_str, int_iter)) # R: Iterator[str] # XFAIL[ty]: Iterator gives Any
397+
reveal_type(select(pred_gt0, int_iter)) # R: Iterator[int] # XFAIL[ty]: Iterator gives Any
398+
366399
# -- Should be errors --
367400
walk(int_to_str, int_list, int_list) # E: too many arguments
368401
zipdict(123, [1, 2]) # E: not iterable

0 commit comments

Comments
 (0)