nandi/code-transpiler-rust-frontend-findingspublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/code-transpiler-rust-frontend-findings.git
git clone ssh://git@rickub.com/nandi/code-transpiler-rust-frontend-findings.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

README.md

Rust → Nim in Code-Transpiler: what actually happens

A reproducible bug report for tarekwasfy01/Code-Transpiler,
which lists rust as a supported source language.

Summary: Rust cannot be used as a source language, and the CLI does not say so.
Most real Rust fails in the lexer. The rest parses into a tree containing no
functions, and the CLI writes an empty-but-valid target file and exits 0.

Nothing here is a criticism of the project's R-derived core, which works. The
issue is specifically the Rust frontend, and the silent failure that hides it.

Reproduce

./repro.sh

Clones upstream, builds cmd/r2many, runs the minimal cases through the CLI,
then re-runs them through a prober that reports which UAST node kinds the
frontend actually produced. Needs Go 1.26+ and network access. Recorded output:
EXPECTED.md.

What was tried first

A random real crate: base16ct 1.0.0 — 613 lines, pure Rust, no_std, no
dependencies. Five of its six source files failed to parse. The sixth, which
contains three functions, transpiled to this complete Nim program:

{
}

For the minimal cases in cases/, the emitted Nim file is even smaller: one
byte, a newline. Exit code 0 in both situations.

Full transcript: outputs/base16ct-1.0.0-transcript.txt.

The four defects

0. // line comments are not supported

internal/backend/parser.go:157

This tokenizer recognises # comments only, alongside R operators (<<-,
%in%, [[). A Rust // becomes two division operators:

expected expression near "/"

cases/00-comment.rs and
cases/00-comment-fails.rs differ by exactly one
comment line, and only the second fails. Every file in a real crate has
comments, base16ct included — doc comments alone account for much of why it
never reaches the later stages.

1. ' is always a string opener, so lifetimes break the lexer

internal/matrixir/lexical.go:261

if c == '"' || c == '\'' || c == '`' {

<'a> and Formatter<'_> are consumed as unterminated char literals. The
delimiter pairing in AnalyzeTokenStructure then desyncs, which is why the
reported error is a bracket complaint at an offset that always turns out to be
a lifetime. Case: cases/04-lifetime.rs.

This one looks genuinely small to fix: for source == "rust", treat ' as a
literal only when it matches '\?.', and emit a lifetime token otherwise.

2. The Rust parser is the R parser

internal/backend/frontend_fact_parser.go:387

func (p *factParser) parseIf() (ParsedNode, error) {
	p.next()
	if _, e := p.expect(tokLParen, ""); e != nil {   // requires  if ( ... )

This is a second, separate tokenizer from the one in defect 1 — the two stages
disagree — but both are R's. Parenthesised conditions are mandatory, so Rust's if cond { } can never
parse — it fails with expected "" near "<first token of cond>". Case:
cases/02-if-block.rs. Macro invocations are likewise
unhandled (cases/03-macro.rs).

The emitted semantic contract is hardcoded R for a file declared as Rust:

"language_profile": "rust",
"value_model": "tagged_dynamic_binary64",
"index_base": 1,
"type_contract": { "truth": "r_compatible", "ownership": "unknown" }

3. fn is never recognised as a declaration

This is the one that yields empty output. Node kinds produced for two
equivalent programs, via prober/main.go:

input function nodes
cases/05-equivalent.Rf <- function(a) {…} FUNCTION:1, CLOSURE:1, function:5, parameter:2
cases/01-trivial.rsfn f(a: i32) -> i32 {…} nonecall:2, CALL:1

fn f(a: i32) is parsed as a call to f, with fn left as a stray
identifier. The tree holds no functions, so the Nim emitter correctly emits
nothing. Note that case 01 reports no parse error — it is the silent path.

The silent-failure path

-runtime defaults to true. The fallback catches the parse failure, writes a
syntactically valid empty program, and exits 0. The machinery to report this
already exists and is correct when asked:

-native      strict native frontend for "rust" is not implemented
-no-runtime  1/1 translations failed: [{nim DIRECT_NATIVE_UNAVAILABLE (stage=direct)}]
(default)    exit 0, empty file

Refusing to emit a program with zero functions when the input declared some
would turn a silent wrong answer into an honest error, without touching the
frontend at all.

Scope of a real fix

Defects 0 and 1 are patches. Defects 2 and 3 are not: there is no Rust frontend to
repair. Supporting Rust means writing one — items, generics, lifetimes,
impl/traits, match, macros, ? — and lowering it into a semantic model
that isn't R-shaped, since the type contract is R throughout. That is
comparable in size to the existing ~1,400-line R frontend, and probably larger.

Contents

cases/     minimal inputs, one per defect, plus the R control
prober/    drop-in cmd/dbg that prints UAST node-kind counts
outputs/   the base16ct run: transcript and the one emitted .nim
repro.sh   clone, build, run everything

prober/main.go imports internal/backend, so it only compiles from inside
the upstream tree; repro.sh copies it to cmd/dbg for you.

Upstream is MIT. No upstream or crate source is vendored here — repro.sh
fetches what it needs.