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
- Pass only values produced by PlainDateTime construction APIs (constructors, parse, arithmetic results).
- Check the value's type/class before calling to_plain_time().
- If inputs come from user data, normalize strings via PlainDateTime.parse first.
- 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
- Always obtain PlainDateTime values from constructors or parse, never from raw JSON.
- Add a type check before calling date/time conversion methods.
- Keep datetimes as instances across serialization boundaries (or re-parse strings on load).
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.Prompt._data must contain baml_builtins2::PromptAst
- TimeZoneOffset.from_duration: expected Duration instance
- array access should be either map or array.
- sys_op callee must resolve to a statically-known global func
- expected jump instruction at index {instruction_idx}
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/b3bf7dd6e6559b98.
Report an issue: GitHub.