Skip to content

enums(rust): Tcl From<u16> fallback points to 'error' keyword, not ERROR sentinel #954

@dekobon

Description

@dekobon

Summary

The generated impl From<u16> for Tcl falls back to Self::Error — the Tcl
error keyword (discriminant 13, display "error") — instead of the
tree-sitter ERROR sentinel, which the generator renamed to Tcl::Error2
(discriminant 120, display "ERROR"). Every other language module's From<u16>
fallback correctly points to the ERROR sentinel.

Location

  • src/languages/language_tcl.rs:263-268 (generated From<u16> impl)
  • Root cause: enums/templates/rust.rs:30 (hardcoded Self::Error) +
    enums/src/common.rs:170-184 (ERROR-sentinel rename logic)

Evidence

The Tcl grammar (tree-sitter-tcl =1.1.0, vendored) has two distinct
Error-named symbols after camel-casing:

  • anon_sym_error = 13 -> the Tcl error command keyword (display "error")
  • the appended tree-sitter ERROR sentinel (display "ERROR")

The generator anticipates exactly this collision and renames the sentinel
(enums/src/common.rs:171-184):

// The tree-sitter ERROR sentinel is appended last. If the grammar already
// defines an "error" keyword that camel-cased to "Error", increment the
// counter so this sentinel gets a unique name (e.g. "Error2").
let error_name = match name_count.entry("Error".to_string()) { ... };
names.push((error_name, false, "ERROR".to_string()));

So the enum correctly emits both variants:

Error = 13,      // => "error"
...
Error2 = 120,    // => "ERROR"

But the From<u16> template body is hardcoded and never consumes
error_name (enums/templates/rust.rs:30):

num_traits::FromPrimitive::from_u16(x).unwrap_or(Self::Error)

Result in the generated file (language_tcl.rs:263-268):

impl From<u16> for Tcl {
    fn from(x: u16) -> Self {
        num_traits::FromPrimitive::from_u16(x).unwrap_or(Self::Error) // id 13, the keyword
    }
}

Cross-module comparison (every other module's fallback == its ERROR sentinel;
Tcl is the lone outlier):

language_python.rs   ERROR_sentinel=Error(id=274) fallback=Self::Error(id=274)
language_rust.rs     ERROR_sentinel=Error(id=355) fallback=Self::Error(id=355)
...
language_tcl.rs      ERROR_sentinel=Error2(id=120) fallback=Self::Error(id=13)  <-- wrong

Tcl is uniquely affected: it is the only vendored grammar whose keyword set
includes error (irules, the other Tcl dialect, has no such keyword and is
unaffected).

Expected Behavior

From<u16> for an out-of-range symbol id (e.g. the real tree-sitter ERROR
symbol) should map to the ERROR sentinel variant Tcl::Error2 (display
"ERROR"), matching the contract every other language module upholds.

Actual Behavior

Out-of-range / unknown symbol ids map to Tcl::Error — the Tcl error
command keyword (display "error") — silently misclassifying an
error/unknown node as a valid error command token.

Impact

Currently latent: no metric, checker, getter, or alterator pattern-matches
on Tcl::Error or Tcl::Error2 (both appear only in the generated
From<Tcl> for &str impl), and is_error uses tree-sitter's node.has_error()
directly rather than the enum. So there is no observable behavioral effect today.

The risk is forward-looking: any future code that matches Tcl::Error2 to
detect error nodes, or that relies on From<u16> of an unknown id yielding the
ERROR sentinel, would silently misbehave for Tcl only — the kind of
single-language divergence the mirrored-module design is meant to prevent. The
generated artifact already diverges from the invariant all sibling modules hold.

Generator-only fix

language_tcl.rs is machine-generated (Code generated; DO NOT EDIT.); do not
hand-edit it. The fix belongs in the enums/ generator/template.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions