10 - Type Hierarchy¶
Status: Superseded
Date: 2026-03-27
Authors:
- jinmin.hu@capgemini.com
- huub.joosten@capgemini.com
- luna.li@capgemini.com
- paul.nelissen@esi.nl
- pierre.vandelaar@tno.nl
Table of contents¶
- Context
- Decision
- Implementation notes
- Example
- Rationale
- Consequences
- Alternatives considered
- Related decisions
Context¶
Superseded by the protocol-based node model. This ADR records the historical class-hierarchy approach. The current architecture uses structural node protocols, shared
SemanticKindvalues, and parser-local kind maps instead.
The superseded hierarchy has now been removed. This is an intentional breaking
change: consumers must use NodeProtocol, SemanticKind, PatternKind,
parser_kind, and parser-local predicates. No compatibility wrapper is provided.
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.
AST node types are currently identified by string-based type names (e.g., re.compile(kind,
(?i)Function_?Decl".IGNORECASE)). This approach is fragile, hard to refactor, and requires every consumer to know the
exact string values. In addition, helper functions such as is_statementand is_expression must each maintain their
own lookup tables. A class hierarchy provides a more robust and idiomatic solution.
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 and uses:
NodeProtocolfor the structural node contract.parser_kindfor the exact parser-provided kind.semantic_kindfor 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.
- Helper functions such as
is_statementandis_expressionwill delegate toisinstancechecks, making them generic - and significantly simpler.
Implementation notes¶
- Define abstract base classes for the common node categories (e.g.,
statement,expression,declaration) following Doxygen terminology. - Language-specific node kinds that have no Doxygen equivalent are represented as native Python classes inheriting from the appropriate base.
- Replace all
node.type == "..."comparisons withisinstance(node.type, Statement)checks. - Implement helper predicates as thin wrappers:
def is_statement(node: AstNode) -> bool:
return isinstance(node.kind, Statement)
def is_expression(node: AstNode) -> bool:
return isinstance(node.kind, Expression)
# Usage
node.kind = Assignment(...)
assert is_statement(node) # True — no string comparison needed
assert not is_expression(node) # False
Example¶
Rationale¶
Using the class hierarchy to determine node types is more robust than string comparisons: it is refactor-safe,
IDE-navigable, and benefits from Python's isinstance semantics. Following Doxygen's well-known taxonomy for common
node categories ensures consistency with established conventions and makes the codebase accessible to developers
familiar with that terminology. Helper functions become trivially simple and generically applicable across all language
frontends.
Consequences¶
Positive:
- Eliminates fragile string-based type comparisons.
- Helper functions (
is_statement,is_expression, …) become simple, generic, and reusable. - IDE tooling (auto-complete, go-to-definition, refactoring) works naturally with class hierarchies.
- Consistent with Doxygen conventions for common node categories.
Negative:
- Requires an upfront investment to define the class hierarchy and migrate existing string comparisons.
- Deep inheritance trees can become hard to navigate if not kept shallow and well-documented.
Alternatives considered¶
- String-based type names — rejected because they are fragile, not refactor-safe, and require consumers to know exact string values.
- Enum-based type tags — rejected because they do not compose well with inheritance and still require explicit lookup tables in helper functions.
Related decisions¶
- See ADR 01 (Children and properties) for the overall AST node design that this hierarchy builds upon.
- See ADR 03 (Duck typing) for cases where structural subtyping with
Protocolis preferred over nominal subtyping.
Revision history:
- 2026-03-27: Converted to ADR template and clarified decision.