Changelog
All notable changes to Calor, organized by release.
[Unreleased]
[0.6.5] - 2026-06-30
Fixed
- The
TokenEconomicsbenchmark metric now reports the composite it computes (the discarded-composite bug, #668). The calculator computed a composite advantage — the geometric mean of the token, character, and line ratios — and then discarded it, reporting the raw token-count ratio only despite the metric being namedCompositeTokenEconomics. The category now reports the composite. The metric is deterministic, so its 95% CI equals its point estimate. This lifts the headline numbers — TokenEconomics from1.11×(token-only) to1.42×(composite) and overall from1.28×to1.32×— purely as a measurement correction, not a Calor improvement. The honest caveat stands: Calor still uses more raw tokens than C# on small programs (the§-sigil premium), but is more compact once character and line counts are included.
Changed
- The deferred v0.7
TokenEconomicsgate was recalibrated against the corrected metric. The old token-only target (lower-95%-CI > 1.122) is superseded by a composite gate of ≥ 1.40×, anchored to the measured 1.42× baseline. The public Token Economics metric page was corrected — it had previously and incorrectly reported the category as "C# wins".
[0.6.4] - 2026-06-16
Fixed
- Parser: an elided
§Ccall no longer steals the parent block's terminatingDedent. An elided§C{X}/§C{X} §A argcall that was the last statement of a function body followed by a sibling declaration (e.g. another§F) previously failed withCalor0100: Expected statement but found Func. Discovered while modernizing the TypeSystem sample for this release.
Added
- 7 new TokenEconomics benchmark fixtures broadening corpus coverage of v0.6.3 expression-context call elision and v0.6 bind-inference (parser, formatter, delegation, and aggregation shapes), plus two neutral controls. Honest note: the
TokenEconomicscategory scores raw token count only, and Calor's§-sigils cost more tokens than the equivalent C# on small programs, so these representative fixtures left the category at 1.11x — the v0.7TokenEconomicsgate remains open.
Internal
- The TypeSystem sample and the
04_option_resultE2E scenario were modernized to canonical v0.6.3 syntax — a legacy triply-nested§OK{§ARR…}array form (an artifact of mass C# → Calor conversion) was replaced with the canonical§OK value/§ERR "msg"short form, fixing the generated C# fromResult.Ok<object, string>(new object[]{…})toResult.Ok<int, string>(100).
Documentation
- v0.6 bind-inference RFC §7 — the
Calor0250open question is resolved. The diagnostic has always shipped as a hard error; the "promote warning→error in v0.7?" bullet was a stale carry-over and is now marked resolved, backed by a permanent corpus-clean test pinning zero bind-inference firings across the sample and benchmark corpus.
[0.6.3] - 2026-06-13
Added
calor fix --elide-call-closersbulk migrator. Newcalor fixsubcommand that rewrites existing.calrsource trees to the v0.6.x call-closer-elided form: zero-arg§C{X} §/C→§C{X}and same-line one-arg§C{X} §A arg §/C→§C{X} arg. Multi-line forms, named-arg (§A[name] x), multi-arg, andref/out/inarg modifiers are left untouched. Includes a canonical-emit safety net (re-parse the migrated source, re-emit both ASTs, drop the file's edits on any divergence) and--revert --log <file>for byte-for-byte round-trip. Mutually exclusive with--drop-structural-idsand--compact-ids; supports--dry-run.- LSP quick-fixes for strict bind-inference diagnostics
Calor0251/Calor0252/Calor0253. Each diagnostic now ships aSuggestedFixthat inserts the recommended:typeannotation right before the closing}of the bind's attribute block. Templates::Option<object>(for§NN),:object?(fornull),:Vec<object>/:Map<object, object>/ etc. arity-aware per the matched generic factory, and:f64(for ambiguous numeric). Calor.LanguageServer.DocumentState.Reanalyzenow runsBindValidationPassso strict-bind diagnostics (and their quick-fixes) surface in editors; previously the LSP only ran the lexer/parser/binder and these diagnostics were CLI-only.
Changed
- Expression-context
§Ccalls now elide§/Cby default for one-argument forms.CalorEmitter.Visit(CallExpressionNode)extends the v0.6.1 zero-arg elision and the v0.6.2 stmt-context one-arg elision to expression context:§C{target} arg(no§A, no§/C) when the argument is unnamed, the rendered first token is in theStartsWithExpressionStarterwhitelist, and we are not inside an inline-sibling context. - Strict bind-inference diagnostics
Calor0251/Calor0252/Calor0253are now default-on. These flag bindings that cannot infer a concrete type without an explicit:typeannotation: untyped§NN/null, well-known generic factory calls (Vec.empty,List.empty, etc.), and binary ops mixing integer and floating-point literals. Opt out for one release with--no-strict-bind-inference(CLI) orCompilationOptions.StrictBindInference = false(SDK).
Fixed
- Parser:
Calor0150no longer fires across sibling-statement boundaries. When the next expression-start token after a one-arg elided call is on a different line, it is a sibling statement, not an ambiguous second positional arg. - Emitter:
§LAMbody,§WITHtarget, and§LIST/§HSETelement emit sites now useAcceptInInlineSibling. These same-line sibling positions previously used rawnode.X.Accept(this), which could silently corrupt the AST after the one-arg expression-context elision landed.
[0.6.2] - 2026-06-10
Added
- Elision-aware TokenEconomics benchmark fixtures (
VoidSequence,LogPipeline,PairLogger) exercise the new statement-context§/Celision path. Corpus is now 210 programs.
Changed
- Statement-context
§Ccalls now elide§/Cby default (when safe).CalorEmitter.Visit(CallStatementNode)rewrites zero-argument calls as§C{target}and one-argument unnamed calls (with safe-prefix arguments) as§C{target} arg. Mirrors the v0.6.1 behavior for expression-context calls. Elision is gated byUseImplicitCallCloserand suppressed in inline-sibling contexts.
Removed
calor diagnoseCLI command removed. Deprecated in v0.5.x with a stated removal target of v0.6.0; v0.6.2 completes that deprecation. For machine-readable diagnostics use thecalor_checkMCP tool withaction: "diagnose"(orcalor_compilewith automatic fix application).
Fixed
- Contract verifier: class methods, user-defined types, and visibility preservation.
ContractSimplificationPassnow preservesVisibilityso the verifier reaches§MTmembers.ContractVerificationPasswalks class-method bodies. Z3 translator gained support for user-defined types and dot-path field access (a.b.c).
[0.6.1] - 2026-06-09
Changed
ConversionContext.UseImplicitCallClosernow defaults totrue(wasfalsein v0.6.0). The C# → Calor converter now elides§/Cfor zero-argument calls by default, producing more idiomatic Calor output. The opt-out (UseImplicitCallCloser = false) is preserved.
Compatibility
-
Calor source emitted by v0.6.1 may not parse on v0.6.0 or earlier
calortoolchains. The new default emits more zero-arg§Ccalls without explicit§/C. Sources that exercise the newly-fixed parser layouts (zero-arg§Cimmediately beforeDedent, or followed by a same-column sibling opener) will mis-parse on v0.6.0. To produce v0.6.0-compatible output from v0.6.1, use any of:- CLI single-file:
calor convert --explicit-call-closers <input.cs> - CLI project migration:
calor migrate --explicit-call-closers <path> - MCP
calor_convert/calor_migrate:"explicitCallClosers": true - SDK:
new ConversionOptions { UseImplicitCallCloser = false }
Round-trip (
C# → Calor → C#) remains semantic/structural; the intermediate.calris intentionally not byte-identical to v0.6.0 converter output unless the opt-out is used. - CLI single-file:
Fixed
- Parser:
§Cstandard form no longer swallows trailingDedent. Previously, a zero-arg§Cimmediately followed by the end of its enclosing block could corrupt the structural parse of the surrounding method or§IFbody. Now correctly distinguished from indent-aware block terminators. - Parser:
§Cno longer absorbs a same-column sibling structural opener on the next line. A sibling§IF/§MATCH/§NEWon the next line at the same indent is no longer silently absorbed as the call's inline argument. BothParseCallExpressionandParseCallStatementnow gate the inline-arg form on the candidate argument starting on the same source line as§C{target}. See Calls reference rule 6. - Emitter: zero-arg
§Cinside an inline-sibling context now keeps explicit§/C. With the new default, naively eliding§/Cfrom a zero-arg call emitted inside another call's§Achain or inside an array/§ARR/§ROW/§SALLOC/§IDX2Dinitializer caused silent AST corruption (e.g.M(A(), 2)round-tripping asM(A(2))). The emitter now tracks an inline-sibling-context counter; zero-arg§/Celision is suppressed whenever a call is emitted inside such a context. Top-level / leaf-position calls (binding initializers, return values) still elide as before. - Parser:
§Cstatement form now supports zero-arg implicit close before sibling statements. Previously a zero-arg§C{target}followed by a sibling statement on the next line at the same indent reportedCalor0100. The statement-form parser now recognizes the zero-arg implicit close when the current token is not§A,§/C,Dedent, orEof.
[0.6.0] - 2026-06-04
Added
§Ccall-closer elision (RFCv0.6-call-closer-elision). Expression-context§C{target}calls may now omit the trailing§/Cin two cases: (1) zero arguments —§B{n} §C{items.Count}is equivalent to§B{n} §C{items.Count} §/C; (2) exactly one inline argument (no§A) —§B{y} §C{Math.Abs} xis equivalent to§B{y} §C{Math.Abs} §A x §/C. The parser disambiguates nested elided calls (e.g.,§C{Foo.bar} §C{Baz.qux} y≡Foo.bar(Baz.qux(y))) by counting consecutive§/Cclosers relative to enclosing§Adepth. Trailing member access on inline arguments binds to the argument (§C{Identity} obj?.Length≡Identity(obj?.Length)); trailing member access on zero-arg calls binds to the call result (§C{Maybe}?.Length≡Maybe()?.Length). The explicit§A/§/Cform continues to parse unchanged. See Calls reference.Calor0150 AmbiguousCallContinuation— New diagnostic in the reservedCalor0150-0159range. Fires when an elided§Calready consumed one inline argument and is followed by either a second expression-start token or a§Atoken. The fix message recommends the explicit form.ConversionContext.UseImplicitCallCloseremitter flag. Opt-in property onMigration/ConversionContext. Whentrue,CalorEmitterelides§/Cfor zero-argument calls. Defaultfalsefor backward compatibility. One-argument elision is intentionally deferred to v0.6.1 pending context-aware tracking inside Lisp argument lists.§Bbind-inference formalization (RFCv0.6-bind-inference-formalization). The four supported§Bforms —§B{name}(requires initializer),§B{name} initializer(inferred),§B{name:type}(explicit, no initializer),§B{name:type} initializer(explicit wins) — and the binder's shallow inference rule are now documented at Bindings.Calor0250 BindRequiresTypeOrInitializer—§B{name}with no:typeand no initializer is now a hard error reported throughBindValidationPass. Replaces the pre-v0.6 silent fallback that boundxasINTand produced wrong-typed C# with no diagnostic.Calor0251/Calor0252/Calor0253strict-mode bind-inference diagnostics (opt-in via--strict-bind-inference). Three new diagnostics in theCalor0250-0259range, each silenced by an explicit:typeannotation, scheduled to become default-on in v0.7:Calor0251 BindCannotInferNullLiteral— fires on§B{x} §NNor§B{x} null.Calor0252 BindCannotInferGenericReturn— fires on§B{x} §C{Vec.empty} §/Cand other well-known generic factory targets.Calor0253 BindAmbiguousNumeric— fires on§B{x} (+ INT:0 FLOAT:0.0)— a binary op mixing integer and floating-point literal operands.
- Two new syntax-reference pages — Bindings and Calls, with full disambiguation tables, examples, and diagnostic catalogues.
- v6 compact stable identifiers (default).
IdGenerator.Generate(IdKind)now mints 12-char Crockford-lowercase compact IDs (f_7k9m2npqrstv). The legacy 26-char Crockford-uppercase ULID form (f_01J5X7K9M2NPQRSTABWXYZ12) remains accepted by the parser, validator, and migration tooling. Saves ~9.7 tokens per ID in agent-facing serialisations. calor fix --compact-ids <root>— bulk repo-wide migrator from legacy ULID payloads to v6 compact payloads. Two-pass design with deterministic compact derivation, within-file and cross-file collision detection, and byte-exact revert via--revert --log <file>. Idempotent on already-migrated source.IdValidatoraccepts both compact and legacy ULID forms. New predicatesIsCompactId,IsLegacyUlidId, andIsCanonicalId. NewCalor0821 LegacyUlidPayloaddiagnostic code reserved for the opt-in lint that flags ULID payloads.
Changed
Migration/CalorEmitter.Visit(CallExpressionNode)— Zero-argument calls in expression context now conditionally elide§/CwhenConversionContext.UseImplicitCallCloseristrue. Multi-argument and one-argument paths are unchanged in v0.6.0 (zero-arg-only elision) pending the v0.6.1 context-aware enablement.
Fixed
- Binder no longer silently defaults
§B{x}toINT. A§B{name}with neither a:typeannotation nor an initializer was silently treated asINTby the pre-v0.6 binder, producing wrong-typed C# with no diagnostic. v0.6 surfaces this asCalor0250.
[0.5.1] - 2026-06-03
Added
- Indent-only Calor source — Calor source is now indent-delimited end to end. The parser, migration emitter (
Migration/CalorEmitter), andcalor formatall produce/accept indent form as canonical. Closer-form structural tags (§/F{id},§/M{id},§/I{id},§/L{id}, …) have been removed from the emitter and rejected by the strict CLI compile path. The09_codegen_bugfixesself-test fixture was migrated alongside; round-trip emission stays byte-identical. calor --input … --output … --allow-legacy-closers— Escape hatch on the CLI compile path for users mid-migration. Default is strict (legacy closer form producesCalor0830);calor formatrewrites a file in canonical indent form.CompilationOptions.RejectLegacyClosers— Opt-in compilation flag; the CLI sets it totrueby default, other API surfaces (MSBuild<CompileCalor>, MCP tools, LSP) keep the lax default for now.calor fix --drop-structural-ids <root>— Bulk, mechanical, byte-reversible source rewriter that strips{id}from structural closing tags. Records every removal in amigration.log.jsonand supports--revert --log <file>to restore the original bytes exactly. Seedocs/cli/fix.md.Calor0820 LegacyStructuralId/Calor0830 LegacyCloserForm— Opt-in lints flagging legacy IDs on closing tags and legacy closer-form structural tags, withfixpatches pointing atcalor fixandcalor formatrespectively.- Optional closing-tag IDs everywhere — Structural closing tags (
§/M,§/F,§/AF,§/L,§/I,§/TR,§/CL,§/IN,§/PR,§/MT) may omit the trailing{id}block. Both forms continue to parse; the parser pairs closers with their nearest matching opener by structural nesting. - Phase 5 — Product docs migrated to indent-only syntax. README,
docs/, andwebsite/content/now teach indent-form Calor as the canonical surface; closer-form is mentioned only in legacy callouts that point atcalor fixfor migration.
Changed
- Lint no longer flags leading indentation or blank lines. With indent form now canonical, the two formatting lint rules introduced for the closer-form "agent-optimized" surface (leading whitespace, blank lines) have been removed from both
calor lintand thecheckMCP tool. Indentation and blank lines are first-class. - Benchmark metric calculators score indent form, not closer tags. The four heuristic calculators under
tests/Calor.Evaluation/Metrics/used to award credit for the presence of paired structural closing tags as a proxy for "scope boundaries are explicit". With indent form canonical, the dedent IS the scope-boundary signal, so those bonuses now reward indented body lines per structural opener. Net score magnitude is preserved.
Fixed
Migration/CalorEmitter.Visit(CatchClauseNode)— Catch filters now emit§WHEN(matching the token form the parser produces and the§WHENalready emitted by match-arm guards). Previously emitted a bare lowercaseWHENthat did not round-trip cleanly.MatchExpressionNodeas a§Bbinding initializer — match expressions used as binding initializers now emit indented§Karms relative to the enclosing block. Previously hardcoded 2/4-space indents could triggerCalor0099dedent errors when the binding lived inside a deeper function body.
[0.5.0] - 2026-04-22
Added
- Roslyn 5.3.0 upgrade — Migration pipeline now uses Roslyn 5.3.0 (C# 14 support), enabling conversion of modern C# files using lambda parameter modifiers,
outin lambda parameters, and other C# 13/14 features. LanguageVersion.Previewparse option — The C# parser now accepts the broadest possible C# syntax.
Changed
- Non-exhaustive match on
Option<T>/Result<T,E>is now an error — exhaustive match on known sum types is mandatory syntax (Calor0500). - Microsoft.CodeAnalysis.CSharp upgraded from 4.8.0 to 5.3.0 across all projects.
[0.4.9] - 2026-04-21
Added
- Cross-assembly IL analysis — Opt-in compile-time analysis that traces method calls through referenced .NET assemblies to discover effects not covered by manifests. Enabled via
<CalorEnableILAnalysis>true</CalorEnableILAnalysis>. Handles async state machines, iterator methods, delegate creation, and virtual dispatch. See Cross-Assembly IL Analysis guide. - Cross-module effect propagation — Multi-file Calor projects now verify effect contracts at file boundaries. A caller that invokes a public function declared in another module must declare that callee's effects. Violations produce
Calor0410; public functions without§Eproduce the newCalor0417warning. - Multi-file CLI —
calor --input a.calr --input b.calrcompiles multiple files and runs the cross-module effect pass across them. Single-file invocations are unchanged. - MSBuild cross-module enforcement — The
CompileCalortask runs the cross-module pass over every.calrin the project. Works correctly on warm builds via persistent per-module effect summaries in the build cache. - Effect summary cache (schema v2.0) — Each module's public function declarations, internal names, and call-site listings are cached alongside the content hash, so incremental builds retain complete cross-module coverage without re-parsing skipped files.
- Cross-Module Effect Propagation guide — Contract model, bare-name vs. qualified calls, incremental build semantics, CLI + MSBuild integration.
Changed
--inputoption in thecalorCLI now accepts multiple values.- Build state cache format bumped from
1.0to2.0— existing caches auto-invalidate on first build after upgrade. - Options hash includes
EffectKindenum shape — future enum changes automatically invalidate caches, preventing stale summaries from silently dropping effects.
[0.4.8] - 2026-04-20
Added
- Incremental compilation —
CompileCalorMSBuild task now owns all incremental logic with a two-level cache gate: (mtime, size) stat check then SHA256 content hash. Global invalidation on compiler DLL, options, effect manifest, or output directory changes. calor effects suggestCLI command — Analyzes Calor source files and generates a.calor-effects.suggested.jsonmanifest template for unresolved external calls. Supports--jsonfor agent consumption,--mergefor additive updates to existing manifests.- Shared
ExternalCallCollector— Extracted fromInteropEffectCoverageCalculator, extended to walk class methods and constructors. Resolves variable types via§NEWinitializer scanning. - Incremental build benchmark — Measures cold, warm (no changes), and warm (1 file changed) build times
- Effect manifests .NET ecosystem guide — ~170 covered types, resolution mechanics, custom manifest authoring, CLI tools
[0.4.7] - 2026-04-20
Added
- Static analysis for class members — The
--analyzeflag now examines methods, constructors, property accessors, operators, indexers, and event accessors (previously only top-level functions were analyzed) - Verification-gated reporting —
--analyzeonly reports proven findings by default (Z3-confirmed or constant analysis); use--all-findingsfor lower-confidence results - Taint hop-count tracking — Taint analysis tracks propagation steps; single-hop parameter-to-sink flows filtered by default to reduce false positives
- Bug pattern detection in class members — Division by zero, null dereference, integer overflow, off-by-one, path traversal, command injection, and SQL injection detection now covers all class member bodies
- Arity-aware overload resolution — Correct overload resolved by argument count, preventing wrong return types from flowing into Z3
- Constructor initializer binding —
: base()/: this()arguments visible to bug pattern checkers - 33 new unit tests for class member binding, scope, overloads, dataflow, and end-to-end analysis
- New
--all-findingsCLI flag for showing all analysis findings including inconclusive results - New static analysis documentation page
Fixed
- False positive elimination — Unhandled expression types return opaque expressions instead of literal zero, eliminating false division-by-zero reports
- this.field shadowing —
this.fieldresolves from class scope, not method scope - Throw-to-catch CFG edges — Throw statements inside try blocks now flow to catch blocks
- Assignment dataflow —
x = 1no longer reportsxas used before write
Validated
- 47 open-source projects scanned — 23 verified findings, ~90% true positive rate
- Real findings: ILSpy null dereferences, FluentFTP path traversal, ASP.NET Core path traversal
[0.4.6] - 2026-04-18
Added
- Effect system: .NET framework manifests — Tier B effect manifests for 30+ common .NET framework interfaces (ILogger, DbContext, HttpClient, ControllerBase, etc.)
- Effect system: ecosystem library manifests — Manifests for Serilog, Newtonsoft.Json, Dapper, MediatR, AutoMapper, FluentValidation, Polly
- Effect system: BCL manifest expansion — New manifests for System.Text.Json, Regex, Concurrent collections, Crypto types
- Effect system: variable type resolution — Enforcement pass resolves instance method calls via initializer tracking
- 95 new enforcement tests (210 total)
Fixed
- Effect system: unified resolver — Consolidated three parallel effect systems into a single manifest-based resolver
- Parser: compound effect codes — Fixed silent mis-parsing of chained compound codes
[0.4.5] - 2026-04-16
Fixed
- Restrict expression-only match case to lambda-only patterns
- PLIST nested call depth, call-as-pattern, OPTION compaction
Earlier Releases
See the full changelog on GitHub for versions prior to 0.4.5.