BoundaryML/baml · error

PlainDateTime.to_plain_time: expected PlainDateTime instance

Error message

PlainDateTime.to_plain_time: expected PlainDateTime instance

What it means

This Rust panic occurs inside PlainDateTime.to_plain_time when the value passed is not an actual PlainDateTime object instance allocated by the VM. The implementation calls vm.as_instance() and .expect()s it to succeed; any other Value (null, number, string, plain object) trips the panic instead of returning a catchable error.

Source

Thrown at baml_language/crates/bex_vm/src/package_baml/time.rs:335

        };
        let days = i128::try_from(&*civil)
            .ok()
            .map(|n| n.div_euclid(NANOS_PER_DAY))
            .and_then(|d| i64::try_from(d).ok())
            .ok_or_else(|| {
                invalid_argument(
                    "PlainDateTime.to_plain_date: value is outside the supported year range"
                        .to_string(),
                )
            })?;
        Ok(copy::time::PlainDate { _days: days }.to_value(vm))
    }

    fn to_plain_time(vm: &mut BexVm, plaindatetime: &Value) -> Value {
        let civil = {
            let instance = vm
                .as_instance(plaindatetime)
                .expect("PlainDateTime.to_plain_time: expected PlainDateTime instance");
            view::time::PlainDateTime { instance }._nanoseconds()
        };
        // Euclidean modulo one day is exact regardless of the bigint's
        // magnitude or sign, and the result always fits in an i64.
        let day = num_bigint::BigInt::from(NANOS_PER_DAY);
        let time_ns = ((&*civil % &day) + &day) % &day;
        let time_ns = i64::try_from(time_ns).expect("time-of-day fits in i64");
        copy::time::PlainTime {
            _nanoseconds: time_ns,
        }
        .to_value(vm)
    }

    fn year(plaindatetime: &view::time::PlainDateTime<'_>) -> Result<i64, VmRustFnError> {
        Ok(i64::from(
            civil_datetime(&plaindatetime._nanoseconds(), "PlainDateTime.year")?.year(),
        ))
    }

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Pass only values produced by PlainDateTime construction APIs (constructors, parse, arithmetic results).
  2. Check the value's type/class before calling to_plain_time().
  3. If inputs come from user data, normalize strings via PlainDateTime.parse first.
  4. Report a bug if a genuine PlainDateTime instance triggers this (instance table corruption).

Example fix

// before
let t = "2024-01-01T10:00:00".to_plain_time(); // string, panics
// after
let t = PlainDateTime.parse("2024-01-01T10:00:00").to_plain_time();
Defensive patterns

Strategy: type-guard

Validate before calling

fn is_plain_datetime(v: &Value, vm: &BexVm) -> bool {
    vm.as_instance(v).map(|i| i.class() == "PlainDateTime").unwrap_or(false)
}

Type guard

fn as_plain_datetime<'a>(vm: &'a BexVm, v: &Value) -> Option<InstanceRef<'a>> {
    vm.as_instance(v).ok().filter(|i| i.class() == "PlainDateTime")
}

Try / catch

// BAML side: guard before call
if v is PlainDateTime { v.to_plain_time() } else { Error("expected PlainDateTime") }

Prevention

When it happens

Trigger: Calling the BAML to_plain_time() method with a non-PlainDateTime argument: a null, an int, a string, a plain object, or a Zoned/PlainTime of another class.

Common situations: Passing a datetime that was deserialized as a string or struct instead of a PlainDateTime instance; calling the method on the wrong type after a refactor; JSON round-trips that lose instance identity.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/b3bf7dd6e6559b98. Report an issue: GitHub.