v0.22.0—Bounded nullability checks and practical .NET migration guidance.See what's new

convert

Convert a single file between C# and Calor.

Bash
calor convert <input> [options]

Overview

The convert command performs bidirectional conversion between C# and Calor:

  • C# → Calor: Convert .cs files to Calor syntax
  • Calor → C#: Convert .calr files to generated C#

The conversion direction is automatically detected from the input file extension.


Quick Start

Bash
# Convert C# to Calor
calor convert MyService.cs

# Convert Calor to C#
calor convert MyService.calr

# Specify output path
calor convert MyService.cs --output src/MyService.calr

# Include benchmark comparison
calor convert MyService.cs --benchmark

Options

OptionShortDefaultDescription
--output-oAuto-detectedOutput file path
--benchmark-bfalseInclude benchmark metrics comparison
--verbose-vfalseEnable verbose output
--explain-efalseExplain unsupported features in detail
--no-fallback—falseFail on unsupported constructs instead of emitting fallback TODOs
--validate—falseParse generated Calor before writing and report errors
--passthrough—falseWiden the set of C# members preserved as §CSHARP interop blocks. Since 0.16 calor convert already rescues a member it cannot convert this way; see What Changed in 0.16
--timeout-t0Conversion timeout in seconds; 0 disables it
--explicit-call-closers—falseEmit explicit §/C for every §C call (v0.6.0-compatible output).
--format—texttext or schema 2.0 json; there is no -f alias

Auto-Detected Output Paths

If --output is not specified:

InputOutput
MyFile.csMyFile.calr
MyFile.calrMyFile.g.cs

Supported Constructs

The converter validates generated C# before claiming a fully successful conversion. It reports semantic losses with file:line locations and exposes the same list as data.lossCount and data.losses[] in JSON. Unknown operators, patterns, compound assignments, namespace handling, and local functions no longer silently substitute a different construct.

When native conversion is impossible, the containing member is preserved as a §CSHARP interop block and counted — on the command line this happens by default, without --passthrough, which widens it further. "Conversion successful" is printed only when there are no recorded losses and requested validation passed.

C# ConstructCalor Equivalent
namespace§M{id:Name} module
class§CL{id:Name:vis} class
class method§MT{id:Name:vis} method
property§PROP{id:Name:vis:type} property
field§FLD{id:type:name} field
if/else if/else§IF{id} / §EI / §EL branches (end at dedent)
for loop§L{id:var:from:to:step}
while loop§WH{id}

What Changed in 0.17

0.16 got all 364 modules to parse. 0.17 is about what happens next: a module that parses can still stop at name binding, before any of the effect checking runs. At the 0.16 branch cut, 60 of the 364 stopped there. Now 40 do, and the number of modules the effect checker reaches went from 304 to 324.

Most of that came from two fixes to how the compiler resolves calls.

Overload resolution learned the conversions C# already allows. Deciding which overload you meant was comparing parameter types too literally. Passing a PropertyEnricher to a parameter typed ILogEventEnricher — an interface the class implements — was reported as "no overload matches", and so was passing a generic parameter T to a parameter typed as one of T's own constraints.

Member lookup learned about inheritance. Looking up a method asked the receiver type for its own declarations only — nothing about base classes, base interfaces, generic constraints, or System.Object. IValidator.GetType() was "no such member" because an interface has no base type to walk; so were MethodInfo.GetParameters(), declared on MethodBase, and IList<T>.Add(item), declared on ICollection<T>. Across the same three projects, the share of call sites the compiler can resolve went from 92.8% to 95.9%.

Four converter and emitter defects are fixed alongside them. A class using §EXT emitted unqualified calls to module functions, which the C# compiler then rejected; the same function referenced as a method group rather than called had the same problem; a var bound to a method call lost its type, and with it every member access on the result; and a named argument matching no parameter crashed the binder outright.

What Changed in 0.16

Converted code is only useful if the compiler can read it back. We test that on three real open-source projects — MediatR, Serilog, and FluentValidation — which come to 364 modules.

In 0.15, 59 of those 364 modules came out of the converter as Calor the compiler could not parse. Long lambdas were written at the wrong indent, calls split across lines came out wrong, and a few other patterns tripped it. In 0.16 all 364 parse.

One more thing is fixed alongside those: when the converter could not work out a lambda parameter's type, it used to write ?, which the compiler cannot read — it gave up on the rest of that method. It now treats the parameter as untyped, and reads the type from the Func<...> or Action<...> the lambda is handed to. Between that fix and the parsing fixes above, the number of modules where the converter crashed part-way through dropped from 73 to 1 across the same three projects.

There is one change to what you get back. calor convert now often rescues a member it could not convert into working Calor, by wrapping the original C# in a §CSHARP block rather than sinking the whole file. That means converted output can contain §CSHARP blocks even when you did not pass --passthrough, and the report tells you how many. Partly converted beats nothing converted, but treat each block as work still to do.

The rescue is not a guarantee. It fires when the generated Calor fails to parse or fails to round-trip back to C#; output that parses but that Calor's own compiler then rejects still fails the conversion, with nothing written and exit 1. --passthrough does not change that outcome.


Benchmark Comparison

Use --benchmark to see how the Calor version compares to C#:

Bash
calor convert PaymentService.cs --benchmark

This is a local source-pair comparison, not the published 217-program dashboard. Keep its metric output with the conversion report rather than assuming it reproduces the website headline.


Exit Codes

CodeMeaning
0Conversion completed; inspect the loss report to distinguish fully native from preserved interop
1Missing input, unsupported route without fallback, timeout, or conversion failure

See Also