From 0a1e06bbf79b8c2332c268901abfad7fda7dbd7b Mon Sep 17 00:00:00 2001 From: Pierre van de Laar Date: Thu, 8 Oct 2026 16:35:43 +0200 Subject: [PATCH 1/3] Classify PEP 695 type alias statements PYTHON_KIND_MAP had no entry for TypeAlias, so a type alias statement fell through to SemanticKind.NODE on the ast-based backends. Recipes that select statements by semantic kind silently skipped type aliases. Hypothesis found this through test_from_cst_returns_statement. The tree-sitter backend already reports type_alias_statement as a statement, so this also restores agreement between backends. Added a deterministic test so the regression does not depend on Hypothesis rediscovering the input. --- src/renaissance/integrations/python/ast/kinds.py | 1 + test/python/ast/test_python_rst_node.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/renaissance/integrations/python/ast/kinds.py b/src/renaissance/integrations/python/ast/kinds.py index 53135c9b..357db1bf 100644 --- a/src/renaissance/integrations/python/ast/kinds.py +++ b/src/renaissance/integrations/python/ast/kinds.py @@ -70,6 +70,7 @@ "Try": SemanticKind.STATEMENT, "TryStar": SemanticKind.STATEMENT, "Delete": SemanticKind.STATEMENT, + "TypeAlias": SemanticKind.STATEMENT, "Expr": SemanticKind.EXPRESSION, "Await": SemanticKind.EXPRESSION, "Yield": SemanticKind.EXPRESSION, diff --git a/test/python/ast/test_python_rst_node.py b/test/python/ast/test_python_rst_node.py index 12ec59c2..fd65b88e 100644 --- a/test/python/ast/test_python_rst_node.py +++ b/test/python/ast/test_python_rst_node.py @@ -208,6 +208,18 @@ def test_corner_case(self) -> None: node = factory.create_from_text("class ŻP𭻊鲖ÉØ_ąň𣑗: pass\n") assert_that(node.children[0].semantic_kind is not SemanticKind.NODE, is_(True)) + @pytest.mark.parametrize( + "code", + [ + "type Alias = int\n", + "type Alias[T] = list[T]\n", + ], + ) + def test_type_alias_statement_is_classified(self, code: str) -> None: + """Verify a PEP 695 type alias statement is classified as a statement rather than an unclassified node.""" + node = PythonRstNode.load_from_text(code) + assert_that(node[0].semantic_kind, is_(SemanticKind.STATEMENT)) + @pytest.mark.parametrize( "code, expected_kind", [ From dce9052b573681376283af7dd04ab344c16b235b Mon Sep 17 00:00:00 2001 From: Pierre van de Laar Date: Thu, 8 Oct 2026 16:50:31 +0200 Subject: [PATCH 2/3] Assert every Python statement type has a semantic kind Enumerates all ast.stmt subclasses and fails when any falls through to SemanticKind.NODE, so a future grammar addition cannot slip through unclassified. Raises the S101 budget by one for the new assert. Solves issue 262. --- lint-budget.json | 2 +- .../ast/test_python_protocol_metadata.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lint-budget.json b/lint-budget.json index bbde5f2c..12b36b18 100644 --- a/lint-budget.json +++ b/lint-budget.json @@ -78,7 +78,7 @@ "RUF013": 2, "RUF015": 2, "RUF059": 2, - "S101": 201, + "S101": 202, "S110": 4, "S603": 5, "S608": 1, diff --git a/test/python/ast/test_python_protocol_metadata.py b/test/python/ast/test_python_protocol_metadata.py index ee8883cb..ad448091 100644 --- a/test/python/ast/test_python_protocol_metadata.py +++ b/test/python/ast/test_python_protocol_metadata.py @@ -4,10 +4,31 @@ from renaissance.integrations.python.ast.cst_node import PythonCstNode from renaissance.integrations.python.ast.factory import PythonFactory +from renaissance.integrations.python.ast.kinds import PYTHON_KIND_MAP from renaissance.integrations.python.ast.rst_node import PythonRstNode from renaissance.syntax_tree.semantic_kind import SemanticKind +def _all_subclasses(base: type) -> list[type]: + """Collect every subclass of base, recursively.""" + found: list[type] = [] + for subclass in base.__subclasses__(): + found.append(subclass) + found.extend(_all_subclasses(subclass)) + return found + + +def test_every_python_statement_type_is_classified() -> None: + """Assert no ast statement type falls through to NODE, so a new Python grammar cannot add one unnoticed.""" + unclassified = sorted( + statement.__name__ + for statement in _all_subclasses(ast.stmt) + if PYTHON_KIND_MAP.get(statement.__name__, SemanticKind.NODE) is SemanticKind.NODE + ) + + assert not unclassified, f"PYTHON_KIND_MAP has no entry for {unclassified}" + + def test_native_ast_exposes_protocol_metadata() -> None: """AI: Assert a native ast-based Python node exposes the expected parser_kind and semantic_kind.""" node = PythonFactory(ast.AST).create_from_text("def f(): pass").body[0] From ce958665f16edb9866c0761e19dc5e3edfb1ee90 Mon Sep 17 00:00:00 2001 From: Pierre van de Laar Date: Fri, 9 Oct 2026 08:44:57 +0200 Subject: [PATCH 3/3] corrected wrong merge --- lint-budget.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint-budget.json b/lint-budget.json index c5af3aeb..99137eae 100644 --- a/lint-budget.json +++ b/lint-budget.json @@ -78,7 +78,7 @@ "RUF013": 2, "RUF015": 2, "RUF059": 2, - "S101": 203, + "S101": 204, "S110": 4, "S603": 5, "S608": 1,