From 541e7fb4559ee551bebebfcea93f7ab672daeef4 Mon Sep 17 00:00:00 2001 From: rmichaelthomas Date: Sat, 12 Sep 2026 00:06:07 -0700 Subject: [PATCH] fix(conformance): the corpus counts AST nodes, not every dataclass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `node_kinds` counted any dataclass it walked into. `ChooseBranch` is a plain dataclass here — not an `ASTNode` subclass — and an interface with no `kind` field in TypeScript. So both implementations already agree it is a container rather than a node, and counting it was the one thing making them disagree: two corpus cases failed in the port for a representation difference neither side considers meaningful. Recursion is unchanged; only the counting is narrowed. Two results move, both `choose if ... includes ...`. Found on the first run of the node comparison against the port, which is the comparison doing its job — in the other direction, on itself. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E6qcDj1e1dEYvc8jLnpGZy --- scripts/gen_conformance_corpus.py | 11 +++++++++-- tests/fixtures/conformance-0.18.1.json | 4 ---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/gen_conformance_corpus.py b/scripts/gen_conformance_corpus.py index ba28af6..2aaec24 100644 --- a/scripts/gen_conformance_corpus.py +++ b/scripts/gen_conformance_corpus.py @@ -40,7 +40,7 @@ from liminate.analyzer import analyze # noqa: E402 from liminate.lexer import LexError, tokenize # noqa: E402 -from liminate.parser import parse # noqa: E402 +from liminate.parser import ASTNode, parse # noqa: E402 from liminate.renderer import render # noqa: E402 from liminate.reorderer import reorder # noqa: E402 from liminate.result import LiminateResult, ResultStatus # noqa: E402 @@ -120,12 +120,19 @@ def node_kinds(node: object) -> list[str]: Kind names are the class name here and the `kind` field in TypeScript, and they already agree — `RequireNode` is `RequireNode` on both sides. + + An `ASTNode` subclass is a node; another dataclass is a container. The walk + recurses through both and counts only the first. `ChooseBranch` is the case + that made the distinction necessary: it is a plain dataclass here and an + interface with no `kind` field in TypeScript, so both implementations + already agree it is not a node — and counting it made them disagree. """ found: list[str] = [] def walk(value: object) -> None: if dataclasses.is_dataclass(value) and not isinstance(value, type): - found.append(type(value).__name__) + if isinstance(value, ASTNode): + found.append(type(value).__name__) for f in dataclasses.fields(value): walk(getattr(value, f.name)) elif isinstance(value, (list, tuple)): diff --git a/tests/fixtures/conformance-0.18.1.json b/tests/fixtures/conformance-0.18.1.json index b5b8289..3a4425c 100644 --- a/tests/fixtures/conformance-0.18.1.json +++ b/tests/fixtures/conformance-0.18.1.json @@ -188,8 +188,6 @@ { "canonical": "choose if tags includes urgent: show yes otherwise show no", "nodes": [ - "ChooseBranch", - "ChooseBranch", "ChooseNode", "ConditionNode", "NameRef", @@ -218,8 +216,6 @@ { "canonical": "choose if tags not includes urgent: show ok otherwise show skip", "nodes": [ - "ChooseBranch", - "ChooseBranch", "ChooseNode", "ConditionNode", "NameRef",