8.3 KiB
Sprint 162 Plan: Fix Class Emission in All Language Generators
Context
A/B test run on 2026-02-25 (see docs/ab_test_ast_vs_language_first_2026-02-25.md)
exposed that whetstone_run_pipeline Python→C++ emitted an empty namespace when
given a Python class as input. A full audit of all 8 language generators revealed
the root cause is systemic and identical across every language:
visitModule never calls getChildren("classes").
Every generator's visitModule iterates variables and functions only. Parsers
correctly populate the "classes" role during parsing — ClassDeclaration nodes
are in the AST — but the module walk skips them entirely. Classes are silently
dropped from all generated output across all 8 languages.
The visitClassDeclaration implementations are mostly already written and correct:
| Language | visitClassDeclaration |
Line range | Status |
|---|---|---|---|
| Python | Yes | PythonGenerator.h:535–562 | Good |
| Rust | Yes | RustGenerator.h:427–444 | Good — struct + impl |
| Go | Yes | GoGenerator.h:412–425 | Good — struct + receivers |
| Java | Yes | JavaGenerator.h:447–483 | Good — handles abstract/extends |
| JavaScript | Yes | JavaScriptGenerator.h:454–469 | Good — ES6 class syntax |
| TypeScript | Yes (inherited from JS) | — | Good |
| Elisp | Yes | ElispGenerator.h:497–513 | Good — cl-defstruct |
| C++ | No | CppGenerator.h | Missing entirely |
The fix for 7 languages is adding getChildren("classes") iteration to
visitModule — approximately 3 lines each. C++ additionally needs
visitClassDeclaration written from scratch.
Java and JavaScript have a secondary issue: their visitModule reconstructs
classes from function-name heuristics instead of the AST. That heuristic path
must be replaced with the real getChildren("classes") iteration.
This sprint fixes all 8 generators in one pass. After this sprint, any language can emit a class from a parsed AST, and cross-language class transpilation (e.g. Python → C++, Python → Rust) works end-to-end.
Goals
- Add
getChildren("classes")iteration tovisitModulein all 8 generators - Write
visitClassDeclarationfor C++ (the only language missing it entirely) - Replace the function-name heuristic in Java and JavaScript
visitModulewith real AST class iteration - End-to-end: the exact Python PriorityQueue from the A/B test must produce
compilable output in C++, Rust, and Go via
run_pipeline
Steps
Step 1854: Fix visitModule in Python, Rust, Go, Elisp generators (12 tests)
For each of these four generators, add a loop after the existing functions
iteration in visitModule:
for (auto& cls : node->getChildren("classes")) {
result += dispatchGenerate(cls);
}
No other changes — visitClassDeclaration already works correctly in all four.
Tests (12, 3 per language): Python class round-trips through parse→generate,
Rust class produces struct + impl block, Go class produces type X struct
with method receivers, Elisp class produces cl-defstruct — and for each,
a module containing both a function and a class emits both (function not lost,
class not lost).
Step 1855: Fix visitModule in Java and JavaScript generators (10 tests)
Java and JavaScript use a function-name heuristic to reconstruct classes
(partitioning function names to guess class membership) instead of reading
getChildren("classes"). Replace the heuristic path with the real AST iteration.
For both generators, visitModule should iterate getChildren("classes")
using dispatchGenerate(cls) — same pattern as Step 1854.
The existing visitClassDeclaration implementations (Java:447–483,
JavaScript:454–469) remain unchanged.
TypeScript inherits from JavaScript and requires no separate change.
Tests (10): Java class produces correct public class X { } syntax, Java class
with extends emits extends, Java abstract class emits abstract, JavaScript
class produces ES6 class X { } syntax, JavaScript class with superclass emits
extends, TypeScript class works via inheritance, Java module with function +
class emits both, JavaScript module with function + class emits both, no heuristic
path remains (grep for the old partition logic returns nothing),
regression: existing function-only modules still work correctly.
Step 1856: Write visitClassDeclaration for C++ generator (12 tests)
C++ is the only language missing visitClassDeclaration entirely. Add it to
CppGenerator.h.
The implementation must emit a complete header-only C++ class:
structfor pure data classes (no methods other than comparators)classwithpublic:section for classes with methods- Fields with inferred types: Python
str→std::string,int→int,bool→bool,float→double, unknown →auto /* TODO: specify type */ - Method declarations with
autoparameter/return types when not inferrable - Required
#includedirectives (<string>when string fields present, etc.)
Also add getChildren("classes") to C++ visitModule (same 3-line pattern
as Step 1854).
Tests (12): empty class emits valid C++ class X { };, class with string field
emits std::string, class with int field emits int, class with unknown field
emits auto with TODO comment, class with methods emits method declarations,
visitModule emits both functions and classes, #include <string> present when
string field exists, pragma once present at module level, C++ class output
parseable by Whetstone's own CppParser, class with __init__-style constructor
emits C++ constructor, class with only fields uses struct, class with methods
uses class with public:.
Step 1857: End-to-end — Python PriorityQueue → C++, Rust, Go (10 tests)
Create editor/src/Sprint162EndToEnd.h.
Uses the exact Python source from the failing 2026-02-25 A/B run. Runs it
through run_pipeline targeting C++, Rust, and Go. All three must produce
non-empty, parseable output containing the class definitions.
Expected C++ output: contains class WorkItem, class PriorityQueue,
enqueue, dequeue, std::string, int.
Expected Rust output: contains struct WorkItem, impl PriorityQueue,
fn enqueue, fn dequeue.
Expected Go output: contains type WorkItem struct, type PriorityQueue struct,
method receivers for enqueue and dequeue.
Tests (10): C++ output non-empty, C++ contains "class WorkItem", C++ contains "class PriorityQueue", C++ contains "std::string", Rust output non-empty, Rust contains "struct WorkItem", Rust contains "impl PriorityQueue", Go output non-empty, Go contains "type WorkItem struct", Go contains "type PriorityQueue struct".
Step 1858: Sprint 162 Integration Summary (8 tests)
Create editor/src/Sprint162IntegrationSummary.h.
Record: steps_completed=5 (1854–1858), root_cause="visitModule skips
getChildren(classes) in all 8 generators", languages_fixed=8,
new_code_written="CppGenerator::visitClassDeclaration only — all other
visitClassDeclaration implementations pre-existing", ab_test_reference=
"docs/ab_test_ast_vs_language_first_2026-02-25.md", success=true.
Full sprint regression: steps 1854–1858 pass. All existing function-level pipeline tests pass (no regression).
Tests (8): struct constructable, steps_completed==5, languages_fixed==8, root_cause non-empty, all 8 generators emit classes, Python round-trip works, C++ round-trip works, success==true.
Architecture Gate
- No new files required except Sprint162IntegrationSummary.h and the C++ visitClassDeclaration (added inline to CppGenerator.h, ≤ 600 line limit)
- No new vcpkg dependencies
- The 3-line
getChildren("classes")fix must be applied consistently across all generators — no one-off solutions - Existing function-level output must be unchanged in all generators (regression tests in Step 1858 enforce this)
- Type inference for C++ must be conservative: unknown →
auto+ comment, never silently wrong - Java/JavaScript heuristic class reconstruction must be fully removed, not left alongside the new path
Note on Scope
This sprint does NOT require writing new visitClassDeclaration implementations
for Python, Rust, Go, Java, JavaScript, TypeScript, or Elisp. Those already exist
and are correct. The entire fix for those 7 languages is the getChildren("classes")
loop in visitModule. Only C++ requires new implementation work.