Skip to content

Commit c0602f3

Browse files
committed
Fix inconsistent set ordering in annotations
1 parent c952b12 commit c0602f3

File tree

2 files changed

+44
-9
lines changed

2 files changed

+44
-9
lines changed

Lib/annotationlib.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,24 @@ def __convert_to_ast(self, other):
371371
elif type(other) in (list, tuple, set):
372372
extra_names = {}
373373
elts = []
374-
for elt in other:
375-
new_elt, new_extra_names = self.__convert_to_ast(elt)
376-
if new_extra_names is not None:
377-
extra_names.update(new_extra_names)
378-
elts.append(new_elt)
374+
375+
# For sets of types, sort elements by name for consistent ordering.
376+
if type(other) is set and all(isinstance(x, type) for x in other):
377+
# Sort the elements by name to ensure deterministic output.
378+
sorted_elts = sorted(other, key=lambda x: x.__name__)
379+
for elt in sorted_elts:
380+
new_elt, new_extra_names = self.__convert_to_ast(elt)
381+
if new_extra_names is not None:
382+
extra_names.update(new_extra_names)
383+
elts.append(new_elt)
384+
else:
385+
# For lists, tuples, and other sets, preserve the original order.
386+
for elt in other:
387+
new_elt, new_extra_names = self.__convert_to_ast(elt)
388+
if new_extra_names is not None:
389+
extra_names.update(new_extra_names)
390+
elts.append(new_elt)
391+
379392
ast_class = {list: ast.List, tuple: ast.Tuple, set: ast.Set}[type(other)]
380393
return ast_class(elts), extra_names
381394
else:
@@ -817,6 +830,10 @@ def _stringify_single(anno):
817830
return anno
818831
elif isinstance(anno, _Template):
819832
return ast.unparse(_template_to_ast(anno))
833+
elif isinstance(anno, set) and all(isinstance(x, type) for x in anno):
834+
# Sort set elements by name to ensure consistent ordering.
835+
sorted_elements = sorted(anno, key=lambda x: x.__name__)
836+
return "{" + ", ".join(x.__name__ for x in sorted_elements) + "}"
820837
else:
821838
return repr(anno)
822839

@@ -1022,10 +1039,17 @@ def annotations_to_string(annotations):
10221039
10231040
Always returns a fresh a dictionary.
10241041
"""
1025-
return {
1026-
n: t if isinstance(t, str) else type_repr(t)
1027-
for n, t in annotations.items()
1028-
}
1042+
result = {}
1043+
for n, t in annotations.items():
1044+
if isinstance(t, str):
1045+
result[n] = t
1046+
elif isinstance(t, set) and all(isinstance(x, type) for x in t):
1047+
# Sort set elements by name to ensure consistent ordering.
1048+
sorted_elements = sorted(t, key=lambda x: x.__name__)
1049+
result[n] = "{" + ", ".join(x.__name__ for x in sorted_elements) + "}"
1050+
else:
1051+
result[n] = type_repr(t)
1052+
return result
10291053

10301054

10311055
def _get_and_call_annotate(obj, format):

Lib/test/test_annotationlib.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,17 @@ def g(
394394
},
395395
)
396396

397+
def test_set_ordering_consistency(self):
398+
"""Test that sets of types are consistently ordered in string representations."""
399+
# Test with direct set annotations
400+
def g1(x: {int, str}):
401+
pass
402+
403+
anno_g1 = get_annotations(g1, format=Format.STRING)
404+
405+
# The set should have elements sorted by name (int before str)
406+
self.assertEqual(anno_g1["x"], "{int, str}")
407+
397408
def test_nested_expressions(self):
398409
def f(
399410
nested: list[Annotated[set[int], "set of ints", 4j]],

0 commit comments

Comments
 (0)