From 7bb49db24e82e488336c46ee53f4f4273c55b33e Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 9 Sep 2026 15:24:21 +0100 Subject: [PATCH 1/3] Preserve generic type arguments in conversion errors Fixes #450. --- src/libsemigroups_pybind11/to.py | 5 ++--- tests/test_to.py | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/libsemigroups_pybind11/to.py b/src/libsemigroups_pybind11/to.py index d40d0b26..b054f09a 100644 --- a/src/libsemigroups_pybind11/to.py +++ b/src/libsemigroups_pybind11/to.py @@ -8,7 +8,7 @@ |libsemigroups_pybind11| objects from one type to another. """ -from typing import _GenericAlias +from typing import get_origin as _get_origin from _libsemigroups_pybind11 import ( Order as _Order, @@ -56,7 +56,6 @@ from .todd_coxeter import ToddCoxeter as _ToddCoxeter -# FIXME this function converts list[int] -> list, but shouldn't def _nice_name(type_list): """Convert an iterable of type-like things into a string""" single_element = False @@ -67,7 +66,7 @@ def _nice_name(type_list): for t in type_list: if isinstance(t, str): out.append(t) - elif isinstance(t, _GenericAlias): + elif _get_origin(t) is not None: out.append(str(t)) elif hasattr(t, "__name__"): out.append(t.__name__) diff --git a/tests/test_to.py b/tests/test_to.py index 1e50cc34..9d620625 100644 --- a/tests/test_to.py +++ b/tests/test_to.py @@ -8,6 +8,8 @@ """This module contains some tests for the to function.""" +from typing import Literal + import pytest # TODO(1) be good to remove the imports from _libsemigroups_pybind11, but @@ -50,6 +52,7 @@ to, ) from libsemigroups_pybind11.detail.cxx_wrapper import to_cxx +from libsemigroups_pybind11.to import _nice_name ############################################################################### # Helper functions @@ -1068,6 +1071,41 @@ def test_to_Congruence_010(): ############################################################################### +@pytest.mark.parametrize( + "value, expected", + [ + (list[int], "list[int]"), + (list[str], "list[str]"), + (dict[str, list[int]], "dict[str, list[int]]"), + (tuple[int, ...], "tuple[int, ...]"), + (Literal[1, 2], "typing.Literal[1, 2]"), + (list, "list"), + (str, "str"), + (Presentation, "Presentation"), + ("Set", "Set"), + (Order.lenlex, "Order.lenlex"), + ((Presentation,), "(Presentation)"), + ((Presentation, list[int]), "(Presentation, list[int])"), + ( + (KnuthBendix, list[int], "Trie", Order.lenlex), + "(KnuthBendix, list[int], Trie, Order.lenlex)", + ), + ], +) +def test_nice_name(value, expected): + assert _nice_name(value) == expected + + +def test_to_invalid_word_type(): + with pytest.raises(TypeError) as exc_info: + to(sample_froidure_pin(), rtype=(Presentation, list[str])) + + message = str(exc_info.value) + assert "\n * (Presentation, list[int])\n" in message + assert "\n * (KnuthBendix, list[int], Trie, Order.lenlex)\n" in message + assert message.endswith("but found: (Presentation, list[str])") + + def test_to_999(): x = 10 with pytest.raises(TypeError): From a25f0539bfffd8a68ad8405f1753c55fea0eb32c Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 9 Sep 2026 15:29:12 +0100 Subject: [PATCH 2/3] Quote string values in conversion type names --- src/libsemigroups_pybind11/to.py | 2 +- tests/test_to.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libsemigroups_pybind11/to.py b/src/libsemigroups_pybind11/to.py index b054f09a..2c9e7bf6 100644 --- a/src/libsemigroups_pybind11/to.py +++ b/src/libsemigroups_pybind11/to.py @@ -65,7 +65,7 @@ def _nice_name(type_list): out = [] for t in type_list: if isinstance(t, str): - out.append(t) + out.append(f'"{t}"') elif _get_origin(t) is not None: out.append(str(t)) elif hasattr(t, "__name__"): diff --git a/tests/test_to.py b/tests/test_to.py index 9d620625..b28e1a7c 100644 --- a/tests/test_to.py +++ b/tests/test_to.py @@ -1082,13 +1082,13 @@ def test_to_Congruence_010(): (list, "list"), (str, "str"), (Presentation, "Presentation"), - ("Set", "Set"), + ("Set", '"Set"'), (Order.lenlex, "Order.lenlex"), ((Presentation,), "(Presentation)"), ((Presentation, list[int]), "(Presentation, list[int])"), ( (KnuthBendix, list[int], "Trie", Order.lenlex), - "(KnuthBendix, list[int], Trie, Order.lenlex)", + '(KnuthBendix, list[int], "Trie", Order.lenlex)', ), ], ) @@ -1102,7 +1102,7 @@ def test_to_invalid_word_type(): message = str(exc_info.value) assert "\n * (Presentation, list[int])\n" in message - assert "\n * (KnuthBendix, list[int], Trie, Order.lenlex)\n" in message + assert '\n * (KnuthBendix, list[int], "Trie", Order.lenlex)\n' in message assert message.endswith("but found: (Presentation, list[str])") From 85799ce0d7a945830cababab2e590f1c875d0b59 Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Sat, 12 Sep 2026 12:56:16 +0100 Subject: [PATCH 3/3] to: rename lvars in _nice_name + add trailing comma --- src/libsemigroups_pybind11/to.py | 21 ++++++++++++--------- tests/test_to.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/libsemigroups_pybind11/to.py b/src/libsemigroups_pybind11/to.py index 2c9e7bf6..30ac41a9 100644 --- a/src/libsemigroups_pybind11/to.py +++ b/src/libsemigroups_pybind11/to.py @@ -8,7 +8,8 @@ |libsemigroups_pybind11| objects from one type to another. """ -from typing import get_origin as _get_origin +from collections.abc import Iterator as _Iterator +from typing import Any as _Any, get_origin as _get_origin from _libsemigroups_pybind11 import ( Order as _Order, @@ -56,14 +57,14 @@ from .todd_coxeter import ToddCoxeter as _ToddCoxeter -def _nice_name(type_list): - """Convert an iterable of type-like things into a string""" - single_element = False - if not isinstance(type_list, tuple): - single_element = True - type_list = [type_list] +def _nice_name(types: _Iterator[_Any]) -> str: + """Convert an iterable of things into a string""" + not_tuple = False + if not isinstance(types, tuple): + not_tuple = True + types = (types,) out = [] - for t in type_list: + for t in types: if isinstance(t, str): out.append(f'"{t}"') elif _get_origin(t) is not None: @@ -72,8 +73,10 @@ def _nice_name(type_list): out.append(t.__name__) else: out.append(str(t)) - if single_element: + if not_tuple: return out[0] + if len(types) == 1: + out[0] += "," return f"({', '.join(out)})" diff --git a/tests/test_to.py b/tests/test_to.py index b28e1a7c..4599ae00 100644 --- a/tests/test_to.py +++ b/tests/test_to.py @@ -1084,7 +1084,7 @@ def test_to_Congruence_010(): (Presentation, "Presentation"), ("Set", '"Set"'), (Order.lenlex, "Order.lenlex"), - ((Presentation,), "(Presentation)"), + ((Presentation,), "(Presentation,)"), ((Presentation, list[int]), "(Presentation, list[int])"), ( (KnuthBendix, list[int], "Trie", Order.lenlex),