From 005a3f2469da5bab06cfb2add925a8d227ae5c73 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:08:20 +0200 Subject: [PATCH 01/19] Migrate Python pattern matching to PatternKind --- src/renaissance/integrations/clang/clang_ast_node.py | 9 +++++++-- .../integrations/clang/clang_json_ast_node.py | 7 +++++++ src/renaissance/integrations/python/ast/factory.py | 6 +++--- src/renaissance/syntax_tree/match_finder.py | 6 +++--- test/python/ast/test_patternic_style.py | 11 +++++------ test/python/ast/test_python_matcher.py | 5 +++-- test/python/ast/test_python_pattern_factory.py | 9 +++++---- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/renaissance/integrations/clang/clang_ast_node.py b/src/renaissance/integrations/clang/clang_ast_node.py index 451da149..40850a5d 100644 --- a/src/renaissance/integrations/clang/clang_ast_node.py +++ b/src/renaissance/integrations/clang/clang_ast_node.py @@ -27,6 +27,7 @@ UnknownType, ) from renaissance.syntax_tree import ASTFinder, ASTNode, ASTReference +from renaissance.syntax_tree.pattern_kind import PatternKind from renaissance.syntax_tree.semantic_kind import SemanticKind from renaissance.utils.ast_utils import match_children, match_props @@ -131,6 +132,10 @@ def __init__( self._kind = insert_kind if insert_kind is not None else self.__derive_kind() self.parser_kind = self._kind self.semantic_kind = CLANG_KIND_MAP.get(self.parser_kind, SemanticKind.NODE) + self.pattern_kind = { + "MatchOne": PatternKind.MATCH_ONE, + "MatchAll": PatternKind.MATCH_ALL, + }.get(self.parser_kind) self.ast_type = KIND_MAP.get(self._kind, UnknownType) self.indent = "" # TODO: TextUtils.get_indent(self.content, self._offset) @@ -443,9 +448,9 @@ def __derive_kind(self) -> str: return str(self.node.kind.name) if self.node.kind.name in ["UNEXPOSED_EXPR", "VAR_DECL", "DECL_REF_EXPR"]: if self.node.displayname.startswith("$$") and " " not in self.node.displayname: - return MatchAll.__name__ + return "MatchAll" if self.node.displayname.startswith("$") and " " not in self.node.displayname: - return MatchOne.__name__ + return "MatchOne" return str(self.node.kind.name) except Exception: return EMPTY_STR diff --git a/src/renaissance/integrations/clang/clang_json_ast_node.py b/src/renaissance/integrations/clang/clang_json_ast_node.py index d3ec6867..d19bc74b 100644 --- a/src/renaissance/integrations/clang/clang_json_ast_node.py +++ b/src/renaissance/integrations/clang/clang_json_ast_node.py @@ -31,6 +31,7 @@ UnknownType, ) from renaissance.syntax_tree import ASTNode, ASTReference +from renaissance.syntax_tree.pattern_kind import PatternKind from renaissance.syntax_tree.semantic_kind import SemanticKind from renaissance.utils.ast_utils import match_children, match_props @@ -118,6 +119,10 @@ def __init__( self._kind = insert_kind if insert_kind is not None else self.__derive_kind() self.parser_kind = self._kind self.semantic_kind = CLANG_KIND_MAP.get(self.parser_kind, SemanticKind.NODE) + self.pattern_kind = { + "MatchOne": PatternKind.MATCH_ONE, + "MatchAll": PatternKind.MATCH_ALL, + }.get(self.parser_kind) self.ast_type = KIND_MAP.get(self._kind, UnknownType) self._name = insert_name if insert_name is not None else self._derive_name() # a fake child is introduced to handle the case where the type of declaration is not found @@ -164,9 +169,11 @@ def __init__( if self.name.startswith("$$"): self._kind = MatchAll.__name__ self.ast_type = MatchAll + self.pattern_kind = PatternKind.MATCH_ALL elif self.name.startswith("$"): self._kind = MatchOne.__name__ self.ast_type = MatchOne + self.pattern_kind = PatternKind.MATCH_ONE self._children = self.__inserted_children + [ ClangJsonASTNode( ClangJsonASTNode._remove_wrapper(n), diff --git a/src/renaissance/integrations/python/ast/factory.py b/src/renaissance/integrations/python/ast/factory.py index f742b287..48d4a71a 100644 --- a/src/renaissance/integrations/python/ast/factory.py +++ b/src/renaissance/integrations/python/ast/factory.py @@ -12,7 +12,7 @@ from renaissance.integrations.python.ast.rst_node import PythonRstNode from renaissance.integrations.tree_sitter.adapter import TreeSitterAdapter from renaissance.integrations.tree_sitter.lst import LSTNode -from renaissance.integrations.types import Arg, DeclarationExpression, ExpressionStatement, MatchAll, MatchOne, Name, Type +from renaissance.integrations.types import Arg, DeclarationExpression, ExpressionStatement, Name, Type from renaissance.syntax_tree.match_finder import is_match from renaissance.syntax_tree.node_protocol import NodeProtocol from renaissance.syntax_tree.pattern_kind import PatternKind @@ -79,10 +79,10 @@ def derive_type(self, node) -> str: if node.ast_type in [DeclarationExpression, ExpressionStatement, Name, Arg]: if _MATCH_ALL_RE.match(signature): self.pattern_kind = PatternKind.MATCH_ALL - return MatchAll + return node.ast_type if _MATCH_ONE_RE.match(signature): self.pattern_kind = PatternKind.MATCH_ONE - return MatchOne + return node.ast_type return node.ast_type diff --git a/src/renaissance/syntax_tree/match_finder.py b/src/renaissance/syntax_tree/match_finder.py index ce077bee..e2df6680 100644 --- a/src/renaissance/syntax_tree/match_finder.py +++ b/src/renaissance/syntax_tree/match_finder.py @@ -1,7 +1,6 @@ from collections.abc import Iterable, Sequence from typing import Self -from renaissance.integrations.types import MatchAll, MatchOne from renaissance.utils.ast_utils import use_dollar from .node_protocol import AstProtocol as AstProtocol @@ -20,9 +19,10 @@ def pattern_kind(node: NodeProtocol) -> PatternKind | None: value = getattr(node, "pattern_kind", None) if value is not None: return value - if node.ast_type == MatchOne: + legacy_kind = getattr(node.ast_type, "__name__", "") + if legacy_kind == "MatchOne": return PatternKind.MATCH_ONE - if node.ast_type == MatchAll: + if legacy_kind == "MatchAll": return PatternKind.MATCH_ALL return None diff --git a/test/python/ast/test_patternic_style.py b/test/python/ast/test_patternic_style.py index c24bc48f..e5e24b43 100644 --- a/test/python/ast/test_patternic_style.py +++ b/test/python/ast/test_patternic_style.py @@ -16,8 +16,6 @@ If, Import, Match, - MatchAll, - MatchOne, Pass, Raise, Return, @@ -27,6 +25,7 @@ With, ) from renaissance.syntax_tree.match_finder import is_match +from renaissance.syntax_tree.pattern_kind import PatternKind class TestPythonicStyle: @@ -119,20 +118,20 @@ def test_assign_node_2(self): def python_does_not_parse_dollar(self): it = PythonRstNode.load_from_text("$pa") - assert_that(it.ast_type, is_(MatchOne)) + assert_that(it.pattern_kind, is_(PatternKind.MATCH_ONE)) def python_does_not_parse_dollar_dollar(self): it = PythonRstNode.load_from_text("$$pa") - assert_that(it.ast_type, is_(MatchAll)) + assert_that(it.pattern_kind, is_(PatternKind.MATCH_ALL)) def test_kind_is_match_all(self): PythonPatternFactory(PythonFactory(PythonRstNode)) simple = self.pattern_factory.create_statement("$$pa") - assert_that(simple.ast_type(), instance_of(MatchAll)) + assert_that(simple.pattern_kind, is_(PatternKind.MATCH_ALL)) def test_kind_is_match_one(self): simple = self.pattern_factory.create_statement("$pa") - assert_that(simple.ast_type(), instance_of(MatchOne)) + assert_that(simple.pattern_kind, is_(PatternKind.MATCH_ONE)) def test_match_one_is_not_equal(self): atu = self.factory.create_from_text("ba(55)\nca(555)\nlo(4444)\nna=55", "test.py") diff --git a/test/python/ast/test_python_matcher.py b/test/python/ast/test_python_matcher.py index e034bf2a..ac349f69 100644 --- a/test/python/ast/test_python_matcher.py +++ b/test/python/ast/test_python_matcher.py @@ -6,7 +6,7 @@ from renaissance.integrations.python.ast.factory import PythonFactory, PythonPatternFactory from renaissance.integrations.python.ast.rst_node import PythonRstNode -from renaissance.integrations.types import ExpressionStatement, MatchOne +from renaissance.integrations.types import ExpressionStatement from renaissance.syntax_tree import MatchFinder from renaissance.syntax_tree.match_finder import ( find_variants, @@ -14,6 +14,7 @@ match_pattern, variant_in_match_stmt, ) +from renaissance.syntax_tree.pattern_kind import PatternKind class TestPythonMatcher: @@ -109,7 +110,7 @@ def test_generic_is_match_any_stmt(self): def test_generic_is_match_any_assignment(self): atu = self.factory.create_from_text("na=55", "test.py") simple = self.pattern_factory.create_statement("$pa") - assert_that(simple.ast_type(), instance_of(MatchOne)) + assert_that(simple.pattern_kind, is_(PatternKind.MATCH_ONE)) assert_that(is_match(atu.children[0], simple, {}), is_(True)) def test_match_multiple_single_stmt(self): diff --git a/test/python/ast/test_python_pattern_factory.py b/test/python/ast/test_python_pattern_factory.py index b9f934d5..a389ac5d 100644 --- a/test/python/ast/test_python_pattern_factory.py +++ b/test/python/ast/test_python_pattern_factory.py @@ -29,6 +29,7 @@ With, ) from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.pattern_kind import PatternKind class TestPythonFactory: @@ -299,7 +300,7 @@ def test_misalignment(self, _, factory, raw, expected) -> None: def test_function_with_multi_patterns(self): pattern = self.pattern_factory.create_expression("$f($$before, $a, $$after)") assert_that(pattern.ast_type(), Call) - assert_that(pattern.children[0].ast_type(), is_(MatchOne)) - assert_that(pattern.children[1].children[0].ast_type(), is_(MatchAll)) - assert_that(pattern.children[1].children[1].ast_type(), is_(MatchOne)) - assert_that(pattern.children[1].children[2].ast_type(), is_(MatchAll)) + assert_that(pattern.children[0].pattern_kind, is_(PatternKind.MATCH_ONE)) + assert_that(pattern.children[1].children[0].pattern_kind, is_(PatternKind.MATCH_ALL)) + assert_that(pattern.children[1].children[1].pattern_kind, is_(PatternKind.MATCH_ONE)) + assert_that(pattern.children[1].children[2].pattern_kind, is_(PatternKind.MATCH_ALL)) From b0a1d9b05663f5c41b9d421fa1bff4e5cc71b0ad Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:08:21 +0200 Subject: [PATCH 02/19] Migrate Python examples to semantic finder APIs --- src/rejuvenation/batch_process_examples.py | 6 +++--- src/rejuvenation/python_ast_example.py | 6 +++--- src/rejuvenation/python_cst_example.py | 6 +++--- src/rejuvenation/python_lst_example.py | 6 +++--- src/rejuvenation/python_rst_example.py | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/rejuvenation/batch_process_examples.py b/src/rejuvenation/batch_process_examples.py index a13de232..8af79833 100644 --- a/src/rejuvenation/batch_process_examples.py +++ b/src/rejuvenation/batch_process_examples.py @@ -5,7 +5,6 @@ from renaissance.integrations.clang import ClangASTNode from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode -from renaissance.integrations.types import Call from renaissance.recipes import CleanupRefactoring from renaissance.syntax_tree import ( ASTFactory, @@ -20,6 +19,7 @@ final_action, recipe_step, ) +from renaissance.syntax_tree.semantic_kind import SemanticKind example_1 = textwrap.dedent(""" void x(int a) {} @@ -109,7 +109,7 @@ def batch_repeat_example(): # remove a function to create more unused variables def remove_function(ast_processor: ASTProcessor): - [ast_processor.insert_before("// ", node, False, False) for node in ast_processor.find_ast_type(Call)] + [ast_processor.insert_before("// ", node, False, False) for node in ast_processor.find_semantic_kind(SemanticKind.CALL)] # batch_processor.repeat(simple_codebase_provider, [remove_function]) batch_processor.repeat( @@ -134,7 +134,7 @@ def __init__(self): def store_function_call(self, ast_processor: ASTProcessor) -> Callable[[], None] | None: # find all function calls and store them, this routing is invoked in parallel! calls: list[CallInfo] = [] - [AnalysisRecipe._add_function_call(node, calls) for node in ast_processor.find_ast_type(Call)] + [AnalysisRecipe._add_function_call(node, calls) for node in ast_processor.find_semantic_kind(SemanticKind.CALL)] # the resulting lambda is invoked single threaded # this kind of mechanism is mainly used to store results from multiple processors # for refactoring operations this is not needed as a refactoring operation is single threaded diff --git a/src/rejuvenation/python_ast_example.py b/src/rejuvenation/python_ast_example.py index 7da83122..ef5904e3 100644 --- a/src/rejuvenation/python_ast_example.py +++ b/src/rejuvenation/python_ast_example.py @@ -3,10 +3,10 @@ from rejuvenation.python_lst_example import python_lst_smoke_test from renaissance.integrations.python.ast.factory import PythonFactory, PythonPatternFactory -from renaissance.integrations.types import Call from renaissance.syntax_tree import ASTRewriter, ASTShower -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind example_code = """ from module import foo, bar, baz, quux @@ -43,7 +43,7 @@ def python_ast_smoke_test(): ASTShower.show_node(atu) print("_______________simple find____________________________________") - nodes = find_ast_type(atu, Call) + nodes = find_semantic_kind(atu, SemanticKind.CALL) ASTShower.show_node(nodes[0]) diff --git a/src/rejuvenation/python_cst_example.py b/src/rejuvenation/python_cst_example.py index 7df4a884..73778fef 100644 --- a/src/rejuvenation/python_cst_example.py +++ b/src/rejuvenation/python_cst_example.py @@ -3,10 +3,10 @@ from rejuvenation.python_lst_example import python_lst_smoke_test from renaissance.integrations.python.ast.cst_node import PythonCstNode from renaissance.integrations.python.ast.factory import PythonFactory, PythonPatternFactory -from renaissance.integrations.types import Call from renaissance.syntax_tree import ASTRewriter, ASTShower -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind example_code = """ from module import foo, bar, baz, quux @@ -43,7 +43,7 @@ def python_cst_smoke_test(): ASTShower.show_node(atu) print("_______________simple find____________________________________") - nodes = find_ast_type(atu, Call) + nodes = find_semantic_kind(atu, SemanticKind.CALL) ASTShower.show_node(nodes[0]) diff --git a/src/rejuvenation/python_lst_example.py b/src/rejuvenation/python_lst_example.py index f6b44553..d76d81d9 100644 --- a/src/rejuvenation/python_lst_example.py +++ b/src/rejuvenation/python_lst_example.py @@ -2,10 +2,10 @@ from renaissance.integrations.python.ast.factory import PythonFactory, PythonPatternFactory from renaissance.integrations.tree_sitter.lst import LSTNode -from renaissance.integrations.types import Call from renaissance.syntax_tree import ASTRewriter, ASTShower -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind example_code = """ from module import foo, bar, baz, quux @@ -42,7 +42,7 @@ def python_lst_smoke_test(): ASTShower.show_node(atu) print("_______________simple find____________________________________") - nodes = find_ast_type(atu, Call) + nodes = find_semantic_kind(atu, SemanticKind.CALL) ASTShower.show_node(nodes[0]) diff --git a/src/rejuvenation/python_rst_example.py b/src/rejuvenation/python_rst_example.py index a992277d..03157642 100644 --- a/src/rejuvenation/python_rst_example.py +++ b/src/rejuvenation/python_rst_example.py @@ -4,10 +4,10 @@ from renaissance.integrations.python.ast.factory import PythonFactory, PythonPatternFactory from renaissance.integrations.python.ast.rst_node import PythonRstNode -from renaissance.integrations.types import Call from renaissance.syntax_tree import ASTRewriter, ASTShower -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind example_code = """ from module import foo, bar, baz, quux @@ -41,7 +41,7 @@ def python_rst_smoke_test(): ASTShower.show_node(atu) print("_______________simple find____________________________________") - nodes = find_ast_type(atu, Call) + nodes = find_semantic_kind(atu, SemanticKind.CALL) ASTShower.show_node(nodes[0]) From e03cd66583ce82bf3eaaffdeaaff5e28597a72a9 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:12 +0200 Subject: [PATCH 03/19] Migrate Python recipes to semantic kinds --- src/renaissance/integrations/clang/kinds.py | 5 ++ .../integrations/python/ast/kinds.py | 2 + .../recipes/cleanup_refactoring.py | 8 ++- src/renaissance/recipes/taut2pyunit.py | 67 ++++++++++++++----- src/renaissance/recipes/unit2pytest.py | 12 ++-- src/renaissance/syntax_tree/semantic_kind.py | 1 + 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/renaissance/integrations/clang/kinds.py b/src/renaissance/integrations/clang/kinds.py index f64d7ec3..36d390e8 100644 --- a/src/renaissance/integrations/clang/kinds.py +++ b/src/renaissance/integrations/clang/kinds.py @@ -5,13 +5,16 @@ "translation_unit": SemanticKind.TRANSLATION_UNIT, "TranslationUnitDecl": SemanticKind.TRANSLATION_UNIT, "FunctionDecl": SemanticKind.FUNCTION, + "FUNCTION_DECL": SemanticKind.FUNCTION, "CXXMethodDecl": SemanticKind.FUNCTION, "CallExpr": SemanticKind.CALL, "DeclRefExpr": SemanticKind.NAME, "IntegerLiteral": SemanticKind.LITERAL, + "INTEGER_LITERAL": SemanticKind.LITERAL, "FloatingLiteral": SemanticKind.LITERAL, "StringLiteral": SemanticKind.LITERAL, "VarDecl": SemanticKind.DECLARATION, + "VAR_DECL": SemanticKind.DECLARATION, "ParmVarDecl": SemanticKind.PARAMETER, "BinaryOperator": SemanticKind.BINARY_OPERATION, "UnaryOperator": SemanticKind.UNARY_OPERATION, @@ -19,9 +22,11 @@ "ForStmt": SemanticKind.LOOP, "WhileStmt": SemanticKind.LOOP, "ReturnStmt": SemanticKind.RETURN, + "RETURN_STMT": SemanticKind.RETURN, "RecordDecl": SemanticKind.CLASS, "CXXRecordDecl": SemanticKind.CLASS, "NamespaceDecl": SemanticKind.DEFINITION, "CompoundStmt": SemanticKind.STATEMENT, + "COMPOUND_STMT": SemanticKind.STATEMENT, "Import": SemanticKind.IMPORT, } diff --git a/src/renaissance/integrations/python/ast/kinds.py b/src/renaissance/integrations/python/ast/kinds.py index d367e8e6..b7a748ed 100644 --- a/src/renaissance/integrations/python/ast/kinds.py +++ b/src/renaissance/integrations/python/ast/kinds.py @@ -7,12 +7,14 @@ "ClassDef": SemanticKind.CLASS, "Call": SemanticKind.CALL, "Name": SemanticKind.NAME, + "Attribute": SemanticKind.ATTRIBUTE, "arg": SemanticKind.PARAMETER, "Param": SemanticKind.PARAMETER, "Constant": SemanticKind.LITERAL, "Integer": SemanticKind.LITERAL, "Float": SemanticKind.LITERAL, "SimpleString": SemanticKind.LITERAL, + "FormattedString": SemanticKind.LITERAL, "Assign": SemanticKind.ASSIGNMENT, "AnnAssign": SemanticKind.ASSIGNMENT, "AugAssign": SemanticKind.ASSIGNMENT, diff --git a/src/renaissance/recipes/cleanup_refactoring.py b/src/renaissance/recipes/cleanup_refactoring.py index 29a357f1..4a8d95e3 100644 --- a/src/renaissance/recipes/cleanup_refactoring.py +++ b/src/renaissance/recipes/cleanup_refactoring.py @@ -1,8 +1,8 @@ from more_itertools import flatten -from renaissance.integrations.types import CompoundStatement, VariableDef from renaissance.syntax_tree import ASTProcessor -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind +from renaissance.syntax_tree.semantic_kind import SemanticKind class CleanupRefactoring: @@ -12,5 +12,7 @@ def __init__(self): @staticmethod def remove_unused_variables(ast_refactor: ASTProcessor) -> None: """Removes all unused variables from a function.""" - refs = flatten(find_ast_type(n, VariableDef) for n in find_ast_type(ast_refactor.node, CompoundStatement)) + refs = flatten( + find_semantic_kind(n, SemanticKind.DECLARATION) for n in find_semantic_kind(ast_refactor.node, SemanticKind.STATEMENT) + ) [ast_refactor.remove(ref.parent, True, True) for ref in refs if len(ref.referenced_by) == 0] diff --git a/src/renaissance/recipes/taut2pyunit.py b/src/renaissance/recipes/taut2pyunit.py index 0f7fae0e..c7b858f2 100644 --- a/src/renaissance/recipes/taut2pyunit.py +++ b/src/renaissance/recipes/taut2pyunit.py @@ -5,9 +5,10 @@ import test_data.test_class as tst_class import test_data.test_insert as tst_insert -from renaissance.integrations.types import Attribute, FunctionDef, ImportFrom, ImportStatement, Name from renaissance.recipes.python_refactoring import PythonRefactoring +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind class Taut2Pyunit(PythonRefactoring): @@ -78,11 +79,19 @@ def get_migrated_path(self, file_path): def replace_taut(self): """Replace TAUT.TestCase by unittest.TestCase.""" - [self.replace("unittest.TestCase", node, False, False) for node in self.find_ast_type(Attribute) if node.name == "TAUT.TestCase"] - [self.replace("unittest.TestCase", node, False, False) for node in self.find_ast_type(Name) if node.name == "TestCase"] + [ + self.replace("unittest.TestCase", node, False, False) + for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) + if node.name == "TAUT.TestCase" + ] + [ + self.replace("unittest.TestCase", node, False, False) + for node in self.find_semantic_kind(SemanticKind.NAME) + if node.name == "TestCase" + ] def remove_decorator(self): - [self.remove(node, False, False) for node in self.find_ast_type(Attribute) if node.name == "TAUT.log_stub"] + [self.remove(node, False, False) for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "TAUT.log_stub"] def add_self(self): matching = [ @@ -105,27 +114,43 @@ def add_self(self): "emrwxviprxwh", ] parent_func = ["setUpCommon", "setUp"] - [self.replace("self." + node.name, node, False, False) for node in self.find_ast_type(Name) if node.name in matching] + [ + self.replace("self." + node.name, node, False, False) + for node in self.find_semantic_kind(SemanticKind.NAME) + if node.name in matching + ] matching2 = ["EMRWxREAD.emrwxread"] [ self.replace("self." + node.name.split(".")[1], node, False, False) - for node in self.find_ast_type(Attribute) + for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name in matching2 and node.get_ancestor("FunctionDef").name not in parent_func ] def convert_assert(self): - [self.replace("self.assertFalse", node, False, False) for node in self.find_ast_type(Attribute) if node.name == "self.assert_false"] - [self.replace("self.assertTrue", node, False, False) for node in self.find_ast_type(Attribute) if node.name == "self.assert_true"] - [self.replace("self.assertEqual", node, False, False) for node in self.find_ast_type(Attribute) if node.name == "self.assert_equal"] + [ + self.replace("self.assertFalse", node, False, False) + for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) + if node.name == "self.assert_false" + ] + [ + self.replace("self.assertTrue", node, False, False) + for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) + if node.name == "self.assert_true" + ] + [ + self.replace("self.assertEqual", node, False, False) + for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) + if node.name == "self.assert_equal" + ] def remove_stubserver(self): - [self.remove(node, False, False) for node in self.find_ast_type(Attribute) if node.name == "TAUT.StubServer"] + [self.remove(node, False, False) for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "TAUT.StubServer"] def replace_mock(self): [ self.replace("patch", node, False, False) - for node in self.find_ast_type(Attribute) + for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "mock.patch" and node.parent.parent.name == "decorator_list" ] @@ -225,16 +250,16 @@ def convert_teardown_common(self): def convert_add_patcher(self): pattern = self.pattern_factory.create_statements("def tearDownCommon(self):\n $$aa") for match in match_pattern(self.root.children, pattern): - patcher_pattern = [node for node in self.find_ast_type(FunctionDef) if node.name == "add_patcher"] + patcher_pattern = [node for node in self.find_semantic_kind(SemanticKind.FUNCTION) if node.name == "add_patcher"] if len(patcher_pattern) == 0: self.insert_after(tst_class.insert_add_patcher, match.nodes) def find_import_interface(self, name: str): interface = name if name.islower(): - node_list = [node for node in self.find_ast_type(ImportStatement) if node.name == name] + node_list = [node for node in self.find_semantic_kind(SemanticKind.IMPORT) if node.name == name] if node_list: - if node_list[0].ast_type == ImportFrom: + if node_list[0].parser_kind == "ImportFrom": interface = node_list[0].properties["module"] else: interface = node_list[0].name if node_list else name @@ -293,7 +318,11 @@ def convert_setup(self): for match in match_pattern(self.root.children, pattern5): self.remove(match.nodes, False, False) self.commit() - [self.replace("self.context_stub", node, False, False) for node in self.find_ast_type(Name) if node.name == "context_stub"] + [ + self.replace("self.context_stub", node, False, False) + for node in self.find_semantic_kind(SemanticKind.NAME) + if node.name == "context_stub" + ] def convert_teardown(self): matched_pattern = self.pattern_factory.create_statements("def tearDown(self):\n $$aa") @@ -345,7 +374,7 @@ def insert_patch_import(self): def replace_taut_skip(self): """Replace @TAUT.skip_test by @unittest.skip.""" - [self.replace("@unittest.skip", node) for node in self.find_ast_type(Attribute) if node.name == "TAUT.skip_test"] + [self.replace("@unittest.skip", node) for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "TAUT.skip_test"] def convert_import_verify(self): import_verify = self.pattern_factory.create_statements("self.import_and_verify_module('$a')") @@ -398,7 +427,11 @@ def assert_func(self): "assert_raises", "assert_double_equal", ] - [self.replace("self." + node.name, node, False, False) for node in self.find_ast_type(Name) if node.name in matching] + [ + self.replace("self." + node.name, node, False, False) + for node in self.find_semantic_kind(SemanticKind.NAME) + if node.name in matching + ] def move_indent(self, indent): pattern1 = self.pattern_factory.create_statements("""def $a($$b): diff --git a/src/renaissance/recipes/unit2pytest.py b/src/renaissance/recipes/unit2pytest.py index 302fd060..c6e6bb54 100644 --- a/src/renaissance/recipes/unit2pytest.py +++ b/src/renaissance/recipes/unit2pytest.py @@ -3,11 +3,11 @@ from pathlib import Path from renaissance.integrations.python.ast.util import convert_function -from renaissance.integrations.types import Attribute, ClassDef, FormattedString, FunctionDef, Literal, Number from renaissance.recipes.python_refactoring import PythonRefactoring from renaissance.syntax_tree import PatternMatch -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import AstProtocol, match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind class Unit2Pytest(PythonRefactoring): @@ -123,7 +123,7 @@ def convert_assert(self, pattern, replacement): self.replace(repl, match.nodes, False, False) def is_swapped(self, match: PatternMatch) -> bool: - return match.expansions["$exp"][0].ast_type in [Literal, FormattedString, Number] + return match.expansions["$exp"][0].semantic_kind is SemanticKind.LITERAL def convert_parameterized_test(self): unittest = self.pattern_factory.create_statements( @@ -171,7 +171,7 @@ def convert_plain_assert_same_length(self): self.replace(repl, match.nodes, False, False) def convert_skip_test(self): - nodes = find_ast_type(self.root, Attribute) + nodes = find_semantic_kind(self.root, SemanticKind.ATTRIBUTE) for node in nodes: if node.signature == "unittest.skip": self.replace("pytest.mark.skip", node, False, False) @@ -187,8 +187,8 @@ def swap_expected_and_actual(self): self.replace(repl, match.nodes, False, False) def restructure_module(self): - funs = [stmt for stmt in self.body if stmt.ast_type == FunctionDef] - test_classes = [stmt for stmt in self.body if stmt.ast_type == ClassDef and stmt.name.startswith("Test")] + funs = [stmt for stmt in self.body if stmt.semantic_kind is SemanticKind.FUNCTION] + test_classes = [stmt for stmt in self.body if stmt.semantic_kind is SemanticKind.CLASS and stmt.name.startswith("Test")] if len(funs) == 0: return if len(test_classes) == 0: diff --git a/src/renaissance/syntax_tree/semantic_kind.py b/src/renaissance/syntax_tree/semantic_kind.py index 7cd2f430..4c6e6974 100644 --- a/src/renaissance/syntax_tree/semantic_kind.py +++ b/src/renaissance/syntax_tree/semantic_kind.py @@ -9,6 +9,7 @@ class SemanticKind(StrEnum): DECLARATION = "declaration" DEFINITION = "definition" NAME = "name" + ATTRIBUTE = "attribute" LITERAL = "literal" CALL = "call" FUNCTION = "function" From 94bd92cb58aa6b74a4abeb6a6be99b083afd548f Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:13 +0200 Subject: [PATCH 04/19] Migrate Tree-sitter utilities to semantic kinds --- .../integrations/tree_sitter/extractor.py | 21 ++++++++++++------- .../integrations/tree_sitter/visualizer.py | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/renaissance/integrations/tree_sitter/extractor.py b/src/renaissance/integrations/tree_sitter/extractor.py index ce04093d..837ed0dd 100644 --- a/src/renaissance/integrations/tree_sitter/extractor.py +++ b/src/renaissance/integrations/tree_sitter/extractor.py @@ -4,14 +4,21 @@ from renaissance.integrations.tree_sitter.adapter import TreeSitterAdapter from renaissance.integrations.tree_sitter.factory import TreeSitterPatternFactory -from renaissance.integrations.types import Call, FunctionDef from renaissance.syntax_tree import PatternMatch from renaissance.syntax_tree.match_finder import match_pattern +from renaissance.syntax_tree.semantic_kind import SemanticKind GRAPHML_DIR = "out_graphml" Path(GRAPHML_DIR).mkdir(parents=True, exist_ok=True) +def _has_semantic_kind(node, kind: SemanticKind) -> bool: + if getattr(node, "semantic_kind", None) is kind: + return True + legacy_kind = getattr(getattr(node, "ast_type", None), "__name__", "") + return legacy_kind == {SemanticKind.FUNCTION: "FunctionDef", SemanticKind.CALL: "Call"}.get(kind) + + class Extractor: def __init__(self, factory: TreeSitterPatternFactory, patterns: list[str]): self.pattern_factory = factory @@ -60,12 +67,12 @@ def _process_file(self, file_path, lst): self.graph.add_edge(folder, file_path, type="contains") for node in lst.traverse(): - if node.ast_type == FunctionDef: + if _has_semantic_kind(node, SemanticKind.FUNCTION): name = node.signature.split("(")[0].split()[-1] self.graph.add_node(name, type="function", file=file_path) self.graph.add_edge(file_path, name, type="defines") - elif node.ast_type == Call: + elif _has_semantic_kind(node, SemanticKind.CALL): call_target = node.signature.strip().split("(")[0] self.graph.add_node(call_target, type="call_target") self.graph.add_edge(file_path, call_target, type="calls") @@ -79,12 +86,12 @@ def _process_file(self, file_path, lst): self.graph.add_edge(folder, file_path, type="contains") for node in lst.traverse(): - if node.ast_type == FunctionDef: + if _has_semantic_kind(node, SemanticKind.FUNCTION): name = node.properties.get("name", "method") self.graph.add_node(name, type="method", file=file_path) self.graph.add_edge(file_path, name, type="defines") - elif node.ast_type == Call: + elif _has_semantic_kind(node, SemanticKind.CALL): target = node.signature.strip().split("(")[0] self.graph.add_node(target, type="method_target") self.graph.add_edge(file_path, target, type="calls") @@ -98,12 +105,12 @@ def _process_file(self, file_path, lst): self.graph.add_edge(folder, file_path, type="contains") for node in lst.traverse(): - if node.ast_type == FunctionDef: + if _has_semantic_kind(node, SemanticKind.FUNCTION): name = node.properties.get("name", "func") self.graph.add_node(name, type="function", file=file_path) self.graph.add_edge(file_path, name, type="defines") - elif node.ast_type == Call: + elif _has_semantic_kind(node, SemanticKind.CALL): call_expr = node.signature.strip().split("(")[0] self.graph.add_node(call_expr, type="call_target") self.graph.add_edge(file_path, call_expr, type="calls") diff --git a/src/renaissance/integrations/tree_sitter/visualizer.py b/src/renaissance/integrations/tree_sitter/visualizer.py index b7de5dad..45dcd2e1 100644 --- a/src/renaissance/integrations/tree_sitter/visualizer.py +++ b/src/renaissance/integrations/tree_sitter/visualizer.py @@ -17,7 +17,7 @@ def _get_node_id(self, node): def _render_node(self, node): node_id = self._get_node_id(node) label = f"""\ - {node_id}: {node.ast_type.__name__} {{ + {node_id}: {node.semantic_kind} ({node.parser_kind}) {{ offset: {node.offset} signature: {signature2id(node.signature)} }}""" From 241858f6ed675466d45f823d38418fc2f06b92cd Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:14 +0200 Subject: [PATCH 05/19] Add native Clang semantic kind aliases --- src/renaissance/integrations/clang/kinds.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renaissance/integrations/clang/kinds.py b/src/renaissance/integrations/clang/kinds.py index 36d390e8..4fe84f83 100644 --- a/src/renaissance/integrations/clang/kinds.py +++ b/src/renaissance/integrations/clang/kinds.py @@ -29,4 +29,6 @@ "CompoundStmt": SemanticKind.STATEMENT, "COMPOUND_STMT": SemanticKind.STATEMENT, "Import": SemanticKind.IMPORT, + "INCLUSION_DIRECTIVE": SemanticKind.IMPORT, + "InclusionDirective": SemanticKind.IMPORT, } From ee28da19996e006f7aede051c3a5d2d41b95e48d Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:15 +0200 Subject: [PATCH 06/19] Document and test Clang semantic classification --- docs/developer/architecture/code-architecture.md | 5 +++++ test/c_cpp/test_node_protocol_metadata.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/developer/architecture/code-architecture.md b/docs/developer/architecture/code-architecture.md index e09ea7a6..045055f2 100644 --- a/docs/developer/architecture/code-architecture.md +++ b/docs/developer/architecture/code-architecture.md @@ -29,6 +29,11 @@ with basic functionality and a back door: `get_original_node` to obtain the AST node as provided by the parser. +1. Parser integrations expose both an exact `parser_kind` and a shared `semantic_kind`. + Multiple parser kinds may map to one shared semantic kind, such as Clang's `FunctionDecl` and + `CXXMethodDecl` both mapping to a function. Parser-specific concepts remain available through + `parser_kind` and integration-local predicates; they are not forced into the shared vocabulary. + 1. AST Nodes are read only and immutable. 1. AST Nodes are navigable, so parent must be present (except for the ATU / top node) and diff --git a/test/c_cpp/test_node_protocol_metadata.py b/test/c_cpp/test_node_protocol_metadata.py index aa825565..bfc483ad 100644 --- a/test/c_cpp/test_node_protocol_metadata.py +++ b/test/c_cpp/test_node_protocol_metadata.py @@ -1,6 +1,7 @@ from pathlib import Path from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode +from renaissance.integrations.clang.kinds import CLANG_KIND_MAP from renaissance.syntax_tree.semantic_kind import SemanticKind @@ -9,3 +10,17 @@ def test_clang_json_nodes_expose_protocol_metadata(): assert node.parser_kind == "TranslationUnitDecl" assert node.semantic_kind is SemanticKind.TRANSLATION_UNIT + + +def test_clang_common_kinds_map_to_shared_semantic_kinds(): + assert CLANG_KIND_MAP["FunctionDecl"] is SemanticKind.FUNCTION + assert CLANG_KIND_MAP["CallExpr"] is SemanticKind.CALL + assert CLANG_KIND_MAP["VarDecl"] is SemanticKind.DECLARATION + assert CLANG_KIND_MAP["CXXRecordDecl"] is SemanticKind.CLASS + + +def test_clang_specific_unknown_kinds_keep_parser_identity(): + parser_kind = "FriendDecl" + + assert parser_kind not in CLANG_KIND_MAP + assert CLANG_KIND_MAP.get(parser_kind, SemanticKind.NODE) is SemanticKind.NODE From fe77f96d1fdf0adce26365dc9bb7b0748f1d3371 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:16 +0200 Subject: [PATCH 07/19] Migrate Clang node matching to semantic metadata --- src/renaissance/integrations/clang/clang_ast_node.py | 4 ++-- .../integrations/clang/clang_json_ast_node.py | 4 ++-- src/renaissance/integrations/clang/cpp_utils.py | 10 ++++++++++ test/c_cpp/test_node_protocol_metadata.py | 8 ++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/renaissance/integrations/clang/clang_ast_node.py b/src/renaissance/integrations/clang/clang_ast_node.py index 40850a5d..b7d35148 100644 --- a/src/renaissance/integrations/clang/clang_ast_node.py +++ b/src/renaissance/integrations/clang/clang_ast_node.py @@ -8,7 +8,7 @@ from clang.cindex import Config, CursorKind, Index, TypeKind from clang.cindex import TranslationUnit as ClangCindexTranslationUnit -from renaissance.integrations.clang.cpp_utils import matches_kind +from renaissance.integrations.clang.cpp_utils import matches_node_kind from renaissance.integrations.clang.kinds import CLANG_KIND_MAP from renaissance.integrations.types import ( KIND_MAP, @@ -295,7 +295,7 @@ def _is_statement_or_declaration(self): @override def matches_kind(self, node: ASTNode) -> bool: - return matches_kind(self.ast_type, node.ast_type) + return matches_node_kind(self, node) def _derive_properties(self) -> dict[str, int | str]: result = {} diff --git a/src/renaissance/integrations/clang/clang_json_ast_node.py b/src/renaissance/integrations/clang/clang_json_ast_node.py index d19bc74b..c094ebb3 100644 --- a/src/renaissance/integrations/clang/clang_json_ast_node.py +++ b/src/renaissance/integrations/clang/clang_json_ast_node.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Any, Self, override -from renaissance.integrations.clang.cpp_utils import CPPUtils, matches_kind +from renaissance.integrations.clang.cpp_utils import CPPUtils, matches_node_kind from renaissance.integrations.clang.kinds import CLANG_KIND_MAP from renaissance.integrations.types import ( KIND_MAP, @@ -321,7 +321,7 @@ def _is_statement_or_declaration(self): @override @property def matches_kind(self, node: ASTNode) -> bool: - return matches_kind(self.ast_type, node.ast_type) + return matches_node_kind(self, node) @override @property diff --git a/src/renaissance/integrations/clang/cpp_utils.py b/src/renaissance/integrations/clang/cpp_utils.py index cec40b7e..ccf6de19 100644 --- a/src/renaissance/integrations/clang/cpp_utils.py +++ b/src/renaissance/integrations/clang/cpp_utils.py @@ -18,6 +18,16 @@ def matches_kind(mine, other) -> bool: ) +def matches_node_kind(mine, other) -> bool: + if mine.semantic_kind.value != "node" and other.semantic_kind.value != "node": + return mine.semantic_kind is other.semantic_kind + return matches_kind(mine.ast_type, other.ast_type) + + +def is_clang_kind(node, *parser_kinds: str) -> bool: + return node.parser_kind in parser_kinds + + class CPPUtils: # a set of cpp reserved keywords in reverse alphabetical order: RESERVED_KEYWORDS = { diff --git a/test/c_cpp/test_node_protocol_metadata.py b/test/c_cpp/test_node_protocol_metadata.py index bfc483ad..31f18804 100644 --- a/test/c_cpp/test_node_protocol_metadata.py +++ b/test/c_cpp/test_node_protocol_metadata.py @@ -1,6 +1,7 @@ from pathlib import Path from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode +from renaissance.integrations.clang.cpp_utils import is_clang_kind from renaissance.integrations.clang.kinds import CLANG_KIND_MAP from renaissance.syntax_tree.semantic_kind import SemanticKind @@ -24,3 +25,10 @@ def test_clang_specific_unknown_kinds_keep_parser_identity(): assert parser_kind not in CLANG_KIND_MAP assert CLANG_KIND_MAP.get(parser_kind, SemanticKind.NODE) is SemanticKind.NODE + + +def test_clang_parser_kind_predicate_preserves_specific_concepts(): + node = type("Node", (), {"parser_kind": "CXXConstructorDecl"})() + + assert is_clang_kind(node, "CXXConstructorDecl") + assert not is_clang_kind(node, "FunctionDecl") From a1ff63333b2c1e6889335594df04574067c2d3bd Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:18 +0200 Subject: [PATCH 08/19] Complete semantic matching for Tree-sitter patterns --- src/renaissance/integrations/tree_sitter/kinds.py | 5 +++++ src/renaissance/syntax_tree/match_finder.py | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/renaissance/integrations/tree_sitter/kinds.py b/src/renaissance/integrations/tree_sitter/kinds.py index a924952e..795a2da5 100644 --- a/src/renaissance/integrations/tree_sitter/kinds.py +++ b/src/renaissance/integrations/tree_sitter/kinds.py @@ -21,9 +21,14 @@ "for_statement": SemanticKind.LOOP, "while_statement": SemanticKind.LOOP, "return_statement": SemanticKind.RETURN, + "return": SemanticKind.RETURN, "import_statement": SemanticKind.IMPORT, + "import": SemanticKind.IMPORT, "import_declaration": SemanticKind.IMPORT, + "if": SemanticKind.CONDITIONAL, "class_definition": SemanticKind.CLASS, "class_declaration": SemanticKind.CLASS, "class_specifier": SemanticKind.CLASS, + "for": SemanticKind.LOOP, + "while": SemanticKind.LOOP, } diff --git a/src/renaissance/syntax_tree/match_finder.py b/src/renaissance/syntax_tree/match_finder.py index e2df6680..cdb4555f 100644 --- a/src/renaissance/syntax_tree/match_finder.py +++ b/src/renaissance/syntax_tree/match_finder.py @@ -19,10 +19,9 @@ def pattern_kind(node: NodeProtocol) -> PatternKind | None: value = getattr(node, "pattern_kind", None) if value is not None: return value - legacy_kind = getattr(node.ast_type, "__name__", "") - if legacy_kind == "MatchOne": + if node.parser_kind in {"MatchOne", "_MatchOne__"}: return PatternKind.MATCH_ONE - if legacy_kind == "MatchAll": + if node.parser_kind in {"MatchAll", "_MatchAll__"}: return PatternKind.MATCH_ALL return None @@ -35,7 +34,7 @@ def node_kinds_match(source: NodeProtocol, pattern: NodeProtocol) -> bool: and pattern.semantic_kind is not SemanticKind.NODE ): return source.semantic_kind == pattern.semantic_kind - return source.ast_type == pattern.ast_type + return source.parser_kind == pattern.parser_kind class Variant: From 863495d1f13a77135e5d57388c2d0d668f8c2fa2 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:21 +0200 Subject: [PATCH 09/19] Remove ast_type from node protocol --- src/renaissance/syntax_tree/node_protocol.py | 1 - test/syntax_tree/test_node_protocol.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/renaissance/syntax_tree/node_protocol.py b/src/renaissance/syntax_tree/node_protocol.py index 03fc7242..665d1969 100644 --- a/src/renaissance/syntax_tree/node_protocol.py +++ b/src/renaissance/syntax_tree/node_protocol.py @@ -8,7 +8,6 @@ class NodeProtocol(Protocol): """Structural interface consumed by generic syntax-tree algorithms.""" - ast_type: object parser_kind: str semantic_kind: SemanticKind properties: Mapping[str, Any] diff --git a/test/syntax_tree/test_node_protocol.py b/test/syntax_tree/test_node_protocol.py index 5d448cbb..eec81246 100644 --- a/test/syntax_tree/test_node_protocol.py +++ b/test/syntax_tree/test_node_protocol.py @@ -4,7 +4,6 @@ class FakeNode: - ast_type = object() parser_kind = "fake_node" semantic_kind = SemanticKind.NODE properties = {} @@ -22,7 +21,7 @@ def test_matcher_prefers_semantic_kind_over_legacy_type(): pattern = FakeNode() source.semantic_kind = SemanticKind.CALL pattern.semantic_kind = SemanticKind.CALL - source.ast_type = object() - pattern.ast_type = object() + source.parser_kind = "source_node" + pattern.parser_kind = "pattern_node" assert is_match(source, pattern) From a6f90f863ba353c68af6e95306a762ef69d20c59 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:23 +0200 Subject: [PATCH 10/19] Add predicate-oriented node finder --- src/renaissance/syntax_tree/ast_finder.py | 11 +++++++---- src/renaissance/syntax_tree/ast_processor.py | 3 +-- test/syntax_tree/test_node_finder.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 test/syntax_tree/test_node_finder.py diff --git a/src/renaissance/syntax_tree/ast_finder.py b/src/renaissance/syntax_tree/ast_finder.py index 4b8fa65a..6dbfd73b 100644 --- a/src/renaissance/syntax_tree/ast_finder.py +++ b/src/renaissance/syntax_tree/ast_finder.py @@ -1,7 +1,6 @@ import re from collections.abc import Callable, Iterator, Sequence -from renaissance.integrations.types import Type from renaissance.utils.ast_utils import traverse from .ast_node import ASTNode @@ -57,12 +56,16 @@ def __matches_kind(ast_node: ASTNode, kind: str | re.Pattern[str]) -> Iterator[A yield from ASTFinder.__matches_kind(child, pattern) -def find_ast_type(ast_node: NodeProtocol, kind: type[Type]) -> Sequence: - return [n for n in traverse(ast_node) if isinstance(n.ast_type(), kind)] +def find_nodes(ast_node: NodeProtocol, predicate) -> Sequence[NodeProtocol]: + return [node for node in traverse(ast_node) if predicate(node)] + + +def find_ast_type(ast_node: NodeProtocol, kind) -> Sequence: + return find_nodes(ast_node, lambda node: isinstance(node.ast_type(), kind)) def find_semantic_kind(ast_node: NodeProtocol, kind: SemanticKind) -> Sequence[NodeProtocol]: - return [n for n in traverse(ast_node) if n.semantic_kind is kind] + return find_nodes(ast_node, lambda node: node.semantic_kind is kind) def matches_kind(ast_node, kind: type[Type]) -> bool: diff --git a/src/renaissance/syntax_tree/ast_processor.py b/src/renaissance/syntax_tree/ast_processor.py index 445f7366..bcd7d970 100644 --- a/src/renaissance/syntax_tree/ast_processor.py +++ b/src/renaissance/syntax_tree/ast_processor.py @@ -2,7 +2,6 @@ from pathlib import Path import renaissance.syntax_tree.match_finder -from renaissance.integrations.types import Type from renaissance.syntax_tree import ASTNode from renaissance.syntax_tree.ast_factory import ASTFactory from renaissance.syntax_tree.ast_finder import ASTFinder, find_ast_type, find_semantic_kind @@ -79,7 +78,7 @@ def insert_after( def find_all(self, function: Callable[[ASTNode], Iterator[ASTNode] | bool]) -> Sequence[ASTNode]: return ASTFinder.find_all(self.__root_node, function) - def find_ast_type(self, kind: type[Type]) -> Sequence[ASTNode]: + def find_ast_type(self, kind) -> Sequence[ASTNode]: return find_ast_type(self.__root_node, kind) def find_semantic_kind(self, kind: SemanticKind) -> Sequence[NodeProtocol]: diff --git a/test/syntax_tree/test_node_finder.py b/test/syntax_tree/test_node_finder.py new file mode 100644 index 00000000..2de1c321 --- /dev/null +++ b/test/syntax_tree/test_node_finder.py @@ -0,0 +1,11 @@ +from renaissance.integrations.python.ast.rst_node import PythonRstNode +from renaissance.syntax_tree.ast_finder import find_nodes +from renaissance.syntax_tree.semantic_kind import SemanticKind + + +def test_find_nodes_accepts_protocol_predicate(): + root = PythonRstNode.load_from_text("def f():\n return 1\n") + + functions = find_nodes(root, lambda node: node.semantic_kind is SemanticKind.FUNCTION) + + assert len(functions) == 1 From b5e01cdda741497b0ee2f913263da872af7b458f Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:26 +0200 Subject: [PATCH 11/19] Migrate extractor examples to semantic kinds --- src/rejuvenation/refactor_with_nested_compositions.py | 6 +++--- src/renaissance/integrations/clang/kinds.py | 1 + src/renaissance/integrations/python/ast/extractor.py | 11 ++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/rejuvenation/refactor_with_nested_compositions.py b/src/rejuvenation/refactor_with_nested_compositions.py index b35fb06c..da0dd566 100644 --- a/src/rejuvenation/refactor_with_nested_compositions.py +++ b/src/rejuvenation/refactor_with_nested_compositions.py @@ -3,10 +3,10 @@ import textwrap from renaissance.integrations.clang import ClangASTNode, CPatternFactory -from renaissance.integrations.types import Call from renaissance.syntax_tree import ASTFactory, ASTRewriter, ASTShower -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import find_all +from renaissance.syntax_tree.semantic_kind import SemanticKind example_code = """ void f1(int a, int b, int c); @@ -82,7 +82,7 @@ def refactor_with_nested_compositions(args): ASTShower.show_node(pattern1[0], include_properties=True) # we only want to search the call expression as a pattern so it's searched using the kind - pattern2 = find_ast_type(pattern2, Call) + pattern2 = find_semantic_kind(pattern2, SemanticKind.CALL) # the replacement code strip indent is used to be agnostic to the indentation of the replacement pattern1replacement = textwrap.dedent(""" diff --git a/src/renaissance/integrations/clang/kinds.py b/src/renaissance/integrations/clang/kinds.py index 4fe84f83..5bc0c582 100644 --- a/src/renaissance/integrations/clang/kinds.py +++ b/src/renaissance/integrations/clang/kinds.py @@ -8,6 +8,7 @@ "FUNCTION_DECL": SemanticKind.FUNCTION, "CXXMethodDecl": SemanticKind.FUNCTION, "CallExpr": SemanticKind.CALL, + "CALL_EXPR": SemanticKind.CALL, "DeclRefExpr": SemanticKind.NAME, "IntegerLiteral": SemanticKind.LITERAL, "INTEGER_LITERAL": SemanticKind.LITERAL, diff --git a/src/renaissance/integrations/python/ast/extractor.py b/src/renaissance/integrations/python/ast/extractor.py index a40e4936..1ece57f9 100644 --- a/src/renaissance/integrations/python/ast/extractor.py +++ b/src/renaissance/integrations/python/ast/extractor.py @@ -3,6 +3,7 @@ import networkx as nx from renaissance.integrations.python.ast.rst_node import PythonRstNode +from renaissance.syntax_tree.semantic_kind import SemanticKind class PythonExtractor: @@ -17,17 +18,17 @@ def process(self, file: Path): self.graph.add_edge(folder, module_name, type="contains") for stmt in root: - match stmt.ast_type: - case "Import": + match stmt.semantic_kind: + case SemanticKind.IMPORT: self.graph.add_edge(module_name, stmt.name, type="include") - case "ImportFrom": + case _ if stmt.parser_kind == "ImportFrom": for alias in stmt.node.names: self.graph.add_edge(module_name, f"{stmt.node.module}.{alias.name}", type="include") - case "FunctionDef": + case SemanticKind.FUNCTION: self.graph.add_edge(module_name, f"{module_name}.{stmt.name}", type="definition") self.graph.add_node(f"{module_name}.{stmt.name}", properties="function") # TODO: convert #, stmt.properties) to graphml - case "ClassDef": + case SemanticKind.CLASS: self.graph.add_edge(module_name, f"{module_name}.{stmt.name}", type="definition") self.graph.add_node(f"{module_name}.{stmt.name}") # convert to args, stmt.properties) case _: From b884abeef8a8e8c93a1f938eb89b696192a5e363 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:28 +0200 Subject: [PATCH 12/19] Add predicate matching to refactor actions --- src/renaissance/syntax_tree/ast_finder.py | 4 +++ .../syntax_tree/ast_refactor_actions.py | 26 ++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/renaissance/syntax_tree/ast_finder.py b/src/renaissance/syntax_tree/ast_finder.py index 6dbfd73b..555c83f4 100644 --- a/src/renaissance/syntax_tree/ast_finder.py +++ b/src/renaissance/syntax_tree/ast_finder.py @@ -60,6 +60,10 @@ def find_nodes(ast_node: NodeProtocol, predicate) -> Sequence[NodeProtocol]: return [node for node in traverse(ast_node) if predicate(node)] +def matches_node(ast_node: NodeProtocol, predicate) -> bool: + return predicate(ast_node) + + def find_ast_type(ast_node: NodeProtocol, kind) -> Sequence: return find_nodes(ast_node, lambda node: isinstance(node.ast_type(), kind)) diff --git a/src/renaissance/syntax_tree/ast_refactor_actions.py b/src/renaissance/syntax_tree/ast_refactor_actions.py index 5bef5318..80f7b3ae 100644 --- a/src/renaissance/syntax_tree/ast_refactor_actions.py +++ b/src/renaissance/syntax_tree/ast_refactor_actions.py @@ -4,7 +4,7 @@ from renaissance.integrations.types import BogusType, Type -from .ast_finder import ASTFinder, matches_kind +from .ast_finder import ASTFinder, matches_kind, matches_node from .ast_node import ASTNode from .ast_processor import ASTProcessor from .match_finder import MatchFinder, PatternMatch @@ -13,6 +13,12 @@ from renaissance.integrations.clang.c_pattern_factory import CPPPatternFactory +def _kind_predicate(kind): + if callable(kind) and not isinstance(kind, type): + return kind + return lambda node: matches_kind(node, kind) + + class ASTRefactorActions: def __init__(self, processor: ASTProcessor, pattern_factory: CPPPatternFactory) -> None: self.processor = processor @@ -20,8 +26,10 @@ def __init__(self, processor: ASTProcessor, pattern_factory: CPPPatternFactory) self.replaced: set[int] = set() def replace_expr(self, name: str, replacement: str, kind: type[Type]): + kind_predicate = _kind_predicate(kind) + def test(n: ASTNode): - if (kind and matches_kind(n, kind)) and n.name == name: + if (kind and matches_node(n, kind_predicate)) and n.name == name: yield n [self.processor.replace(found.text.replace(found.name, replacement, 1), found) for found in self.processor.find_all(test)] @@ -33,10 +41,13 @@ def replace_name( kind: type[Type] = None, skip_kind: type[Type] = BogusType, ): + kind_predicate = _kind_predicate(kind) + skip_kind_predicate = _kind_predicate(skip_kind) + def matches_name(n1: ASTNode) -> bool: return ( - (not kind or ASTFinder.matches_kind(n1, kind)) - and (not skip_kind or not ASTFinder.matches_kind(n1, skip_kind)) + (not kind or matches_node(n1, kind_predicate)) + and (not skip_kind or not matches_node(n1, skip_kind_predicate)) and n1 and n1.name == name ) @@ -53,10 +64,13 @@ def replace_text( kind: type[Type] = None, skip_kind: type[Type] = BogusType, ): + kind_predicate = _kind_predicate(kind) + skip_kind_predicate = _kind_predicate(skip_kind) + def matches_text(n: ASTNode) -> bool: return ( - (not kind or ASTFinder.matches_kind(n, kind)) - and (not skip_kind or not ASTFinder.matches_kind(n, skip_kind)) + (not kind or matches_node(n, kind_predicate)) + and (not skip_kind or not matches_node(n, skip_kind_predicate)) and n is not None and n.text == text ) From 10d1206bc54fa8d315d7a0cef7e009e2fd14bbf4 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:30 +0200 Subject: [PATCH 13/19] Migrate compilation database example to semantic kinds --- src/rejuvenation/walk_compilation_database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rejuvenation/walk_compilation_database.py b/src/rejuvenation/walk_compilation_database.py index 94084e5e..1e14e36d 100644 --- a/src/rejuvenation/walk_compilation_database.py +++ b/src/rejuvenation/walk_compilation_database.py @@ -5,8 +5,8 @@ import targets from renaissance.integrations.clang import ClangASTNode, CompilationDatabase from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode -from renaissance.integrations.types import FunctionDef from renaissance.syntax_tree import ASTProcessor, ASTShower +from renaissance.syntax_tree.semantic_kind import SemanticKind def main(args): @@ -21,7 +21,7 @@ def main(args): ASTShower.show_node(atu, include_properties=True) # do something with the factory and atu ast_refactor = ASTProcessor(atu, factory, in_memory=True) - [print(n.text) for n in ast_refactor.find_ast_type(FunctionDef)] + [print(n.text) for n in ast_refactor.find_semantic_kind(SemanticKind.FUNCTION)] if __name__ == "__main__": From 261e51d3b172806959268e95283f73715cabcaec Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:32 +0200 Subject: [PATCH 14/19] Allow Clang factories to use predicates --- .../integrations/clang/c_pattern_factory.py | 12 +++++++++--- src/renaissance/integrations/clang/kinds.py | 2 ++ test/c_cpp/test_c_pattern_factory.py | 14 +++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/renaissance/integrations/clang/c_pattern_factory.py b/src/renaissance/integrations/clang/c_pattern_factory.py index d1e44746..9eaf8307 100644 --- a/src/renaissance/integrations/clang/c_pattern_factory.py +++ b/src/renaissance/integrations/clang/c_pattern_factory.py @@ -18,13 +18,19 @@ VariableDef, ) from renaissance.syntax_tree.ast_factory import ASTFactory -from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.ast_finder import find_ast_type, find_nodes from renaissance.syntax_tree.ast_node import ASTNode from renaissance.syntax_tree.ast_shower import ASTShower SHOW_NODE = False +def _matches_kind(node, kind) -> bool: + if isinstance(kind, type): + return isinstance(node.ast_type(), kind) + return kind(node) + + def derive_header_text(language: str, ref_node: ASTNode | None): # collect includes #defines and var decl from the refNode header = "\n" @@ -167,7 +173,7 @@ def create(self, text: str, kind: type[Type] = None) -> ASTNode: # print(self.header + text) root = self.factory.create_from_text(self.header + text, "test." + self.language) if kind: - return first(find_ast_type(root.children[-1], kind)) + return first(find_nodes(root.children[-1], lambda node: _matches_kind(node, kind))) return root def create_statement( @@ -207,7 +213,7 @@ def _create_body( # node of the specified kind body = first(find_ast_type(root.children[-1], CompoundStatement)).children - return list(n for n in body if n.is_part_of_translation_unit and first(find_ast_type(n, kind))) + return list(n for n in body if n.is_part_of_translation_unit and first(find_nodes(n, lambda node: _matches_kind(node, kind)))) def _create(self, text: str) -> ASTNode: atu = self.factory.create_from_text(text, "test." + self.language) diff --git a/src/renaissance/integrations/clang/kinds.py b/src/renaissance/integrations/clang/kinds.py index 5bc0c582..f66edd4d 100644 --- a/src/renaissance/integrations/clang/kinds.py +++ b/src/renaissance/integrations/clang/kinds.py @@ -18,7 +18,9 @@ "VAR_DECL": SemanticKind.DECLARATION, "ParmVarDecl": SemanticKind.PARAMETER, "BinaryOperator": SemanticKind.BINARY_OPERATION, + "BINARY_OPERATOR": SemanticKind.BINARY_OPERATION, "UnaryOperator": SemanticKind.UNARY_OPERATION, + "UNARY_OPERATOR": SemanticKind.UNARY_OPERATION, "IfStmt": SemanticKind.CONDITIONAL, "ForStmt": SemanticKind.LOOP, "WhileStmt": SemanticKind.LOOP, diff --git a/test/c_cpp/test_c_pattern_factory.py b/test/c_cpp/test_c_pattern_factory.py index d9b86a8d..0c9711e7 100644 --- a/test/c_cpp/test_c_pattern_factory.py +++ b/test/c_cpp/test_c_pattern_factory.py @@ -13,8 +13,9 @@ MatchOne, VariableDef, ) -from renaissance.syntax_tree import ASTShower +from renaissance.syntax_tree import ASTFactory, ASTShower from renaissance.syntax_tree.ast_finder import find_ast_type +from renaissance.syntax_tree.semantic_kind import SemanticKind class TestCPatternFactory: @@ -274,3 +275,14 @@ def test(self, _, factory, statement_text, expected_stmts, expected_refs): raw = node.signature assert_that(statement_text, starts_with(raw)) + + def test_create_statement_accepts_protocol_predicate(self): + factory = ASTFactory(ClangASTNode, []) + pattern_factory = CPatternFactory(factory) + + statement = pattern_factory.create_statement( + "a == 3;", + kind=lambda node: node.semantic_kind is SemanticKind.BINARY_OPERATION, + ) + + assert statement.semantic_kind is SemanticKind.BINARY_OPERATION From d0a544b1bf38b7fac487caed0c28e32ab6c89be7 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:33 +0200 Subject: [PATCH 15/19] Remove stale legacy finder annotation --- src/renaissance/syntax_tree/ast_finder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renaissance/syntax_tree/ast_finder.py b/src/renaissance/syntax_tree/ast_finder.py index 555c83f4..0e399099 100644 --- a/src/renaissance/syntax_tree/ast_finder.py +++ b/src/renaissance/syntax_tree/ast_finder.py @@ -72,5 +72,5 @@ def find_semantic_kind(ast_node: NodeProtocol, kind: SemanticKind) -> Sequence[N return find_nodes(ast_node, lambda node: node.semantic_kind is kind) -def matches_kind(ast_node, kind: type[Type]) -> bool: +def matches_kind(ast_node, kind) -> bool: return isinstance(ast_node.ast_type(), kind) From 4d6df106a2705655569e24cb9aea38a49409da0c Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:35 +0200 Subject: [PATCH 16/19] Document protocol architecture and supersede type hierarchy ADR --- .../architecture/adr/03_duck_typing.md | 4 ++++ .../architecture/adr/10_type_hierarchy.md | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/developer/architecture/adr/03_duck_typing.md b/docs/developer/architecture/adr/03_duck_typing.md index f3bcb65b..305f4473 100644 --- a/docs/developer/architecture/adr/03_duck_typing.md +++ b/docs/developer/architecture/adr/03_duck_typing.md @@ -39,6 +39,10 @@ expected by the consumers. - Provide adapter/wrapper helpers (see ADR 06) to normalize foreign node-like objects into the project's canonical node shape. +The current implementation exposes `NodeProtocol` as the canonical structural contract. Nodes also expose +`parser_kind` and `semantic_kind`; parser-specific mappings remain inside their integration. `PatternKind` is +separate from node classification and represents matcher behavior such as one-node and all-node placeholders. + ```python @runtime_checkable class NodeMatchProtocol(protocol): diff --git a/docs/developer/architecture/adr/10_type_hierarchy.md b/docs/developer/architecture/adr/10_type_hierarchy.md index 66c6b9a2..ed95b965 100644 --- a/docs/developer/architecture/adr/10_type_hierarchy.md +++ b/docs/developer/architecture/adr/10_type_hierarchy.md @@ -1,6 +1,6 @@ # 10 - Type Hierarchy -Status: Accepted +Status: Superseded Date: 2026-03-27 @@ -25,6 +25,10 @@ Authors: ## Context +> **Superseded by the protocol-based node model.** This ADR records the historical +> class-hierarchy approach. The current architecture uses structural node protocols, +> shared `SemanticKind` values, and parser-local kind maps instead. + the goal of this ADR is to establish a robust and maintainable type hierarchy for AST nodes use in the algorithms within the Renaissance project and across the languages. @@ -35,6 +39,18 @@ own lookup tables. A class hierarchy provides a more robust and idiomatic soluti ## Decision +The class-hierarchy decision in this ADR is no longer the target architecture. It is +retained as historical context for the compatibility code being removed incrementally. + +The current decision is documented in [ADR 03](03_duck_typing.md) and uses: + +- `NodeProtocol` for the structural node contract. +- `parser_kind` for the exact parser-provided kind. +- `semantic_kind` for shared cross-language concepts. +- parser-local maps and predicates for language-specific concepts. + +The remainder of this section describes the superseded approach. + - Follow the Doxygen definition for common node types (e.g., statement, expression, declaration) and use native Python - types for language-specific or non-standard node kinds. - Use the class hierarchy to determine the type of a node instead of string-based type name comparisons. From d5bdf66964311481047fe4ab470739ec8ae8b537 Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:09:36 +0200 Subject: [PATCH 17/19] Update ADR references for protocol architecture --- docs/developer/architecture/adr/08_pytest_suite.md | 2 +- docs/developer/architecture/adr/12_patterns_as_not_nodes.md | 2 +- docs/developer/architecture/adr/13_match_pattern.md | 2 +- docs/developer/architecture/adr/README.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/developer/architecture/adr/08_pytest_suite.md b/docs/developer/architecture/adr/08_pytest_suite.md index 2d41e864..0126173d 100644 --- a/docs/developer/architecture/adr/08_pytest_suite.md +++ b/docs/developer/architecture/adr/08_pytest_suite.md @@ -206,7 +206,7 @@ Negative: ## Related decisions - See ADR 09 (Property-based tests) for the use of Hypothesis alongside pytest. -- See ADR 10 (Type hierarchy) for the `SyntacticKind` taxonomy referenced in find-functionality tests. +- See ADR 03 (Duck typing for nodes) and the shared `SemanticKind` vocabulary for kind-based matching. - See ADR 12 (Patterns are not nodes) for the `Pattern` type used in matching tests. --- diff --git a/docs/developer/architecture/adr/12_patterns_as_not_nodes.md b/docs/developer/architecture/adr/12_patterns_as_not_nodes.md index cb4ee2ae..71dafa7c 100644 --- a/docs/developer/architecture/adr/12_patterns_as_not_nodes.md +++ b/docs/developer/architecture/adr/12_patterns_as_not_nodes.md @@ -126,7 +126,7 @@ Negative: - See ADR 01 (Children and properties) for the AST node structure that `Pattern.node` wraps. - See ADR 03 (Duck typing) for the protocol-based approach used by the matcher to accept `Pattern` objects. -- See ADR 10 (Type hierarchy) for the `SyntacticKind` taxonomy used as the `kind` field. +- See ADR 03 (Duck typing for nodes) for the protocol-based node shape and shared semantic kinds. --- diff --git a/docs/developer/architecture/adr/13_match_pattern.md b/docs/developer/architecture/adr/13_match_pattern.md index bda64d10..37457543 100644 --- a/docs/developer/architecture/adr/13_match_pattern.md +++ b/docs/developer/architecture/adr/13_match_pattern.md @@ -132,7 +132,7 @@ Negative: - See ADR 12 (Patterns are not nodes) for the `Pattern` / `SyntacticKind` design used by the pattern factory. -- See ADR 10 (Type hierarchy) for the node kind taxonomy referenced by find-by-kind functionality. +- See ADR 03 (Duck typing for nodes) for the protocol-based node shape and shared semantic kinds. - See ADR 08 (Test architecture) for the test requirements that cover matching, placeholders, and equivalent-code matching. - See ADR 11 (Parser with space and comment) for the lossless round-trip required by transformation tests. diff --git a/docs/developer/architecture/adr/README.md b/docs/developer/architecture/adr/README.md index 9920ae3e..735cb92f 100644 --- a/docs/developer/architecture/adr/README.md +++ b/docs/developer/architecture/adr/README.md @@ -24,7 +24,7 @@ The goal of ADR is to give the developer of new language AST for Renaissance a g | [07](07_package_management.md) | Use UV for package & environment management | Proposal | | [08](08_pytest_suite.md) | Test Architecture | Accepted | | [09](09_property_based_tests.md) | Property-Based Tests | Proposal | -| [10](10_type_hierarchy.md) | Type Hierarchy | Proposal | +| [10](10_type_hierarchy.md) | Type Hierarchy | Superseded | | [11](11_parser_with_space_and_comment.md) | Parser with Space and Comment | Proposal | | [12](12_patterns_as_not_nodes.md) | Patterns Are Not Nodes | Proposal | | [13](13_match_pattern.md) | Match Pattern | Proposal | From 821055cce954f37d3e3a124341b3f81bff22a48a Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 19:46:36 +0200 Subject: [PATCH 18/19] Remove unused ASTFinder import --- src/renaissance/syntax_tree/ast_refactor_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renaissance/syntax_tree/ast_refactor_actions.py b/src/renaissance/syntax_tree/ast_refactor_actions.py index 80f7b3ae..35bd196d 100644 --- a/src/renaissance/syntax_tree/ast_refactor_actions.py +++ b/src/renaissance/syntax_tree/ast_refactor_actions.py @@ -4,7 +4,7 @@ from renaissance.integrations.types import BogusType, Type -from .ast_finder import ASTFinder, matches_kind, matches_node +from .ast_finder import matches_kind, matches_node from .ast_node import ASTNode from .ast_processor import ASTProcessor from .match_finder import MatchFinder, PatternMatch From e94cabb7c68eb0375beb82f7a6b417a19f219eff Mon Sep 17 00:00:00 2001 From: Corvino Date: Thu, 10 Sep 2026 18:18:05 +0200 Subject: [PATCH 19/19] Fix legacy removal lint errors --- src/renaissance/integrations/clang/clang_ast_node.py | 2 -- src/renaissance/recipes/taut2pyunit.py | 1 - test/python/ast/test_python_pattern_factory.py | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/renaissance/integrations/clang/clang_ast_node.py b/src/renaissance/integrations/clang/clang_ast_node.py index b7d35148..07cffb4c 100644 --- a/src/renaissance/integrations/clang/clang_ast_node.py +++ b/src/renaissance/integrations/clang/clang_ast_node.py @@ -19,8 +19,6 @@ Definition, Literal, MacroDef, - MatchAll, - MatchOne, Statement, TranslationUnit, UnaryOperation, diff --git a/src/renaissance/recipes/taut2pyunit.py b/src/renaissance/recipes/taut2pyunit.py index c7b858f2..c928448e 100644 --- a/src/renaissance/recipes/taut2pyunit.py +++ b/src/renaissance/recipes/taut2pyunit.py @@ -6,7 +6,6 @@ import test_data.test_class as tst_class import test_data.test_insert as tst_insert from renaissance.recipes.python_refactoring import PythonRefactoring -from renaissance.syntax_tree.ast_finder import find_semantic_kind from renaissance.syntax_tree.match_finder import match_pattern from renaissance.syntax_tree.semantic_kind import SemanticKind diff --git a/test/python/ast/test_python_pattern_factory.py b/test/python/ast/test_python_pattern_factory.py index a389ac5d..4754c9db 100644 --- a/test/python/ast/test_python_pattern_factory.py +++ b/test/python/ast/test_python_pattern_factory.py @@ -19,8 +19,6 @@ ImplicitNode, ImportFrom, Literal, - MatchAll, - MatchOne, Number, Pass, Return,