Objective
Make XTalk source code canonical, readable, and portable again.
The first phase is deliberately narrow:
- define and enforce the canonical XTalk source language;
- build a linter that explains which layer a form belongs to;
- clean the
xt.lang.* source files first;
- use JavaScript as the reference implementation;
- only then address Python, Dart, and other target-language differences.
The goal is to stop target-language constraints leaking into canonical XTalk source.
Why this is needed
XTalk currently has several overlapping layers:
XTalk source
-> grammar/staging and macro expansion
-> typed lowering/inference
-> target-specific rewrites
-> target emitter
The implementation already contains enough evidence that the language works across targets, but source code has accumulated compatibility forms such as:
- large amounts of explicit
x:get-key;
- explicit
x:not-nil? added for target truthiness constraints;
- block forms appearing in value positions;
- target-specific lambda and conditional workarounds;
- inconsistent handling of object fields, array destructuring, and snake_case.
The immediate problem is not that XTalk lacks features. It is that the canonical source layer is no longer clearly separated from the lower-level semantic IR and target adapters.
Current findings
1. Dot access already has the intended canonical shape
The typed lowerer already maps:
(. obj ["key"])
=> (x:get-key obj "key")
(. obj ["a"] ["b"])
=> (x:get-path obj ["a" "b"] nil)
See src/hara/typed/xtalk_lower.clj.
The grammar also defines . as the index/access operator, while x:get-key, x:get-path, and x:set-key are macro-level access operations in src/hara/common/grammar_xtalk.clj.
The canonical authoring rule should therefore be:
(. obj ["key"])
(. obj [key])
(. obj ["a"] ["b"])
x:get-key and x:get-path should remain valid semantic/internal forms, but should not be required for ordinary source code.
A redundant simple form such as:
should be lintable as:
provided it has no non-nil default and no special dynamic semantics.
2. Python currently exposes the access-layer leak
Live emission currently differs:
(. a ["key"])
=> a["key"]
(x:get-key a "key")
=> a.get("key")
The second form preserves XTalk nil-safe access semantics; the first does not necessarily do so.
This is exactly the kind of difference that must be resolved in the target rewrite layer rather than in canonical source. Dot access and x:get-key should first normalize to one semantic access representation, then Python should decide how to emit that representation.
3. Destructuring has two distinct canonical meanings
The typed environment already distinguishes:
as object/field destructuring, from:
as positional/array destructuring.
The canonical checker must preserve this distinction:
- set target -> object fields;
- vector target -> array positions;
- map target -> explicitly supported map destructuring;
- anything else -> diagnostic.
Object destructuring should normalize field names through the same field-key function used by record typing and map literals.
For example, these should canonicalize to the same external field:
:arr-name
:arr_name
arr-name
arr_name
The checker must also detect collisions after normalization, such as:
Local binding names and external field keys must remain separate concepts. A local binding may be emitted as arr_name, while its canonical source symbol remains arr-name.
4. Field-key normalization is partly implemented but not consistently enforced
hara.typed.xtalk-common/field-key already normalizes field keys to snake_case, and typed map inference already uses it.
However, normalization is not consistently applied to:
- dot access;
- explicit
x:get-key;
- object destructuring;
- emitted map keys;
- collision detection;
- target-specific access rewrites.
The canonical linter must make field-key normalization visible and deterministic.
There is also an important boundary to define: declared XTalk record fields should be normalized, but arbitrary external protocol keys such as clientInfo, protocolVersion, or other wire-format identifiers must not be silently rewritten. The implementation needs a clear distinction between canonical field keys and opaque external object keys.
5. Block/value context is not currently checked strongly enough
The grammar classifies if, cond, when, let, do, while, and for as :block.
:? is the value-oriented conditional.
The intended rule is:
(def a (if b 1 2)) ; illegal
(var a (if b 1 2)) ; illegal
(return (if b 1 2)) ; illegal
Use:
(def a (:? b 1 2))
(var a (:? b 1 2))
(return (:? b 1 2))
The current inference path can infer the branch union for if, but it does not carry enough value-position context to report this as a canonical error. Direct emission can therefore produce invalid target code.
The linter must add an explicit value/block context pass before target emission.
6. Dart truthiness belongs in the Dart adapter
The Dart rewrite already detects boolean-compatible expressions and wraps general XTalk truthiness using:
(and (x:not-nil? value)
(not= false value))
The canonical source should remain:
Dart-specific bool adaptation should happen in hara.model.spec-dart.rewrite.
Explicit x:not-nil? remains valid when the programmer means “present even if false”; it should not be required merely because Dart has stricter conditional typing.
7. Python lambda lifting already belongs in the target rewrite
Python already has a lambda compatibility predicate and a hoisting rewriter. Complex canonical lambdas can be lifted into named helper functions during Python rewriting.
That machinery should be retained and improved later. It should not force Python-shaped forms into canonical XTalk.
8. Python conditional emission needs parity coverage
Live Python emission currently turns:
into an and/or expression. That is unsafe when the true branch is falsey.
This should become a target conformance test and be fixed in the Python adapter after the canonical JavaScript contract is locked.
Canonical source contract for phase one
The first implementation should establish these forms as canonical:
;; object access
(. obj ["name"])
(. obj [key])
(. obj ["meta"] ["role"])
;; value conditionals
(:? test then else)
;; object destructuring
(var #{name age} m)
;; positional destructuring
(var [name age] arr)
;; ordinary lambdas
(fn [x] ...)
(fn:> [x] expression)
The following remain valid but are lower-level or exceptional:
(x:get-key obj key default)
(x:get-path obj path default)
(x:get-idx arr index default)
(x:not-nil? value)
(x:nil? value)
Linter design
The linter should operate on canonical XTalk source before target-specific rewriting.
Initial diagnostics:
XT001 block-in-value
XT002 redundant-get-key
XT003 access-kind-mismatch
XT004 field-key-collision
XT005 invalid-destructuring-target
XT006 field-key-normalization
XT007 unsupported-canonical-form
XT008 target rewrite required (informational only)
The linter should report:
- source location;
- original form;
- canonical interpretation;
- suggested rewrite where safe;
- layer responsible for the issue:
- source/canonical;
- typed semantic;
- target rewrite;
- emitter/runtime.
It should expose a programmatic API suitable for a Kimi guard, rather than putting the semantic logic entirely in clj-kondo. clj-kondo can provide syntax and lightweight style integration, while the Foundation XTalk analyzer should own semantic checks.
Staged implementation
Stage 0 — Canonical fixtures
Create a small golden corpus covering:
- dot access;
- nested paths;
- dynamic keys;
- defaults;
- object and array destructuring;
- snake_case normalization;
- field collisions;
- block-in-value errors;
:? truthiness;
- nil checks;
- lambdas.
Each fixture should record:
- source form;
- canonical normalized form;
- inferred type;
- JavaScript output.
Stage 1 — Build the linter
Add a source-level XTalk linter with:
- context-aware value/block checking;
- access normalization;
- destructuring classification;
- field-key normalization;
- redundant
x:get-key detection;
- structured diagnostics;
- JSON output for Kimi/editor integration.
Initially run it in warning mode over src-lang/xt.
Stage 2 — Clean xt.lang.*
Use the linter to clean src-lang/xt/lang/* first.
The target is not broad mechanical replacement. Every rewrite must preserve:
- nil/default behavior;
- dynamic versus static keys;
- object versus array access;
- truthy versus non-nil semantics.
JavaScript is the reference target for this stage.
Stage 3 — Lock JavaScript as the canonical reference
For every canonical fixture:
- source must pass the linter;
- typed analysis must succeed;
- JavaScript emission must be stable;
- runtime tests must pass.
This stage establishes what XTalk means before target languages are allowed to influence the source contract.
Stage 4 — Clean the remaining src-lang/xt source
Apply the same canonical rules to the rest of the XTalk source tree.
Generated test-lang/xtbench files should not be edited manually; regenerate them from canonical seeds where applicable.
Stage 5 — Cross-language conformance
Only after the canonical JavaScript layer is concrete. Lua is included alongside Python and Dart in this conformance stage:
- compare Python, Dart, Lua, and other targets against the canonical fixtures;
- classify failures as target rewrite, emitter, runtime, or actual language limitation;
- add target-specific rewrites without changing canonical source.
Stage 6 — Kimi guard and CI enforcement
Promote diagnostics gradually:
- canonical errors -> blocking;
- redundant syntax -> warning/autofix;
- target adaptation notices -> informational;
- unsupported target capability -> target-specific failure.
Definition of done
This epic is complete when:
- canonical XTalk source forms are documented and mechanically checked;
xt.lang.* no longer requires target-specific compatibility syntax;
- dot access and
x:get-key have one semantic meaning;
- object and array destructuring are unambiguous;
- field-key normalization and collisions are deterministic;
- block forms cannot silently appear in value positions;
- JavaScript is a stable reference implementation;
- Python/Dart differences are isolated in target adapters;
- the linter can explain whether a failure belongs to source, typing, rewriting, or emission;
- Kimi can run the linter as a guard before edits are accepted.
Initial implementation issue candidates
- Define canonical XTalk fixture format and JavaScript golden tests.
- Add context-aware canonical source walker.
- Add access normalization and redundant
x:get-key diagnostics.
- Add destructuring and field-key checks.
- Add block-in-value diagnostics.
- Add JSON linter API for Kimi.
- Clean
src-lang/xt/lang/*.
- Fix JavaScript canonical emission/runtime parity.
- Add Python/Dart/Lua conformance matrix after the canonical phase.
Update — 2026-07-22
Implemented and pushed in commit fd927be1.
- Added typed canonical lowering context for free XTalk forms.
- Local bindings are inferred before structural access lowering.
- Python now receives canonical x:get-key and emits nil-safe .get(...) access.
- Added lazy dependent-namespace type registration.
- Removed the obsolete +intrinsic-targets+ remapping.
- Added regressions for method-call preservation, runtime helper resolution, and canonical-stage behavior.
- Verified xt.event.base-route-test with 54 passing checks.
The remaining epic work is the staged cleanup of canonical xt.lang.* sources and later target conformance work.
Objective
Make XTalk source code canonical, readable, and portable again.
The first phase is deliberately narrow:
xt.lang.*source files first;The goal is to stop target-language constraints leaking into canonical XTalk source.
Why this is needed
XTalk currently has several overlapping layers:
The implementation already contains enough evidence that the language works across targets, but source code has accumulated compatibility forms such as:
x:get-key;x:not-nil?added for target truthiness constraints;The immediate problem is not that XTalk lacks features. It is that the canonical source layer is no longer clearly separated from the lower-level semantic IR and target adapters.
Current findings
1. Dot access already has the intended canonical shape
The typed lowerer already maps:
See
src/hara/typed/xtalk_lower.clj.The grammar also defines
.as the index/access operator, whilex:get-key,x:get-path, andx:set-keyare macro-level access operations insrc/hara/common/grammar_xtalk.clj.The canonical authoring rule should therefore be:
x:get-keyandx:get-pathshould remain valid semantic/internal forms, but should not be required for ordinary source code.A redundant simple form such as:
should be lintable as:
provided it has no non-nil default and no special dynamic semantics.
2. Python currently exposes the access-layer leak
Live emission currently differs:
The second form preserves XTalk nil-safe access semantics; the first does not necessarily do so.
This is exactly the kind of difference that must be resolved in the target rewrite layer rather than in canonical source. Dot access and
x:get-keyshould first normalize to one semantic access representation, then Python should decide how to emit that representation.3. Destructuring has two distinct canonical meanings
The typed environment already distinguishes:
(var #{name age} m)as object/field destructuring, from:
(var [name age] arr)as positional/array destructuring.
The canonical checker must preserve this distinction:
Object destructuring should normalize field names through the same field-key function used by record typing and map literals.
For example, these should canonicalize to the same external field:
The checker must also detect collisions after normalization, such as:
#{arr-name arr_name}Local binding names and external field keys must remain separate concepts. A local binding may be emitted as
arr_name, while its canonical source symbol remainsarr-name.4. Field-key normalization is partly implemented but not consistently enforced
hara.typed.xtalk-common/field-keyalready normalizes field keys to snake_case, and typed map inference already uses it.However, normalization is not consistently applied to:
x:get-key;The canonical linter must make field-key normalization visible and deterministic.
There is also an important boundary to define: declared XTalk record fields should be normalized, but arbitrary external protocol keys such as
clientInfo,protocolVersion, or other wire-format identifiers must not be silently rewritten. The implementation needs a clear distinction between canonical field keys and opaque external object keys.5. Block/value context is not currently checked strongly enough
The grammar classifies
if,cond,when,let,do,while, andforas:block.:?is the value-oriented conditional.The intended rule is:
Use:
The current inference path can infer the branch union for
if, but it does not carry enough value-position context to report this as a canonical error. Direct emission can therefore produce invalid target code.The linter must add an explicit value/block context pass before target emission.
6. Dart truthiness belongs in the Dart adapter
The Dart rewrite already detects boolean-compatible expressions and wraps general XTalk truthiness using:
The canonical source should remain:
(:? value then else)Dart-specific bool adaptation should happen in
hara.model.spec-dart.rewrite.Explicit
x:not-nil?remains valid when the programmer means “present even if false”; it should not be required merely because Dart has stricter conditional typing.7. Python lambda lifting already belongs in the target rewrite
Python already has a lambda compatibility predicate and a hoisting rewriter. Complex canonical lambdas can be lifted into named helper functions during Python rewriting.
That machinery should be retained and improved later. It should not force Python-shaped forms into canonical XTalk.
8. Python conditional emission needs parity coverage
Live Python emission currently turns:
into an
and/orexpression. That is unsafe when the true branch is falsey.This should become a target conformance test and be fixed in the Python adapter after the canonical JavaScript contract is locked.
Canonical source contract for phase one
The first implementation should establish these forms as canonical:
The following remain valid but are lower-level or exceptional:
Linter design
The linter should operate on canonical XTalk source before target-specific rewriting.
Initial diagnostics:
XT001 block-in-valueXT002 redundant-get-keyXT003 access-kind-mismatchXT004 field-key-collisionXT005 invalid-destructuring-targetXT006 field-key-normalizationXT007 unsupported-canonical-formXT008 target rewrite required(informational only)The linter should report:
It should expose a programmatic API suitable for a Kimi guard, rather than putting the semantic logic entirely in clj-kondo. clj-kondo can provide syntax and lightweight style integration, while the Foundation XTalk analyzer should own semantic checks.
Staged implementation
Stage 0 — Canonical fixtures
Create a small golden corpus covering:
:?truthiness;Each fixture should record:
Stage 1 — Build the linter
Add a source-level XTalk linter with:
x:get-keydetection;Initially run it in warning mode over
src-lang/xt.Stage 2 — Clean
xt.lang.*Use the linter to clean
src-lang/xt/lang/*first.The target is not broad mechanical replacement. Every rewrite must preserve:
JavaScript is the reference target for this stage.
Stage 3 — Lock JavaScript as the canonical reference
For every canonical fixture:
This stage establishes what XTalk means before target languages are allowed to influence the source contract.
Stage 4 — Clean the remaining
src-lang/xtsourceApply the same canonical rules to the rest of the XTalk source tree.
Generated
test-lang/xtbenchfiles should not be edited manually; regenerate them from canonical seeds where applicable.Stage 5 — Cross-language conformance
Only after the canonical JavaScript layer is concrete. Lua is included alongside Python and Dart in this conformance stage:
Stage 6 — Kimi guard and CI enforcement
Promote diagnostics gradually:
Definition of done
This epic is complete when:
xt.lang.*no longer requires target-specific compatibility syntax;x:get-keyhave one semantic meaning;Initial implementation issue candidates
x:get-keydiagnostics.src-lang/xt/lang/*.Update — 2026-07-22
Implemented and pushed in commit fd927be1.
The remaining epic work is the staged cleanup of canonical xt.lang.* sources and later target conformance work.