gleam-lang/gleam · error

todo constants should not reach code generation

Error message

todo constants should not reach code generation

What it means

`Constant::Todo` (a `todo` placeholder used in constant position) is not translatable to JavaScript; the generator panics to signal it should never see one. Earlier compiler phases are expected to reject or replace todo constants before codegen.

Solutions

  1. Replace `todo` in the constant with a concrete value.
  2. Turn the constant into a function returning the value so `todo` is allowed in the body.
  3. If the code should already have been rejected with a proper error, report a compiler bug.

Example fix

// before
pub const api_key = todo
// after
pub const api_key = "changeme"
Defensive patterns

Strategy: validation

Validate before calling

// reject todo in constant position before codegen
if let Constant::Todo { location, .. } = constant {
    return Err(error_todo_constant(location));
}

Type guard

fn is_resolved_constant(c: &Constant) -> bool {
    !matches!(c, Constant::Todo { .. })
}

Try / catch

match std::panic::catch_unwind(|| codegen_constant(&c)) {
    Ok(doc) => doc,
    Err(_) => emit_internal_error_diagnostic(),
}

Prevention

When it happens

Trigger: A constant expression containing `todo` reaches the JavaScript backend's `constant_expression`, e.g. `pub const x = todo` where type inference failed to reject it first.

Common situations: Stubbing out a module constant with `todo` during prototyping; compiler regression letting a Todo constant slip through validation.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of gleam-lang/gleam@49f8762da5 (2026-09-14). Data as JSON: /api/errors/5521fa49ebc719da. Report an issue: GitHub.

Appendix: source

Thrown at compiler-core/src/javascript/expression.rs:2862

                | BinOp::MultFloat
                | BinOp::DivInt
                | BinOp::DivFloat
                | BinOp::RemainderInt => {
                    unreachable!("invalid constant operator made it to code generation")
                }

                BinOp::Concatenate => {
                    let left = self.constant_expression(arena, context, left);
                    let right = self.constant_expression(arena, context, right);
                    docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right]
                }
            },

            Constant::RecordUpdate { .. } => {
                panic!("record updates should not reach code generation")
            }
            Constant::Todo { .. } => {
                panic!("todo constants should not reach code generation")
            }
            Constant::Invalid { .. } => {
                panic!("invalid constants should not reach code generation")
            }
        }
    }

    fn constant_bit_array(
        &mut self,
        arena: &'doc DocumentArena<'a, 'doc>,
        segments: &'a [TypedConstantBitArraySegment],
        context: Context,
    ) -> Document<'a, 'doc> {
        self.tracker.bit_array_literal_used = true;
        let segments_array = array(
            arena,
            segments.iter().filter_map(|segment| {
                let value = match context {

View on GitHub (pinned to 49f8762da5)