diff --git a/baml_language/crates/baml_base/src/attr.rs b/baml_language/crates/baml_base/src/attr.rs index b39e7f3e2b7..58c4b19ebb5 100644 --- a/baml_language/crates/baml_base/src/attr.rs +++ b/baml_language/crates/baml_base/src/attr.rs @@ -70,6 +70,16 @@ pub struct TyAttr { } impl TyAttr { + /// The all-`Unset` attribute set — a `const` equal to + /// [`TyAttr::default`], usable where a `&'static TyAttr` is needed (e.g. + /// the `ty_family!`-generated `attr()` accessor's fallback for variants + /// that carry no attribute of their own). + pub const EMPTY: TyAttr = TyAttr { + sap_parse_without_null: TyAttrValue::Unset, + sap_pending_never: TyAttrValue::Unset, + sap_in_progress_never: TyAttrValue::Unset, + }; + /// Return the canonical names of all attributes that are `Set`. pub fn attr_names(&self) -> Vec<&'static str> { let mut names = Vec::new(); diff --git a/baml_language/crates/baml_compiler2_emit/src/analysis.rs b/baml_language/crates/baml_compiler2_emit/src/analysis.rs index 5d107f79a6a..496363cb8e2 100644 --- a/baml_language/crates/baml_compiler2_emit/src/analysis.rs +++ b/baml_language/crates/baml_compiler2_emit/src/analysis.rs @@ -1978,7 +1978,7 @@ mod tests { kind: StatementKind::Assign { destination: Place::Local(Local(0)), value: Rvalue::Array( - baml_type::TyTemplate::Concrete(baml_type::RuntimeTy::unknown()), + baml_type::TyTemplate::from(baml_type::RealizedTy::unknown()), vec![ Operand::copy_local(target), Operand::Constant(Constant::Int(1)), diff --git a/baml_language/crates/baml_compiler2_emit/src/emit.rs b/baml_language/crates/baml_compiler2_emit/src/emit.rs index 33dfab6f3cc..3efc0ce23d4 100644 --- a/baml_language/crates/baml_compiler2_emit/src/emit.rs +++ b/baml_language/crates/baml_compiler2_emit/src/emit.rs @@ -14,7 +14,7 @@ use baml_compiler2_mir::{ BasicBlock, BinOp, BlockId, Constant, IndexKind, IntrinsicOp, Local, LogLevel, MirFunctionBody, Operand, Place, Rvalue, StatementKind, Terminator, UnaryOp, }; -use baml_type::{RuntimeTy, TyTemplate, TypeName}; +use baml_type::{RealizedTy, RuntimeTy, TyTemplate, TypeName}; use bex_vm_types::{ BinOp as VmBinOp, Bytecode, CmpOp, ConstValue, Function, FunctionCaptureProps, FunctionKind, FunctionOrigin, GlobalIndex, Instruction, Object, ObjectIndex, ObjectPool, @@ -449,6 +449,23 @@ impl<'ctx, 'obj> StackifyCodegen<'ctx, 'obj> { .or_else(|| self.class_object_indices.get(tn.name().as_str()).copied()) } + /// Enum-object index for an enum type name, mirroring + /// [`Self::class_object_index_for_type_name`]. Used by `is ` to test + /// enum identity (`ConstValue::Object`) rather than the shared `ENUM` tag, + /// which cannot distinguish two enum types (`Color` vs `Status`). + fn enum_object_index_for_type_name(&self, tn: &TypeName) -> Option { + let full_name = tn.render_dotted(false); + self.enum_object_indices + .get(&full_name) + .copied() + .or_else(|| { + self.enum_object_indices + .get(tn.display_name().as_str()) + .copied() + }) + .or_else(|| self.enum_object_indices.get(tn.name().as_str()).copied()) + } + /// Resolve the type of a MIR Place by walking from the root local through projections. fn resolve_place_type(&self, place: &Place) -> Option { match place { @@ -1476,12 +1493,8 @@ impl<'ctx, 'obj> StackifyCodegen<'ctx, 'obj> { // before draining the entries, so push key first, value // second — mirroring the `alloc_map` helper. Omitting // these tags makes the VM read the entry keys as types. - unwrap_infallible( - self.load_type(&TyTemplate::Concrete(RuntimeTy::string())), - ); - unwrap_infallible( - self.load_type(&TyTemplate::Concrete(RuntimeTy::unknown())), - ); + unwrap_infallible(self.load_type(&TyTemplate::from(RealizedTy::string()))); + unwrap_infallible(self.load_type(&TyTemplate::from(RealizedTy::unknown()))); self.emit(Instruction::AllocMap(2)); // 7. Restore call-site span and emit SendEvent @@ -3191,120 +3204,163 @@ impl PullSink for StackifyCodegen<'_, '_> { } fn is_type(&mut self, ty_template: &TyTemplate) -> Result<(), Self::Error> { - // Helper: emit IsType for a concrete RuntimeTy leaf. + let emit_false = |this: &mut Self| { + this.emit(Instruction::Pop(1)); + let idx = this.add_constant(ConstValue::Bool(false)); + let inst = this.emit(Instruction::LoadConst(idx)); + this.set_operand(inst, OperandMeta::Const("false".to_string())); + }; + // Hand the whole template to the VM's value matcher + // (`type_match::value_matches_template`) via a raw `ConstValue::Type`: + // it resolves the template's frame refs against `frame.type_args` and + // relates *invariantly* at generic-argument positions — the element- and + // arg-discriminating check a coarse type tag cannot express (`int[]` ≠ + // `string[]`, `map` ≠ `map`, a realized `T[]`). + let emit_structural = |this: &mut Self, template: &TyTemplate| { + let c = this.add_constant(ConstValue::Type(template.clone())); + let inst = this.emit(Instruction::IsType(c)); + this.set_operand(inst, OperandMeta::Const(template.to_string())); + }; + // A template position is a match-any hole (`_`) when it is a bare + // `Wildcard` — the only leaf that matches any type at its slot. + let is_match_any = |t: &TyTemplate| matches!(t, TyTemplate::Wildcard); + match ty_template { // ── Class check ────────────────────────────────────────────────── - TyTemplate::Class(tn, type_args_templates) => { - // Generic class instantiation with TypeArgRef leaves or - // concrete-but-parametric (e.g. Foo). Use the - // ClassWithTypeArgs constant so the VM can compare args. + // Every class (monomorphic `Foo`, concrete `Foo`, or generic + // `Foo`) is a `Class` template. Non-empty args → `ClassWithTypeArgs` + // so the VM compares each arg; empty args → class-pointer identity. + TyTemplate::Class(tn, type_args_templates, _) => { let class_name_str = tn.display_name(); - if let Some(class_obj_idx) = self.class_object_index_for_type_name(tn) { + let Some(class_obj_idx) = self.class_object_index_for_type_name(tn) else { + emit_false(self); + return Ok(()); + }; + if type_args_templates.is_empty() { + let c = + self.add_constant(ConstValue::Object(ObjectIndex::from_raw(class_obj_idx))); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand(inst, OperandMeta::Const(class_name_str.to_string())); + } else { let c = self.add_constant(ConstValue::ClassWithTypeArgs { class_obj: ObjectIndex::from_raw(class_obj_idx), type_args_templates: type_args_templates.clone(), }); let inst = self.emit(Instruction::IsType(c)); self.set_operand(inst, OperandMeta::Const(format!("{class_name_str}<...>"))); - } else { - self.emit(Instruction::Pop(1)); - let idx = self.add_constant(ConstValue::Bool(false)); - let inst = self.emit(Instruction::LoadConst(idx)); - self.set_operand(inst, OperandMeta::Const("false".to_string())); } - return Ok(()); } - TyTemplate::Concrete(ty) => { - // ── Class (concrete) ───────────────────────────────────────── - let maybe_class = match ty { - RuntimeTy::Class(tn, ty_args, _) => Some((tn, Some(ty_args.as_slice()))), - RuntimeTy::TypeAlias(tn, _) => Some((tn, None)), - _ => None, - }; - if let Some((tn, ty_args_opt)) = maybe_class { - let class_name_str = tn.display_name(); - if let Some(class_obj_idx) = self.class_object_index_for_type_name(tn) { - match ty_args_opt { - Some(ty_args) if !ty_args.is_empty() => { - // Concrete generic class, e.g. Foo: emit - // ClassWithTypeArgs with Concrete templates. - let type_args_templates: Vec = ty_args - .iter() - .map(|t| TyTemplate::Concrete(t.clone())) - .collect(); - let c = self.add_constant(ConstValue::ClassWithTypeArgs { - class_obj: ObjectIndex::from_raw(class_obj_idx), - type_args_templates, - }); - let inst = self.emit(Instruction::IsType(c)); - self.set_operand( - inst, - OperandMeta::Const(format!("{class_name_str}<...>")), - ); - } - _ => { - // Monomorphic class or TypeAlias: fast pointer-identity path. - let c = self.add_constant(ConstValue::Object( - ObjectIndex::from_raw(class_obj_idx), - )); - let inst = self.emit(Instruction::IsType(c)); - self.set_operand( - inst, - OperandMeta::Const(class_name_str.to_string()), - ); - } + + // ── Containers ─────────────────────────────────────────────────── + // A list/map whose element positions are all match-any holes is + // exactly the coarse "any list" / "any map" check, so the cheap type + // tag suffices — and this preserves an erased `T[]` / `_[]` pattern's + // "any list" semantics. When an element carries a discriminating type + // the tag would conflate `int[]` with `string[]`, so route the whole + // template through the structural matcher instead. + TyTemplate::List(elem, _) if is_match_any(elem) => { + let c = self.add_constant(ConstValue::Int(baml_type::typetag::LIST)); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand(inst, OperandMeta::Const(ty_template.to_string())); + } + TyTemplate::Map { key, value, .. } if is_match_any(key) && is_match_any(value) => { + let c = self.add_constant(ConstValue::Int(baml_type::typetag::MAP)); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand(inst, OperandMeta::Const(ty_template.to_string())); + } + + // ── Structural (value matcher) ─────────────────────────────────── + // Element/key/value discriminates, a bare frame reference (`T`, + // `T[]`), or a union that may carry one: the VM value matcher. The + // deprecated `TypeArgRefOrWildcard` (B-634 dispatch-guard tolerance) + // routes here too — `substitute` resolves it to the same frame slot + // as `TypeArgRef`, and the matcher's covariant top-level relation + // gives it the subtype-or-wildcard semantics it needs. + #[expect( + deprecated, + reason = "TypeArgRefOrWildcard is a live dispatch-guard template variant until type erasure is removed" + )] + TyTemplate::List(..) + | TyTemplate::Map { .. } + | TyTemplate::TypeArgRef(_) + | TyTemplate::TypeArgRefOrWildcard(_) + | TyTemplate::Union(..) => emit_structural(self, ty_template), + + // ── Function signatures ────────────────────────────────────────── + // Every function template — realized, frame-referencing, or holey — + // keeps the legacy coarse FUNCTION-tag check ("is it a callable"). + // + // FIXME(function-type-matching): signature-precise matching through + // the value matcher is blocked on the empty-`throws` convention + // mismatch: a function *type* writes "never throws" as `never`, + // while a function *value*'s reconstructed signature writes it as + // `void` (see `bex_vm`'s `function_object_ty`), and the canonical + // covariant throws relation has no bridge (`void <: never` is + // false) — so a structural test would constant-false every + // never-throwing closure. Unify the convention first, then route + // hole-free signatures through the matcher (which already applies + // contravariant params / covariant return correctly). + TyTemplate::Function { .. } => { + let c = self.add_constant(ConstValue::Int(baml_type::typetag::FUNCTION)); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand(inst, OperandMeta::Const(ty_template.to_string())); + } + + // A bare wildcard is the erased/unrepresentable fallback — an + // unresolved `Self` or associated projection lowered to a hole. Keep + // it constant-false rather than over-matching every value; faithful + // `Self` and projection lowering land in later units. + TyTemplate::Wildcard => emit_false(self), + + // Everything else keeps its existing coarse check. + other => { + // A fully-realized leaf (primitive, enum, alias, literal, …): + // class-pointer identity for a `TypeAlias`, otherwise its type + // tag. The only non-realized template reaching here is an + // associated projection (Unit-5 work), which has no + // representable check yet. + if let Ok(realized) = <&RealizedTy>::try_from(other) { + if let RealizedTy::TypeAlias(tn, _) = realized { + if let Some(class_obj_idx) = self.class_object_index_for_type_name(tn) { + let c = self.add_constant(ConstValue::Object(ObjectIndex::from_raw( + class_obj_idx, + ))); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand( + inst, + OperandMeta::Const(tn.display_name().to_string()), + ); + } else { + emit_false(self); + } + } else if let RealizedTy::Enum(tn, _) = realized { + // Enum-pointer identity: `is Color` tests the value's enum + // object, so it discriminates `Color` from `Status` — the + // shared `ENUM` type tag cannot. Falls back to constant-false + // if the enum object is absent (e.g. an unreferenced enum). + if let Some(enum_obj_idx) = self.enum_object_index_for_type_name(tn) { + let c = self.add_constant(ConstValue::Object(ObjectIndex::from_raw( + enum_obj_idx, + ))); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand( + inst, + OperandMeta::Const(tn.display_name().to_string()), + ); + } else { + emit_false(self); } + } else if let Some(tag) = realized_type_tag(realized) { + let c = self.add_constant(ConstValue::Int(tag)); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand(inst, OperandMeta::Const(realized.to_string())); } else { - self.emit(Instruction::Pop(1)); - let idx = self.add_constant(ConstValue::Bool(false)); - let inst = self.emit(Instruction::LoadConst(idx)); - self.set_operand(inst, OperandMeta::Const("false".to_string())); + emit_false(self); } - return Ok(()); - } - - // ── Primitive type tags ─────────────────────────────────────── - let type_tag = match ty { - RuntimeTy::Int { .. } => Some(baml_type::typetag::INT), - RuntimeTy::Bigint { .. } => Some(baml_type::typetag::BIGINT), - RuntimeTy::String { .. } => Some(baml_type::typetag::STRING), - RuntimeTy::Bool { .. } => Some(baml_type::typetag::BOOL), - RuntimeTy::Null { .. } => Some(baml_type::typetag::NULL), - RuntimeTy::Float { .. } => Some(baml_type::typetag::FLOAT), - RuntimeTy::Enum(..) => Some(baml_type::typetag::ENUM), - RuntimeTy::List(..) => Some(baml_type::typetag::LIST), - RuntimeTy::Map { .. } => Some(baml_type::typetag::MAP), - RuntimeTy::Function { .. } => Some(baml_type::typetag::FUNCTION), - RuntimeTy::Uint8Array { .. } => Some(baml_type::typetag::UINT8ARRAY), - RuntimeTy::Literal(lit, _, _) => Some(match lit { - baml_base::Literal::Int(_) => baml_type::typetag::INT, - baml_base::Literal::Bigint(_) => baml_type::typetag::BIGINT, - baml_base::Literal::Float(_) => baml_type::typetag::FLOAT, - baml_base::Literal::String(_) => baml_type::typetag::STRING, - baml_base::Literal::Bool(_) => baml_type::typetag::BOOL, - }), - _ => None, - }; - - if let Some(tag) = type_tag { - let c = self.add_constant(ConstValue::Int(tag)); - let inst = self.emit(Instruction::IsType(c)); - self.set_operand(inst, OperandMeta::Const(ty.to_string())); } else { - self.emit(Instruction::Pop(1)); - let idx = self.add_constant(ConstValue::Bool(false)); - let inst = self.emit(Instruction::LoadConst(idx)); - self.set_operand(inst, OperandMeta::Const("false".to_string())); + emit_false(self); } } - // ── Other templates (Array, Optional, Union, Map) ───────────────── - // These don't arise from pattern matching today — fall back to false. - _ => { - self.emit(Instruction::Pop(1)); - let idx = self.add_constant(ConstValue::Bool(false)); - let inst = self.emit(Instruction::LoadConst(idx)); - self.set_operand(inst, OperandMeta::Const("false".to_string())); - } } Ok(()) } @@ -3440,6 +3496,32 @@ impl StackEffectSink for StackifyCodegen<'_, '_> { } } +/// The coarse `IsType` type tag for a realized leaf type, or `None` for a type +/// with no representable tag (classes take the pointer-identity path instead). +fn realized_type_tag(ty: &RealizedTy) -> Option { + match ty { + RealizedTy::Int { .. } => Some(baml_type::typetag::INT), + RealizedTy::Bigint { .. } => Some(baml_type::typetag::BIGINT), + RealizedTy::String { .. } => Some(baml_type::typetag::STRING), + RealizedTy::Bool { .. } => Some(baml_type::typetag::BOOL), + RealizedTy::Null { .. } => Some(baml_type::typetag::NULL), + RealizedTy::Float { .. } => Some(baml_type::typetag::FLOAT), + RealizedTy::Enum(..) => Some(baml_type::typetag::ENUM), + RealizedTy::List(..) => Some(baml_type::typetag::LIST), + RealizedTy::Map { .. } => Some(baml_type::typetag::MAP), + RealizedTy::Function { .. } => Some(baml_type::typetag::FUNCTION), + RealizedTy::Uint8Array { .. } => Some(baml_type::typetag::UINT8ARRAY), + RealizedTy::Literal(lit, _, _) => Some(match lit { + baml_base::Literal::Int(_) => baml_type::typetag::INT, + baml_base::Literal::Bigint(_) => baml_type::typetag::BIGINT, + baml_base::Literal::Float(_) => baml_type::typetag::FLOAT, + baml_base::Literal::String(_) => baml_type::typetag::STRING, + baml_base::Literal::Bool(_) => baml_type::typetag::BOOL, + }), + _ => None, + } +} + // ============================================================================ // Public Entry Point // ============================================================================ diff --git a/baml_language/crates/baml_compiler2_emit/src/lib.rs b/baml_language/crates/baml_compiler2_emit/src/lib.rs index 12f1adb317e..f11e9aebcaf 100644 --- a/baml_language/crates/baml_compiler2_emit/src/lib.rs +++ b/baml_language/crates/baml_compiler2_emit/src/lib.rs @@ -382,7 +382,7 @@ fn build_packages( .find(|(an, _)| an == name) .map(|(_, t)| t.clone()) .unwrap_or_else(|| { - baml_type::TyTemplate::Concrete(baml_type::RuntimeTy::BuiltinUnknown { + baml_type::TyTemplate::from(baml_type::RealizedTy::BuiltinUnknown { attr: TyAttr::default(), }) }); @@ -590,13 +590,13 @@ fn build_packages( // The implementor pattern is the class at its own parameters; bounds // come from the class's generic parameters. Shared by all its blocks. let for_ty_pattern = if generics.is_empty() { - baml_type::TyTemplate::Concrete(baml_type::RuntimeTy::Class( + baml_type::TyTemplate::from(baml_type::RealizedTy::Class( class_tn.clone(), Vec::new(), TyAttr::default(), )) } else { - baml_type::TyTemplate::Class( + baml_type::TyTemplate::class( class_tn.clone(), (0..u32::try_from(generics.len()).expect("generic arity fits u32")) .map(baml_type::TyTemplate::TypeArgRef) @@ -983,7 +983,7 @@ pub fn generate_project_bytecode_with_opt( let mut fields = Vec::new(); // Class-level generic params, used to resolve `T`-references in // field type expressions to `TyTemplate::TypeArgRef(N)`. When - // empty, `tir2_to_template` produces `TyTemplate::Concrete(...)` + // empty, `tir2_to_template` produces a `Concrete`-equivalent leaf // for every leaf and `field_template == Concrete(field_type)`. let class_generic_params: Vec = class_data.generic_params.clone(); // BEP-044: collect only the class's actual runtime fields. @@ -1019,7 +1019,12 @@ pub fn generate_project_bytecode_with_opt( let null_ty = baml_type::RuntimeTy::Null { attr: baml_type::TyAttr::default(), }; - (null_ty.clone(), baml_type::TyTemplate::Concrete(null_ty)) + ( + null_ty.clone(), + baml_type::TyTemplate::from(baml_type::RealizedTy::Null { + attr: baml_type::TyAttr::default(), + }), + ) } }; let (field_desc, field_alias, field_skip) = extract_schema_attrs(attrs.as_slice()); diff --git a/baml_language/crates/baml_compiler2_emit/src/pull_semantics.rs b/baml_language/crates/baml_compiler2_emit/src/pull_semantics.rs index 535b9499f5f..a2f29d83219 100644 --- a/baml_language/crates/baml_compiler2_emit/src/pull_semantics.rs +++ b/baml_language/crates/baml_compiler2_emit/src/pull_semantics.rs @@ -399,7 +399,7 @@ pub(crate) fn walk_rvalue_pull(sink: &mut S, rvalue: &Rvalue) -> Re // produced by the array-literal lowering (which emits the typed // `Rvalue::Array`); this arm is a defensive fallback. sink.alloc_array( - &TyTemplate::Concrete(baml_type::RuntimeTy::unknown()), + &TyTemplate::from(baml_type::RealizedTy::unknown()), fields.len(), ) } diff --git a/baml_language/crates/baml_compiler2_emit/src/stack_carry.rs b/baml_language/crates/baml_compiler2_emit/src/stack_carry.rs index 26f17755d69..3471539d6c6 100644 --- a/baml_language/crates/baml_compiler2_emit/src/stack_carry.rs +++ b/baml_language/crates/baml_compiler2_emit/src/stack_carry.rs @@ -1468,7 +1468,7 @@ impl StackEffectSink for StackCarryPullSink<'_> { #[cfg(test)] mod tests { use baml_compiler2_mir::{AggregateKind, BasicBlock, LocalDecl, Statement}; - use baml_type::{TyAttr, TyTemplate}; + use baml_type::{RealizedTy, TyAttr, TyTemplate}; use super::*; @@ -1532,7 +1532,7 @@ mod tests { let ok = simulate_aggregate_operand_pull_stack( &Rvalue::Array( - TyTemplate::Concrete(RuntimeTy::unknown()), + TyTemplate::from(RealizedTy::unknown()), vec![ Operand::copy_local(carried), Operand::copy_local(sibling), @@ -1586,8 +1586,8 @@ mod tests { let ok = simulate_aggregate_operand_pull_stack( &Rvalue::Map( - TyTemplate::Concrete(RuntimeTy::string()), - TyTemplate::Concrete(RuntimeTy::unknown()), + TyTemplate::from(RealizedTy::string()), + TyTemplate::from(RealizedTy::unknown()), vec![ ( Operand::Constant(Constant::String("a".to_string())), @@ -1614,8 +1614,8 @@ mod tests { let key = Local(1); let value = Local(2); let rvalue = Rvalue::Map( - TyTemplate::Concrete(RuntimeTy::string()), - TyTemplate::Concrete(RuntimeTy::unknown()), + TyTemplate::from(RealizedTy::string()), + TyTemplate::from(RealizedTy::unknown()), vec![(Operand::copy_local(key), Operand::copy_local(value))], ); @@ -1634,7 +1634,7 @@ mod tests { let ok = simulate_aggregate_operand_pull_stack( &Rvalue::Array( - TyTemplate::Concrete(RuntimeTy::unknown()), + TyTemplate::from(RealizedTy::unknown()), vec![ Operand::copy_local(real_prefix), Operand::copy_local(carried), @@ -1664,7 +1664,7 @@ mod tests { &Rvalue::Aggregate { kind: AggregateKind::Class { name: "Box".to_string(), - type_arg_templates: vec![TyTemplate::Concrete(int_ty())], + type_arg_templates: vec![TyTemplate::from(baml_type::RealizedTy::int())], }, fields: vec![Operand::copy_local(sibling), Operand::copy_local(carried)], }, diff --git a/baml_language/crates/baml_compiler2_mir/src/ir.rs b/baml_language/crates/baml_compiler2_mir/src/ir.rs index 819d4840d2a..16e505e9630 100644 --- a/baml_language/crates/baml_compiler2_mir/src/ir.rs +++ b/baml_language/crates/baml_compiler2_mir/src/ir.rs @@ -730,9 +730,9 @@ pub enum Rvalue { /// /// The type is stored as a `TyTemplate` so that generic class checks like /// `value is Foo` (where `T` is a type parameter in scope) resolve - /// correctly at runtime via `TypeArgRef` substitution. For fully-concrete - /// types the template is `TyTemplate::Concrete(ty)`, which the emitter - /// handles on the same fast path as before. + /// correctly at runtime via `TypeArgRef` substitution. A fully-realized + /// template narrows to a `RealizedTy`, which the emitter handles on the + /// same tag / class-identity fast path as before. IsType { operand: Operand, ty_template: TyTemplate, @@ -793,9 +793,9 @@ pub enum Rvalue { /// Materialize a `Ty` from a `TyTemplate`. /// - /// For concrete templates (`TyTemplate::Concrete`), the `Ty` is baked in - /// at compile time. For templates containing `TypeArgRef(N)`, the VM - /// substitutes `frame.type_args[N]` at execution time. + /// For a fully-realized template, the `Ty` is baked in at compile time. + /// For templates containing `TypeArgRef(N)`, the VM substitutes + /// `frame.type_args[N]` at execution time. /// /// Emitted by the `reflect.type_of()` intrinsic. /// Lowers to `Instruction::LoadType(const_idx)` in bytecode. diff --git a/baml_language/crates/baml_compiler2_mir/src/lower.rs b/baml_language/crates/baml_compiler2_mir/src/lower.rs index d8e10f38339..caef39b1860 100644 --- a/baml_language/crates/baml_compiler2_mir/src/lower.rs +++ b/baml_language/crates/baml_compiler2_mir/src/lower.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use baml_base::{Name, TypePath}; -use baml_type::{ResolvedAliases, RuntimeTy, TyAttr, TyTemplate, TypeName}; +use baml_type::{RealizedTy, ResolvedAliases, RuntimeTy, TyAttr, TyTemplate, TypeName}; use indexmap::IndexMap; use crate::{ @@ -781,28 +781,6 @@ fn contains_assoc_projection(ty: &Tir2Ty) -> bool { // ─── RuntimeTy → TyTemplate conversion for already-resolved RuntimeTy values ────────────── -/// Convert an already-resolved `baml_type::RuntimeTy` back to a `TyTemplate`. -/// -/// This is needed for `IsType` pattern-matching where the pattern type comes -/// through `convert_tir_ty_for_runtime` (so `TypeVars` are already erased), but we still -/// need a `TyTemplate` to carry class-level type args for the VM to compare -/// against `Instance::class_type_args`. -/// -/// For all leaf types that aren't `RuntimeTy::Class`, the result is -/// `TyTemplate::Concrete(ty)`. For `RuntimeTy::Class(tn, args, _)` we produce -/// `TyTemplate::Class(tn, args.map(Concrete))` so the VM can compare the -/// resolved args against the instance's `class_type_args`. -/// -/// Note: by the time `emit_is_type_branch` is called, any `TypeVars` in the -/// pattern have already been resolved to concrete types — so no -/// `generic_params` are needed. If future patterns introduce `TypeVars` that -/// survive to MIR, thread `enclosing_generic_params()` through here. -/// Convert a `Tir2Ty` to `TyTemplate`, mapping any `TypeVar(name)` whose -/// `name` appears at position `N` in `generic_params` to `TypeArgRef(N)`. -/// -/// Free function counterpart to `MirLowerer::ty_to_template`, exposed so -/// that callers outside of MIR (e.g. `baml_compiler2_emit`'s class-field -/// type lowering) can build the same templates. /// Depth cap for recursive blanket-impl bound checking (guards pathological /// `requires`/blanket cycles; real chains are short). const BLANKET_BOUND_DEPTH: u32 = 16; @@ -849,13 +827,75 @@ fn type_satisfies_bound( } } +/// Whether `ty` contains an associated-type projection node at any depth. +/// +/// The companion of [`baml_compiler2_tir::generics::contains_typevar`] for the +/// other symbolic node kind: `contains_typevar` sees a projection only through +/// its base/interface *type variables*, so a projection whose base is already +/// concrete (`(int as Foo).Assoc` left unresolved by inference) slips past it. +/// Template lowering must route either symbolic kind through the structured +/// template arms rather than asserting realizedness. +fn tir_contains_projection(ty: &Tir2Ty) -> bool { + match ty { + Tir2Ty::AssociatedTypeProjection { .. } => true, + Tir2Ty::List(inner, _) | Tir2Ty::EvolvingList(inner, _) => tir_contains_projection(inner), + Tir2Ty::Map { + key: k, value: v, .. + } + | Tir2Ty::EvolvingMap(k, v, _) => tir_contains_projection(k) || tir_contains_projection(v), + Tir2Ty::Union(members, _) => members.iter().any(tir_contains_projection), + Tir2Ty::Class(_, type_args, _) => type_args.iter().any(tir_contains_projection), + Tir2Ty::Interface(_, type_args, associated_bindings, _) => { + type_args.iter().any(tir_contains_projection) + || associated_bindings + .iter() + .any(|(_, ty)| tir_contains_projection(ty)) + } + Tir2Ty::Future(value, error, _) => { + tir_contains_projection(value) || tir_contains_projection(error) + } + Tir2Ty::Function { + params, + ret, + throws, + .. + } => { + params + .iter() + .any(|param| tir_contains_projection(¶m.ty)) + || tir_contains_projection(ret) + || tir_contains_projection(throws) + } + Tir2Ty::WatchAccessor(inner, _) => tir_contains_projection(inner), + _ => false, + } +} + +/// Whether `ty` contains any symbolic node — a type variable or an +/// associated-type projection — that has no realized runtime form and must be +/// lowered through the structured template arms. +fn tir_contains_symbolic(ty: &Tir2Ty) -> bool { + baml_compiler2_tir::generics::contains_typevar(ty) || tir_contains_projection(ty) +} + +/// Convert a `Tir2Ty` to `TyTemplate`, mapping any `TypeVar(name)` whose +/// `name` appears at position `N` in `generic_params` to `TypeArgRef(N)`. +/// +/// Free function counterpart to `MirLowerer::ty_to_template`, exposed so +/// that callers outside of MIR (e.g. `baml_compiler2_emit`'s class-field +/// type lowering) can build the same templates. pub fn tir2_to_template( ty: &Tir2Ty, resolved: &ResolvedAliases, generic_params: &[baml_base::Name], ) -> TyTemplate { match ty { - Tir2Ty::AssociatedTypeProjection { member, .. } => generic_params + Tir2Ty::AssociatedTypeProjection { + base, + interface, + member, + .. + } => generic_params .iter() .position(|p| p == member) .map(|n| { @@ -864,56 +904,87 @@ pub fn tir2_to_template( // The member is a frame generic only inside interface-default-method // bodies (where `enclosing_generic_params` adds the interface's // associated types). Otherwise the projection — e.g. `T.CompareError` - // passed as a call's type argument — is kept faithfully, exactly like - // `Self` below: `convert` resolves it against the bound, or preserves - // it for the runtime to resolve from the receiver's actual type. Never - // an error, never erased. - .unwrap_or_else(|| TyTemplate::Concrete(resolved.convert(ty))), - Tir2Ty::TypeVar(name, _) if name == "Self" => TyTemplate::Concrete(resolved.convert(ty)), + // passed as a call's type argument — is kept as a structured + // projection template: its base/interface positions lower + // recursively (so a frame-generic base realizes at substitution + // time) while the projection itself stays symbolic for the + // consumer to resolve. Never an error, never erased. + .unwrap_or_else(|| TyTemplate::AssociatedTypeProjection { + base: Box::new(tir2_to_template(base, resolved, generic_params)), + interface: interface.as_ref().map(|iface| { + Box::new(baml_type::TyTemplateInterface { + name: iface.name.clone(), + generics: iface + .generics + .iter() + .map(|g| tir2_to_template(g, resolved, generic_params)) + .collect(), + associated_types: iface + .associated_types + .iter() + .map(|(name, ty)| { + (name.clone(), tir2_to_template(ty, resolved, generic_params)) + }) + .collect(), + }) + }), + member: member.clone(), + attr: TyAttr::default(), + }), + // FIXME(typevar-templates): `Self` is the one type variable that may + // legitimately survive to a template (it is a real, if unresolved, + // reference to the concrete implementor type). It maps to `Wildcard` as + // a temporary bandaid — correct for a top-level `x is Self` (emits + // `false`), but it OVER-MATCHES in a nested position (`Foo`), where + // the guard treats `Wildcard` as "any". Give `Self` a real frame slot so + // it lowers to a `TypeArgRef` (interface default-method / class-body + // work) and delete this arm. + Tir2Ty::TypeVar(name, _) if name == "Self" => TyTemplate::Wildcard, Tir2Ty::TypeVar(name, _) => { let Some(n) = generic_params.iter().position(|p| p == name) else { + // A non-`Self` type variable with no frame position is a defect: + // either a lowering gap (it should have been resolved upstream) + // or a real user error the checker should have rejected. It + // cannot be represented faithfully — a symbolic name is + // meaningless to the runtime — and erasing it would violate the + // type contract, so fail loudly rather than smuggle it. unreachable!("type variable not found in type args: {}", name) }; TyTemplate::TypeArgRef(u32::try_from(n).expect("generic param index fits in u32")) } Tir2Ty::List(inner, _) => { - TyTemplate::Array(Box::new(tir2_to_template(inner, resolved, generic_params))) + TyTemplate::list(tir2_to_template(inner, resolved, generic_params)) } Tir2Ty::Map { key: k, value: v, .. - } => TyTemplate::Map( - Box::new(tir2_to_template(k, resolved, generic_params)), - Box::new(tir2_to_template(v, resolved, generic_params)), + } => TyTemplate::map( + tir2_to_template(k, resolved, generic_params), + tir2_to_template(v, resolved, generic_params), ), - Tir2Ty::Union(parts, _) => TyTemplate::Union( + Tir2Ty::Union(parts, _) => TyTemplate::union( parts .iter() - .map(|p| tir2_to_template(p, resolved, generic_params)) - .collect(), + .map(|p| tir2_to_template(p, resolved, generic_params)), ), Tir2Ty::Class(qtn, type_args, attr) => { - if type_args - .iter() - .any(baml_compiler2_tir::generics::contains_typevar) - { + if type_args.iter().any(tir_contains_symbolic) { let template_args: Vec = type_args .iter() .map(|a| tir2_to_template(a, resolved, generic_params)) .collect(); - TyTemplate::Class(qtn.clone(), template_args) + TyTemplate::class(qtn.clone(), template_args) } else { + // The symbolic check above guarantees the args are realized. let resolved_args: Vec = type_args.iter().map(|a| resolved.convert(a)).collect(); - TyTemplate::Concrete(RuntimeTy::Class(qtn.clone(), resolved_args, attr.clone())) + realized_leaf_template(&RuntimeTy::Class(qtn.clone(), resolved_args, attr.clone())) } } Tir2Ty::Interface(qtn, type_args, associated_bindings, attr) => { - if type_args - .iter() - .any(baml_compiler2_tir::generics::contains_typevar) + if type_args.iter().any(tir_contains_symbolic) || associated_bindings .iter() - .any(|(_, ty)| baml_compiler2_tir::generics::contains_typevar(ty)) + .any(|(_, ty)| tir_contains_symbolic(ty)) { let template_args: Vec = type_args .iter() @@ -925,7 +996,7 @@ pub fn tir2_to_template( (name.clone(), tir2_to_template(ty, resolved, generic_params)) }) .collect(); - TyTemplate::Interface(qtn.clone(), template_args, template_bindings) + TyTemplate::interface(qtn.clone(), template_args, template_bindings) } else { let resolved_args: Vec = type_args.iter().map(|a| resolved.convert(a)).collect(); @@ -933,7 +1004,8 @@ pub fn tir2_to_template( .iter() .map(|(name, ty)| (name.clone(), resolved.convert(ty))) .collect(); - TyTemplate::Concrete(RuntimeTy::Interface( + // The symbolic check above guarantees the args are realized. + realized_leaf_template(&RuntimeTy::Interface( qtn.clone(), resolved_args, resolved_bindings, @@ -942,28 +1014,63 @@ pub fn tir2_to_template( } } Tir2Ty::EvolvingList(inner, _) => { - TyTemplate::Array(Box::new(tir2_to_template(inner, resolved, generic_params))) + TyTemplate::list(tir2_to_template(inner, resolved, generic_params)) } - Tir2Ty::EvolvingMap(k, v, _) => TyTemplate::Map( - Box::new(tir2_to_template(k, resolved, generic_params)), - Box::new(tir2_to_template(v, resolved, generic_params)), + Tir2Ty::EvolvingMap(k, v, _) => TyTemplate::map( + tir2_to_template(k, resolved, generic_params), + tir2_to_template(v, resolved, generic_params), ), - // Everything else flattens to a `Concrete` template via `resolved.convert`, which - // lowers faithfully (never a `TypeArgRef`), so a type var in its args is carried - // through as a bare `RuntimeTy::TypeVar` — which `match_template`'s exact `Concrete` - // comparison never matches against a real value's concrete type. Arg-less for-types - // (`Type`/`Resource`/`PromptAst`/primitives/enums) are exact. `Future` carries - // `value`/`error` args, so a *top-level* `Future` for-type is now rejected by - // `is_valid_impl_subject` (a generic `Future` would otherwise bake an - // undispatchable rule). A *nested* generic `Future` — e.g. - // `implement I for Box>` — still carries a bare `T` here, an exotic - // residual; `Class`/`List`/`Map`/`Union`/`Interface` avoid it by recursing through - // `tir2_to_template` (which emits a `TypeArgRef`). Add a `TyTemplate::Future` to - // close the nested case. - other => TyTemplate::Concrete(resolved.convert(other)), + // The remaining nested-type-bearing constructors recurse structurally, + // so a type variable in any position lowers to its `TypeArgRef` frame + // slot instead of leaking a symbolic name. + Tir2Ty::Function { + params, + ret, + throws, + .. + } => TyTemplate::Function { + params: params + .iter() + .map(|p| baml_type::TyTemplateFunctionParamTy { + name: p.name.clone(), + ty: tir2_to_template(&p.ty, resolved, generic_params), + mode: p.mode, + }) + .collect(), + ret: Box::new(tir2_to_template(ret, resolved, generic_params)), + throws: Box::new(tir2_to_template(throws, resolved, generic_params)), + attr: TyAttr::default(), + }, + Tir2Ty::Future(value, error, _) => TyTemplate::Future( + Box::new(tir2_to_template(value, resolved, generic_params)), + Box::new(tir2_to_template(error, resolved, generic_params)), + TyAttr::default(), + ), + Tir2Ty::WatchAccessor(inner, _) => TyTemplate::WatchAccessor( + Box::new(tir2_to_template(inner, resolved, generic_params)), + TyAttr::default(), + ), + // Everything else is a leaf with no nested type positions (primitives, + // enums, aliases, `Type`/`Resource`/`PromptAst`, …) — `convert` lowers + // it faithfully and the narrowing proves it realized. The symbolic + // kinds (`TypeVar`, projections) have their own arms above, so a + // failure here is a compiler invariant violation. + other => realized_leaf_template(&resolved.convert(other)), } } +/// A resolved `RuntimeTy` with no residual type variables, as a leaf template: +/// it narrows to a [`RealizedTy`] (proving realizedness) and widens into a +/// `Concrete`-equivalent `TyTemplate`. Panics if the type still contains a type +/// variable — callers guarantee realizedness (see the `tir_contains_symbolic` +/// gate), so a failure is a compiler invariant violation, not silent erasure. +fn realized_leaf_template(ty: &RuntimeTy) -> TyTemplate { + TyTemplate::from( + RealizedTy::try_from(ty.clone()) + .unwrap_or_else(|e| unreachable!("realized-leaf template must be realized: {e}")), + ) +} + fn tir2_to_dispatch_guard_template( ty: &Tir2Ty, resolved: &ResolvedAliases, @@ -981,30 +1088,29 @@ fn tir2_to_dispatch_guard_template( ) }) .unwrap_or(TyTemplate::Wildcard), - Tir2Ty::List(inner, _) | Tir2Ty::EvolvingList(inner, _) => TyTemplate::Array(Box::new( + Tir2Ty::List(inner, _) | Tir2Ty::EvolvingList(inner, _) => TyTemplate::list( tir2_to_dispatch_guard_template(inner, resolved, generic_params), - )), + ), Tir2Ty::Map { key: k, value: v, .. } - | Tir2Ty::EvolvingMap(k, v, _) => TyTemplate::Map( - Box::new(tir2_to_dispatch_guard_template(k, resolved, generic_params)), - Box::new(tir2_to_dispatch_guard_template(v, resolved, generic_params)), + | Tir2Ty::EvolvingMap(k, v, _) => TyTemplate::map( + tir2_to_dispatch_guard_template(k, resolved, generic_params), + tir2_to_dispatch_guard_template(v, resolved, generic_params), ), - Tir2Ty::Union(parts, _) => TyTemplate::Union( + Tir2Ty::Union(parts, _) => TyTemplate::union( parts .iter() - .map(|p| tir2_to_dispatch_guard_template(p, resolved, generic_params)) - .collect(), + .map(|p| tir2_to_dispatch_guard_template(p, resolved, generic_params)), ), - Tir2Ty::Class(qtn, type_args, _) => TyTemplate::Class( + Tir2Ty::Class(qtn, type_args, _) => TyTemplate::class( qtn.clone(), type_args .iter() .map(|arg| tir2_to_dispatch_guard_template(arg, resolved, generic_params)) .collect(), ), - Tir2Ty::Interface(qtn, type_args, assoc, _) => TyTemplate::Interface( + Tir2Ty::Interface(qtn, type_args, assoc, _) => TyTemplate::interface( qtn.clone(), type_args .iter() @@ -1024,19 +1130,279 @@ fn tir2_to_dispatch_guard_template( } } +/// Convert an already-resolved `baml_type::RuntimeTy` back to a `TyTemplate`. +/// +/// This is used for `IsType` pattern-matching where the pattern type comes +/// through `convert_tir_ty_for_runtime`, which keeps type variables faithful — +/// so a pattern like `(int) -> T` still carries `T` by name here. A realized +/// type becomes a `Concrete`-equivalent leaf (the VM's tag / class-identity +/// fast paths); a composite decomposes structurally. +/// +/// FIXME(typevar-templates): a residual `TypeVar`/projection maps to `Wildcard`, +/// a temporary bandaid that reproduces the old `Concrete(TypeVar)` "matches +/// nothing" at the top level (it OVER-matches nested — see the `Self` note in +/// `tir2_to_template`). Only `Self` legitimately survives here; any other +/// unresolved type variable is a lowering defect or a real user error that must +/// be resolved to a `TypeArgRef` or diagnosed. Remove this fallback once +/// typevars reaching a template are always resolved. pub(crate) fn ty_to_template_from_resolved_ty(ty: &RuntimeTy) -> TyTemplate { match ty { - RuntimeTy::Class(tn, args, _) if !args.is_empty() => { - // Parametric class: produce TyTemplate::Class with Concrete leaves. - // This allows the VM to check `expected_args == inst.class_type_args`. - TyTemplate::Class( - tn.clone(), - args.iter().map(ty_to_template_from_resolved_ty).collect(), - ) + // See the FIXME above: an unresolved type variable or projection is held + // as a `Wildcard` hole rather than smuggled by name. + RuntimeTy::TypeVar(..) | RuntimeTy::AssociatedTypeProjection { .. } => TyTemplate::Wildcard, + // Composites decompose so a nested residual type variable is a hole at + // its own position, not the whole container. + RuntimeTy::List(inner, _) => TyTemplate::list(ty_to_template_from_resolved_ty(inner)), + RuntimeTy::Map { key, value, .. } => TyTemplate::map( + ty_to_template_from_resolved_ty(key), + ty_to_template_from_resolved_ty(value), + ), + RuntimeTy::Union(members, _) => { + TyTemplate::union(members.iter().map(ty_to_template_from_resolved_ty)) + } + RuntimeTy::Class(tn, args, _) if !args.is_empty() => TyTemplate::class( + tn.clone(), + args.iter().map(ty_to_template_from_resolved_ty).collect(), + ), + RuntimeTy::Function { + params, + ret, + throws, + .. + } => TyTemplate::Function { + params: params + .iter() + .map(|p| baml_type::TyTemplateFunctionParamTy { + name: p.name.clone(), + ty: ty_to_template_from_resolved_ty(&p.ty), + mode: p.mode, + }) + .collect(), + ret: Box::new(ty_to_template_from_resolved_ty(ret)), + throws: Box::new(ty_to_template_from_resolved_ty(throws)), + attr: TyAttr::default(), + }, + RuntimeTy::Future(value, error, _) => TyTemplate::Future( + Box::new(ty_to_template_from_resolved_ty(value)), + Box::new(ty_to_template_from_resolved_ty(error)), + TyAttr::default(), + ), + RuntimeTy::WatchAccessor(inner, _) => TyTemplate::WatchAccessor( + Box::new(ty_to_template_from_resolved_ty(inner)), + TyAttr::default(), + ), + // A realized leaf (incl. a monomorphic class): narrow and widen. A + // non-realized value would be a composite handled above or a residual + // type variable handled at the top — a failure is a compiler invariant + // violation, not silent erasure. + other => realized_leaf_template(other), + } +} + +/// Flatten a `RuntimeTy` union into its leaf members (recursively), pushing each +/// non-union leaf into `out`. A recursive alias is kept as a single `TypeAlias` +/// leaf — it is not expanded — matching `ResolvedAliases::convert`. +fn flatten_runtime_union(ty: &RuntimeTy, out: &mut Vec) { + match ty { + RuntimeTy::Union(members, _) => { + for m in members { + flatten_runtime_union(m, out); + } + } + other => out.push(other.clone()), + } +} + +/// Replace a container template's immediate element position(s) with `Wildcard` +/// so the emitter lowers it to a coarse `LIST`/`MAP` type tag rather than a +/// structural element check. A non-container template is returned unchanged. +fn wildcard_container_elements(template: TyTemplate) -> TyTemplate { + match template { + TyTemplate::List(_, attr) => TyTemplate::List(Box::new(TyTemplate::Wildcard), attr), + TyTemplate::Map { attr, .. } => TyTemplate::Map { + key: Box::new(TyTemplate::Wildcard), + value: Box::new(TyTemplate::Wildcard), + attr, + }, + other => other, + } +} + +/// A scrutinee member is *opaque* for a coarse-tag soundness proof when it could +/// dynamically resolve to a container or class instantiation its static type +/// does not reveal — a type alias (`json` keeps its `json[]`/`map` arms hidden +/// behind an opaque leaf), a type variable, `unknown`, an interface (a container +/// or class may implement it), an associated projection, or `void`. A coarse +/// type-tag test can never be *proven* equivalent to the structural test against +/// such a member, so a tag-sufficiency proof must fail closed on it and emit the +/// element/arg-precise structural test instead. +/// +/// Exhaustive on purpose: a new `RuntimeTy` variant must force a deliberate +/// classification here rather than silently defaulting to "safe". +fn member_is_opaque_for_tag_proof(m: &RuntimeTy) -> bool { + match m { + RuntimeTy::TypeAlias(..) + | RuntimeTy::TypeVar(..) + | RuntimeTy::BuiltinUnknown { .. } + | RuntimeTy::AssociatedTypeProjection { .. } + | RuntimeTy::Interface(..) + | RuntimeTy::Void { .. } => true, + // A union should have been flattened before this check; recurse + // defensively so a nested one can't smuggle an opaque member past it. + RuntimeTy::Union(members, _) => members.iter().any(member_is_opaque_for_tag_proof), + // Every concrete leaf carries a fixed runtime tag we can reason about. + RuntimeTy::Int { .. } + | RuntimeTy::Bigint { .. } + | RuntimeTy::Float { .. } + | RuntimeTy::String { .. } + | RuntimeTy::Bool { .. } + | RuntimeTy::Null { .. } + | RuntimeTy::Uint8Array { .. } + | RuntimeTy::Media(..) + | RuntimeTy::Class(..) + | RuntimeTy::Enum(..) + | RuntimeTy::EnumVariant(..) + | RuntimeTy::Literal(..) + | RuntimeTy::Function { .. } + | RuntimeTy::Future(..) + | RuntimeTy::List(..) + | RuntimeTy::Map { .. } + | RuntimeTy::RustType { .. } + | RuntimeTy::Type { .. } + | RuntimeTy::Resource { .. } + | RuntimeTy::PromptAst { .. } + | RuntimeTy::WatchAccessor(..) + | RuntimeTy::Never { .. } => false, + } +} + +/// Whether a coarse `LIST`/`MAP` type-tag test for the container arm `arm` is a +/// *provably sound* substitute for the element-discriminating structural test, +/// for values of the scrutinee's resolved static type `scrutinee`. +/// +/// Generic-argument positions are invariant (`TYPE_SYSTEM.md` "Variance"): `int[]`, +/// `string[]`, and `json[]` are mutually unrelated types. So the coarse "any +/// list" / "any map" tag is sound only when no value the scrutinee admits could +/// carry that tag with a different element type than the arm. That holds when +/// every same-shape (list/map) member of the scrutinee shares the arm's +/// constructor identity, and every other member carries a tag that provably +/// can't be the arm's — a different-shape container or a concrete non-container +/// leaf. +/// +/// We **fail closed**: an opaque member (see [`member_is_opaque_for_tag_proof`]) +/// could hide a container of a differing element type, so it blocks the proof +/// and routes the arm through the structural matcher. In particular a `json` +/// scrutinee (an opaque alias leaf) no longer makes an element-specific `int[]` +/// arm "tag-sufficient" — that was fail-*open* covariance, matching any array. +/// +/// Returns `false` for a non-container `arm` (the caller only asks about one). +fn container_arm_tag_sufficient(arm: &RuntimeTy, scrutinee: &RuntimeTy) -> bool { + use baml_compiler2_tir::exhaustiveness::ty_ctor_identity; + let arm_is_list = match arm { + RuntimeTy::List(..) => true, + RuntimeTy::Map { .. } => false, + _ => return false, + }; + let arm_id = ty_ctor_identity(arm.as_ty()); + let mut members = Vec::new(); + flatten_runtime_union(scrutinee, &mut members); + members.iter().all(|m| match m { + // Same-shape container: sound only for the arm's exact instantiation. + RuntimeTy::List(..) if arm_is_list => ty_ctor_identity(m.as_ty()) == arm_id, + RuntimeTy::Map { .. } if !arm_is_list => ty_ctor_identity(m.as_ty()) == arm_id, + // Anything else is sound iff it cannot secretly be a differing-element + // container — i.e. iff it is not opaque. A different-shape container and + // every concrete leaf carry a tag that can't be the arm's, so they pass. + _ => !member_is_opaque_for_tag_proof(m), + }) +} + +/// Whether the coarse type tag fully discriminates values reaching a switch arm +/// whose (already union-flattened) member type is `member`, given the enclosing +/// match scrutinee's resolved static type when known. +/// +/// Type tags conflate every list, every map, and every instantiation of a +/// generic class, so a tag-keyed jump table sends *any* same-tag value to the +/// first arm carrying that tag. A member is tag-sufficient only when no value +/// the scrutinee admits could be conflated onto it: +/// +/// - a list/map member defers to [`container_arm_tag_sufficient`] (which +/// deliberately treats opaque scrutinee members as non-blockers, preserving +/// the pre-structural erased semantics the chain path keeps for them); with +/// no scrutinee the equivalence is unprovable, so it fails closed to the +/// precise chain; +/// - a parametric class member requires every same-class member of the +/// scrutinee to share its constructor identity (`Foo` is sufficient only +/// if the scrutinee cannot hold a `Foo`), and — being a gate for the +/// arg-precise `ClassWithTypeArgs` chain test rather than a legacy-semantics +/// bridge — fails closed on opaque scrutinee members that could dynamically +/// hold another instantiation; +/// - every other member (primitive, monomorphic class, enum, …) keeps its +/// existing tag behavior. +fn switch_member_tag_sufficient(member: &RuntimeTy, scrutinee: Option<&RuntimeTy>) -> bool { + use baml_compiler2_tir::exhaustiveness::ty_ctor_identity; + match member { + RuntimeTy::List(..) | RuntimeTy::Map { .. } => { + scrutinee.is_some_and(|scrutinee| container_arm_tag_sufficient(member, scrutinee)) + } + // Every enum collapses onto the single shared `ENUM` type tag, so the + // jump table can key an enum-type arm only when no *other* enum type + // reaches this match — otherwise two enum arms dedup onto that one tag + // and the first swallows the rest. When several enum types coexist the + // arm falls to the precise chain, which dispatches on enum-pointer + // identity (`is Color` compares the value's enum object). + RuntimeTy::Enum(name, _) => { + let Some(scrutinee) = scrutinee else { + return false; + }; + let mut flat = Vec::new(); + flatten_runtime_union(scrutinee, &mut flat); + flat.iter().all(|m| match enum_type_name(m) { + // Another enum type shares this arm's `ENUM` tag → collision. + Some(m_name) => m_name == name, + // A concrete non-enum member carries a different runtime tag and + // can't collide, but an opaque member could dynamically be a + // *different* enum (also `ENUM`-tagged) — so it fails closed, like + // the container and class branches. + None => !member_is_opaque_for_tag_proof(m), + }) + } + // A specific variant type (`Color.Red`) needs variant-level discrimination + // the shared `ENUM` tag can't express, so always take the precise chain. + RuntimeTy::EnumVariant(..) => false, + RuntimeTy::Class(name, args, _) => { + if args.is_empty() { + return true; + } + let Some(scrutinee) = scrutinee else { + return false; + }; + let member_id = ty_ctor_identity(member.as_ty()); + let mut flat = Vec::new(); + flatten_runtime_union(scrutinee, &mut flat); + flat.iter().all(|m| match m { + RuntimeTy::Class(m_name, _, _) if m_name == name => { + ty_ctor_identity(m.as_ty()) == member_id + } + // A different class dispatches on a different tag. + RuntimeTy::Class(..) => true, + // An opaque member may admit another instantiation of this + // class at runtime; the proof fails closed. + _ => !member_is_opaque_for_tag_proof(m), + }) } - // All other types: wrap in Concrete. The VM uses this for the - // existing fast paths (primitive type tags, monomorphic classes). - other => TyTemplate::Concrete(other.clone()), + _ => true, + } +} + +/// The enum type name of an enum-shaped type. A bare enum (`Color`) and any of +/// its variants (`Color.Red`) collapse onto the same shared `ENUM` type tag, so +/// both count as "an enum of this type" for the tag-collision check in +/// [`switch_member_tag_sufficient`]. +fn enum_type_name(ty: &RuntimeTy) -> Option<&TypeName> { + match ty { + RuntimeTy::Enum(name, _) | RuntimeTy::EnumVariant(name, _, _) => Some(name), + _ => None, } } @@ -1719,6 +2085,15 @@ struct LoweringContext<'db> { // Comparable`). Saved/restored across nested lambdas. lambda_param_tir_types: FxHashMap, + /// The `(local, resolved static type)` of the value the enclosing `match` is + /// scrutinizing, set for the duration of its arm lowering (save/restored + /// across nested matches). A container arm's runtime type test consults this + /// — guarded on the local matching — to decide whether a coarse `LIST`/`MAP` + /// tag suffices in place of a structural element check (see + /// [`container_arm_tag_sufficient`]). `None` outside a match, and the local + /// guard keeps `is` / standalone tests element-precise even inside one. + match_scrutinee: Option<(Local, RuntimeTy)>, + // The FileScopeId of the expression body currently being lowered. // Updated when descending into lambda bodies (Phase 3+). current_scope: FileScopeId, @@ -2788,6 +3163,7 @@ impl<'db> LoweringContext<'db> { generic_param_bounds, dispatch_cache, lambda_param_tir_types: FxHashMap::default(), + match_scrutinee: None, current_scope: func_scope_id, current_metadata_scope: MetadataScope::Body(func_scope_id), body: expr_body, @@ -2881,6 +3257,7 @@ impl<'db> LoweringContext<'db> { generic_param_bounds: FxHashMap::default(), dispatch_cache, lambda_param_tir_types: FxHashMap::default(), + match_scrutinee: None, current_scope: let_scope_id, current_metadata_scope: MetadataScope::Body(let_scope_id), body: expr_body, @@ -3930,7 +4307,7 @@ impl<'db> LoweringContext<'db> { Some(Tir2Ty::List(elem, _) | Tir2Ty::EvolvingList(elem, _)) => { self.ty_to_template(elem, &generic_params) } - _ => TyTemplate::Concrete(RuntimeTy::unknown()), + _ => TyTemplate::from(RealizedTy::unknown()), } } @@ -3946,8 +4323,8 @@ impl<'db> LoweringContext<'db> { self.ty_to_template(value, &generic_params), ), _ => ( - TyTemplate::Concrete(RuntimeTy::string()), - TyTemplate::Concrete(RuntimeTy::unknown()), + TyTemplate::from(RealizedTy::string()), + TyTemplate::from(RealizedTy::unknown()), ), } } @@ -5119,7 +5496,7 @@ impl LoweringContext<'_> { self.builder.assign( Place::local(parts_local), // Tagged-template literal parts are always strings. - Rvalue::Array(TyTemplate::Concrete(RuntimeTy::string()), parts_ops), + Rvalue::Array(TyTemplate::from(RealizedTy::string()), parts_ops), ); // Interps lower in the closure scope (body-param refs → @@ -5152,7 +5529,7 @@ impl LoweringContext<'_> { self.builder.assign( Place::local(values_local), // Tagged-template interpolated values are heterogeneous. - Rvalue::Array(TyTemplate::Concrete(RuntimeTy::unknown()), value_ops), + Rvalue::Array(TyTemplate::from(RealizedTy::unknown()), value_ops), ); self.builder.assign( @@ -9071,7 +9448,7 @@ impl LoweringContext<'_> { /// /// `Tir2Ty::TypeVar("T")` whose name appears at position `N` in /// `generic_params` maps to `TyTemplate::TypeArgRef(N)`. All other types - /// recurse structurally and bottom out at `TyTemplate::Concrete(...)`. + /// recurse structurally and bottom out at fully-realized leaves. fn ty_to_template(&self, ty: &Tir2Ty, generic_params: &[baml_base::Name]) -> TyTemplate { // Delegate to the free `tir2_to_template` so the two routines can never // drift apart again (C1). They were previously byte-for-byte twins; a @@ -10388,21 +10765,23 @@ impl<'db> LoweringContext<'db> { all_args.push(Operand::Copy(Place::Local(recv_local))); all_args.extend(arg_ops); // Non-generic interfaces (`Equals`/`Compare`) carry empty args/assoc; a - // parameterized interface bakes its (here runtime-converted) arguments - // into the template. The VM threads these to the resolver to disambiguate - // a type implementing the same interface at several instantiations. - let iface_template = TyTemplate::Concrete(RuntimeTy::Interface( - iface_tn.clone(), - iface_type_args - .iter() - .map(|a| self.convert_tir_ty_for_runtime(a)) - .collect(), - iface_assoc - .iter() - .map(|(name, ty)| (name.clone(), self.convert_tir_ty_for_runtime(ty))) - .collect(), - TyAttr::default(), - )); + // parameterized interface bakes its arguments into the template. The + // template reaches the VM through `LoadType` — which substitutes the + // caller's frame type args — so an interface arg that is an enclosing + // generic (`Lorem` inside a generic fn) lowers to its `TypeArgRef` + // slot and arrives at the resolver realized, disambiguating a type + // implementing the same interface at several instantiations. + let generic_params = self.enclosing_generic_params(); + let iface_template = tir2_to_template( + &Tir2Ty::Interface( + iface_tn.clone(), + iface_type_args.to_vec(), + iface_assoc.to_vec(), + TyAttr::default(), + ), + self.resolved_aliases, + &generic_params, + ); let unwind = self.catch_context.as_ref().map(|c| c.unwind_target); let runtime_id_operand = self.lower_runtime_id_operand(runtime_id); let resume = self.builder.create_block(); @@ -12788,14 +13167,10 @@ impl<'db> LoweringContext<'db> { failure: BlockId, ) { let ty_template = match guard { - InterfaceClassGuard::Any => TyTemplate::Concrete(RuntimeTy::Class( - impl_tn.clone(), - Vec::new(), - TyAttr::default(), - )), + InterfaceClassGuard::Any => TyTemplate::class(impl_tn.clone(), Vec::new()), InterfaceClassGuard::Exact(args) => { let generic_params = self.enclosing_generic_params(); - TyTemplate::Class( + TyTemplate::class( impl_tn.clone(), args.iter() .map(|arg| match arg { @@ -13750,20 +14125,44 @@ impl LoweringContext<'_> { .iter() .map(|arm| (arm.pattern, arm.body, arm.guard)) .collect(); - if self.try_lower_as_switch( + + // Expose the scrutinee's static type to each arm's container type test so + // it can decide whether a coarse `LIST`/`MAP` tag suffices; restore the + // enclosing match's scrutinee (if any) once the arms are lowered. + let saved_scrutinee = self + .match_scrutinee + .replace((scrutinee_local, self.expr_ty(scrutinee))); + + let switched = self.try_lower_as_switch( scrutinee_local, &switch_arms, dest.clone(), bb_join, SwitchOtherwise::Match { is_exhaustive }, None, - ) { - self.builder.set_current_block(bb_join); - return; + ); + if !switched { + // Whether any non-final arm emits an invariance-sensitive test + // (typevar guard or element-discriminating container test). Such + // tests are stricter than the lax container relation the static + // exhaustiveness proof used, so the final arm's test must be + // emitted (with a trap on fall-through) instead of skipped. + let backstop_last_arm = arms.len().checked_sub(1).is_some_and(|last| { + arms[..last] + .iter() + .any(|arm| self.pattern_test_invariance_sensitive(arm.pattern)) + }); + self.lower_match_chain( + scrutinee_local, + &arms, + dest, + bb_join, + is_exhaustive, + backstop_last_arm, + ); } - self.lower_match_chain(scrutinee_local, &arms, dest, bb_join, is_exhaustive); - + self.match_scrutinee = saved_scrutinee; self.builder.set_current_block(bb_join); } @@ -13797,6 +14196,16 @@ impl LoweringContext<'_> { } ); + // The enclosing match scrutinee's static type, for the tag-sufficiency + // gate in `classify_pattern_type_tag`. Guarded on the local so the + // catch path — whose error local is never the registered match + // scrutinee — stays conservative (`None`). + let scrutinee_static_ty: Option = self + .match_scrutinee + .as_ref() + .filter(|(local, _)| *local == scrutinee) + .map(|(_, ty)| ty.clone()); + // Classify arms: collect (i64_value, arm_index) for int literal or enum variant // patterns, and check for a trailing wildcard/binding. let mut switch_kind: Option = None; @@ -13818,7 +14227,7 @@ impl LoweringContext<'_> { Some(SwitchKind::TypeTag) => {} Some(_) => return false, } - match self.classify_pattern_type_tag(pattern) { + match self.classify_pattern_type_tag(pattern, scrutinee_static_ty.as_ref()) { Some(tags) => { for tag in tags { if seen_values.insert(tag) { @@ -13907,7 +14316,8 @@ impl LoweringContext<'_> { Some(SwitchKind::TypeTag) => {} Some(_) => return false, } - match this.classify_pattern_type_tag(atom_id) { + match this.classify_pattern_type_tag(atom_id, scrutinee_static_ty.as_ref()) + { Some(tags) => { for tag in tags { if seen_values.insert(tag) { @@ -14209,6 +14619,7 @@ impl LoweringContext<'_> { dest: Place, join: BlockId, exhaustive: bool, + backstop_last_arm: bool, ) { if arms.is_empty() { // No more arms to test. Either a preceding wildcard/binding arm @@ -14230,6 +14641,28 @@ impl LoweringContext<'_> { && arm.guard.is_none() && !matches!(self.body.patterns[arm.pattern], AstPattern::Or(_)) { + // When a preceding arm's test is invariance-sensitive + // (`backstop_last_arm`), "it must match" no longer follows from the + // static exhaustiveness proof: that proof used TIR's lax + // (element-covariant) container relation, so a laxly-admitted value + // (e.g. an `int[]` flowing into a `(int | string)[]` slot) can fail + // every arm at runtime under the invariant tests. Emit the final + // refutable arm's test anyway and trap on fall-through — a loud + // panic instead of silently binding the value to a pattern it does + // not match. Irrefutable last arms (wildcard, bare bind) match + // everything, so they keep the plain skip. + let last_is_refutable = !matches!( + self.body.patterns[arm.pattern], + AstPattern::Wildcard | AstPattern::Bind { subpat: None, .. } + ); + if backstop_last_arm && last_is_refutable { + let bb_body = self.builder.create_block(); + let bb_trap = self.builder.create_block(); + self.lower_pattern_test(scrutinee, arm.pattern, bb_body, bb_trap); + self.builder.set_current_block(bb_trap); + self.builder.unreachable(); + self.builder.set_current_block(bb_body); + } let saved_locals = self.locals.clone(); let watched_depth = self.watched_locals_stack.len(); self.bind_pattern(scrutinee, arm.pattern); @@ -14282,7 +14715,7 @@ impl LoweringContext<'_> { } self.builder.set_current_block(bb_next); - self.lower_match_chain(scrutinee, rest, dest, join, exhaustive); + self.lower_match_chain(scrutinee, rest, dest, join, exhaustive, backstop_last_arm); return; } @@ -14310,7 +14743,53 @@ impl LoweringContext<'_> { self.restore_locals_after_scope(saved_locals, watched_depth); self.builder.set_current_block(bb_next); - self.lower_match_chain(scrutinee, rest, dest, join, exhaustive); + self.lower_match_chain(scrutinee, rest, dest, join, exhaustive, backstop_last_arm); + } + + /// Whether lowering `pat`'s runtime test emits an *invariance-sensitive* + /// check — a typevar-carrying dispatch-guard template or an + /// element-discriminating (not tag-sufficient) container test. Such tests + /// are stricter than the lax (element-covariant) container relation TIR's + /// exhaustiveness proof uses, so an exhaustive match containing one cannot + /// safely skip its final arm's test (see `lower_match_chain`'s backstop). + /// + /// Over-approximation is safe — it costs one extra final test plus a dead + /// trap block; under-approximation would silently bind a value to a pattern + /// it does not match. + fn pattern_test_invariance_sensitive(&self, pat_id: AstPatId) -> bool { + match &self.body.patterns[pat_id] { + AstPattern::Or(parts) => parts + .iter() + .any(|p| self.pattern_test_invariance_sensitive(*p)), + AstPattern::Bind { + subpat: Some(sp), .. + } => self.pattern_test_invariance_sensitive(*sp), + // Irrefutable patterns emit no test. + AstPattern::Wildcard | AstPattern::Bind { subpat: None, .. } => false, + AstPattern::Type(_) | AstPattern::Class { .. } | AstPattern::Array { .. } => { + let Some(tir_ty) = self.tir_pat_type(self.pat_metadata_key(pat_id)) else { + return false; + }; + // Typevar-carrying patterns route through the dispatch-guard + // template (see `lower_pattern_test`), whose realized-binding + // comparison is invariant at argument positions. + if baml_compiler2_tir::generics::contains_typevar(tir_ty) { + return true; + } + // Container arms are sensitive exactly when the emitter checks + // their elements structurally — i.e. when the coarse tag is not + // provably sufficient for this match's scrutinee. + let resolved = self.resolved_aliases.convert(tir_ty); + let scrutinee_ty = self.match_scrutinee.as_ref().map(|(_, ty)| ty); + let mut members = Vec::new(); + flatten_runtime_union(&resolved, &mut members); + members.iter().any(|m| match m { + RuntimeTy::List(..) | RuntimeTy::Map { .. } => !scrutinee_ty + .is_some_and(|scrutinee| container_arm_tag_sufficient(m, scrutinee)), + _ => false, + }) + } + } } /// Emit an `IsType` check that handles union types by expanding them @@ -14364,11 +14843,28 @@ impl LoweringContext<'_> { } } else { // Convert RuntimeTy → TyTemplate so the emitter can handle generic class - // checks (RuntimeTy::Class with args containing TypeVars map to - // TyTemplate::Class with TypeArgRef leaves). For non-generic types - // the template is TyTemplate::Concrete(ty) — the emitter falls back - // to the same fast path as before. + // checks (RuntimeTy::Class with concrete args maps to a `Class` + // template the emitter lowers to `ClassWithTypeArgs`). For non-generic + // types the template is a realized leaf — the emitter's tag / + // class-identity fast path. let ty_template = ty_to_template_from_resolved_ty(&ty); + // If this is a container arm whose element the emitter would otherwise + // check structurally, but the enclosing match's scrutinee statically + // proves a coarse `LIST`/`MAP` tag suffices, wildcard the element so + // the emitter keeps the cheap tag. Guarded on the scrutinee local so + // an `is` / nested test against a different value stays element-precise. + let ty_template = + if self + .match_scrutinee + .as_ref() + .is_some_and(|(local, scrutinee_ty)| { + *local == scrutinee && container_arm_tag_sufficient(&ty, scrutinee_ty) + }) + { + wildcard_container_elements(ty_template) + } else { + ty_template + }; self.emit_is_type_template_branch(scrutinee, ty_template, success, failure); } } @@ -14668,6 +15164,13 @@ impl LoweringContext<'_> { RuntimeTy::Null { .. } => Some(baml_type::typetag::NULL), RuntimeTy::Float { .. } => Some(baml_type::typetag::FLOAT), RuntimeTy::Uint8Array { .. } => Some(baml_type::typetag::UINT8ARRAY), + // The single shared ENUM tag conflates *all* enum types (and all + // variants of one enum). That's safe on the switch path only because + // `switch_member_tag_sufficient` bails an enum-type arm to the precise + // chain whenever a second enum type shares this tag, so `Color | + // Status` no longer dedups the second arm away; the chain tests + // enum-pointer identity (`is Color`). A single-enum-type switch keeps + // the fast tag. RuntimeTy::Enum(..) | RuntimeTy::EnumVariant(..) => Some(baml_type::typetag::ENUM), RuntimeTy::List(..) => Some(baml_type::typetag::LIST), RuntimeTy::Map { .. } => Some(baml_type::typetag::MAP), @@ -15081,12 +15584,17 @@ impl LoweringContext<'_> { self.emit_is_tir_type_branch(scrutinee, &pat_tir_ty, success, failure); return; } - // A class pattern type still carrying the enclosing - // function's TypeVars (e.g. `let e: AllFailed` inside - // `any`) must NOT go through `convert_tir_ty_for_runtime` — - // that erases TypeVar → Void and the test becomes - // constant-false. Build a template instead so the args - // resolve against the frame. + // A pattern type still carrying the enclosing function's + // TypeVars — a bare `T`, `T[]`, `map<_, T>`, a class like + // `AllFailed` inside `any`, or a union thereof — + // must NOT go through `convert_tir_ty_for_runtime`: that + // erases TypeVar → Void and the test becomes constant-false + // (a silent arm miss). Build a template instead so each + // typevar resolves against `frame.type_args` at runtime and + // the value is compared against the *realized* binding + // (TYPE_SYSTEM.md "Type Variables": at any run-time usage + // site a type variable corresponds to exactly one realized + // type). // // Use the dispatch-guard template (frame TypeVar → // `TypeArgRefOrWildcard`, subtype-or-wildcard) rather than the @@ -15103,8 +15611,18 @@ impl LoweringContext<'_> { // its typevar args with `tir2_to_dispatch_guard_template`. // Directionality is preserved: a strictly wider runtime arg // still fails to match a narrower pinned `T`. - if matches!(&pat_tir_ty, Tir2Ty::Class(..)) - && baml_compiler2_tir::generics::contains_typevar(&pat_tir_ty) + // + // `Self`-carrying patterns are excluded: `Self` has no frame + // slot yet, so the guard builder would lower it to a + // match-anything `Wildcard` — over-matching is strictly worse + // than today's constant-false. The `Self` units replace this + // (class bodies substitute the enclosing class; interface + // default methods gain a frame slot). + if baml_compiler2_tir::generics::contains_typevar(&pat_tir_ty) + && !baml_compiler2_tir::generics::contains_typevar_where( + &pat_tir_ty, + &|name| name.as_str() == "Self", + ) { let generic_params = self.enclosing_generic_params(); let template = tir2_to_dispatch_guard_template( @@ -15606,12 +16124,21 @@ impl LoweringContext<'_> { /// Classify a pattern into type tag value(s) for switch dispatch. /// Classify a pattern as type-tag-eligible and return its tag(s). /// - /// Shared by match and catch lowering. + /// Shared by match and catch lowering. `scrutinee_static_ty` is the + /// enclosing match scrutinee's resolved static type when known (`None` on + /// the catch path, whose error value has no useful static type); the + /// tag-sufficiency gate uses it to disqualify arms whose coarse tag would + /// conflate values the scrutinee admits (see + /// [`Self::ty_to_type_tags_for_switch`]). /// /// Returns `Some(tags)` for `TypedBinding` and Binding-with-TIR-type patterns /// that resolve to primitive or class types. Returns `None` for literals, /// wildcards, enum variants, and types without tag mappings. - fn classify_pattern_type_tag(&self, pat_id: AstPatId) -> Option> { + fn classify_pattern_type_tag( + &self, + pat_id: AstPatId, + scrutinee_static_ty: Option<&RuntimeTy>, + ) -> Option> { let pat = &self.body.patterns[pat_id]; if self.pattern_contains_structural(pat_id) { return None; @@ -15648,30 +16175,33 @@ impl LoweringContext<'_> { if let Some(ty_expr) = ascription_ty { if let Some(tir_ty) = self.tir_pat_type(self.pat_metadata_key(pat_id)) { let resolved = self.resolved_aliases.convert(tir_ty); - if let Some(tags) = self.ty_to_type_tags(&resolved) { + if let Some(tags) = self.ty_to_type_tags_for_switch(&resolved, scrutinee_static_ty) + { return Some(tags); } } let resolved = self.resolve_type_annotation(&ty_expr); - return self.ty_to_type_tags(&resolved); + return self.ty_to_type_tags_for_switch(&resolved, scrutinee_static_ty); } match pat { AstPattern::Wildcard => None, AstPattern::Bind { .. } => { let tir_ty = self.tir_pat_type(self.pat_metadata_key(pat_id))?; let resolved = self.resolved_aliases.convert(tir_ty); - self.ty_to_type_tags(&resolved) + self.ty_to_type_tags_for_switch(&resolved, scrutinee_static_ty) } AstPattern::Type(_) => { if let Some(tir_ty) = self.tir_pat_type(self.pat_metadata_key(pat_id)) { let resolved = self.resolved_aliases.convert(tir_ty); - if let Some(tags) = self.ty_to_type_tags(&resolved) { + if let Some(tags) = + self.ty_to_type_tags_for_switch(&resolved, scrutinee_static_ty) + { return Some(tags); } } if let AstPattern::Type(ty_expr) = pat { let resolved = self.resolve_type_annotation(ty_expr); - return self.ty_to_type_tags(&resolved); + return self.ty_to_type_tags_for_switch(&resolved, scrutinee_static_ty); } None } @@ -15679,6 +16209,29 @@ impl LoweringContext<'_> { } } + /// [`Self::ty_to_type_tags`], additionally requiring every flattened member + /// of `ty` to be *tag-sufficient* for a switch keyed on those tags (see + /// [`switch_member_tag_sufficient`]): a coarse tag may only key a jump-table + /// arm when it cannot conflate values the scrutinee admits onto the wrong + /// arm — `int[]` and `string[]` share the LIST tag, `Foo` and + /// `Foo` share `Foo`'s tag, and the tag dedup would silently drop + /// the second arm. An insufficient member disqualifies the pattern + /// (→ `None`), which makes `try_lower_as_switch` fall back to the precise + /// sequential chain. + fn ty_to_type_tags_for_switch( + &self, + ty: &RuntimeTy, + scrutinee_static_ty: Option<&RuntimeTy>, + ) -> Option> { + let tags = self.ty_to_type_tags(ty)?; + let mut members = Vec::new(); + flatten_runtime_union(ty, &mut members); + members + .iter() + .all(|m| switch_member_tag_sufficient(m, scrutinee_static_ty)) + .then_some(tags) + } + /// Convert a `RuntimeTy` to the list of type tag integers it corresponds to. /// Returns `None` if the type has no simple tag representation. /// diff --git a/baml_language/crates/baml_compiler2_tir/src/builder.rs b/baml_language/crates/baml_compiler2_tir/src/builder.rs index 6a35815a4e1..808e7673f21 100644 --- a/baml_language/crates/baml_compiler2_tir/src/builder.rs +++ b/baml_language/crates/baml_compiler2_tir/src/builder.rs @@ -1021,6 +1021,20 @@ impl<'db> TypeInferenceBuilder<'db> { ty_expr: &TypeExpr, diags: &mut Vec, ) -> Ty { + // `Self` is signature-only; in a method body it has no substitution (see + // `self_reference_span`). Reject it here — the sole choke every non- + // pattern body type annotation passes through — rather than let it + // resolve to a stray `TypeVar("Self")` (interface default methods, where + // `"Self"` is in `type_bindings`). The pattern and turbofish paths do + // their own inline lowering, so they guard separately. The silent + // natural-type caller drops `diags`, but the same annotation is reported + // once via its pattern/expression path. + if let Some(span) = crate::lower_type_expr::self_reference_span(ty_expr) { + diags.push(TirTypeError::SelfInBodyPosition { span }); + return Ty::Error { + attr: TyAttr::default(), + }; + } if self.type_bindings.is_empty() { crate::lower_type_expr::lower_type_expr_in_ns( self.context.db(), @@ -2198,6 +2212,24 @@ impl<'db> TypeInferenceBuilder<'db> { let caller_generic_params = self.generic_params.clone(); let suppress_diags = self.is_auto_derived_body; for (param_name, type_arg_expr) in declared_params.iter().zip(type_args.iter()) { + // `type_of()` and other body turbofishes on `Self`: reject it, + // mirroring the body funnel. This inline path uses the caller's + // `generic_params`, where an interface default method's `"Self"` + // would otherwise resolve to a stray `TypeVar("Self")` and erase at + // runtime. + if let Some(span) = crate::lower_type_expr::self_reference_span(type_arg_expr) { + if !suppress_diags { + self.context + .report_simple(TirTypeError::SelfInBodyPosition { span }, call_expr_id); + } + bindings.insert( + param_name.clone(), + Ty::Error { + attr: TyAttr::default(), + }, + ); + continue; + } let mut diags = Vec::new(); let ty = crate::lower_type_expr::lower_type_expr_in_ns( db, @@ -8964,6 +8996,20 @@ impl<'db> TypeInferenceBuilder<'db> { } } } + // A pattern type-test on `Self` (`match (x) { let s: Self => … }`, + // `x is Self`): reject it, mirroring the body funnel — this inline path + // uses `generic_params`, where an interface default method's `"Self"` + // would otherwise resolve to a stray `TypeVar("Self")`. + if let Some(span) = crate::lower_type_expr::self_reference_span(ty) { + self.report_at_pat_or_expr( + TirTypeError::SelfInBodyPosition { span }, + pat_id, + fallback_expr, + ); + return Ty::Error { + attr: TyAttr::default(), + }; + } let mut diags = Vec::new(); let resolved = crate::lower_type_expr::lower_type_expr_in_ns( self.context.db(), diff --git a/baml_language/crates/baml_compiler2_tir/src/infer_context.rs b/baml_language/crates/baml_compiler2_tir/src/infer_context.rs index 7df76a1df29..47dfc88f4b6 100644 --- a/baml_language/crates/baml_compiler2_tir/src/infer_context.rs +++ b/baml_language/crates/baml_compiler2_tir/src/infer_context.rs @@ -428,6 +428,15 @@ pub enum TirTypeError { /// BEP-044: a generic parameter's bound (``) resolved to a /// concrete non-interface type. Generic bounds must be interfaces. GenericBoundNotInterface { bound: Ty }, + /// `Self` used in a method *body* type position (a `let`/cast annotation, a + /// match/`is` pattern type, or a `type_of()` turbofish). `Self` is + /// only supported in signatures for now; a body has no substitution for it, + /// so it is rejected here rather than silently resolving to a type variable + /// (interface default methods) or an unresolved name (class bodies). + SelfInBodyPosition { + /// Span of the offending `Self` reference. + span: TextRange, + }, } impl TirTypeError { @@ -441,6 +450,9 @@ impl TirTypeError { TirTypeError::UnresolvedType { span, .. } if *span != TextRange::default() => { Some(*span) } + TirTypeError::SelfInBodyPosition { span } if *span != TextRange::default() => { + Some(*span) + } _ => None, } } @@ -653,6 +665,13 @@ impl fmt::Display for TirTypeError { ) } } + TirTypeError::SelfInBodyPosition { .. } => { + write!( + f, + "`Self` is only supported in a signature, not in a method body. \ + In a class method, name the enclosing type explicitly (e.g. `Box`)." + ) + } TirTypeError::ArgumentCountMismatch { expected, got } => { write!(f, "expected {expected} argument(s), got {got}") } diff --git a/baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs b/baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs index 1f1d4c9fdcd..6cec4aa2c4b 100644 --- a/baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs +++ b/baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs @@ -244,6 +244,68 @@ fn substitute_paths_walk( /// `replacement`. Used by signature lowering to pre-resolve `Self` to /// the enclosing class/interface's type expression before regular /// resolution runs. +/// The source span of the first `Self` reference appearing anywhere in +/// `type_expr` (a `Path` whose leading segment is `Self`, at any nesting depth), +/// or `None` if it mentions no `Self`. +/// +/// `Self` is a signature-only construct: every signature/declaration site +/// resolves it up front with `substitute_self` against a replacement it +/// supplies (the enclosing type, the impl receiver, or the interface's `Self` +/// bound). A method *body* has no such replacement — a class body cannot name +/// its own instantiation as a value type, and an interface default method +/// cannot name the implementor — so body-position type lowering uses this to +/// reject `Self` with a purposeful diagnostic instead of leaving it to resolve +/// to a misbehaving type variable (interface default methods) or a bare +/// "unresolved type" (class bodies). Mirrors `substitute_self`'s traversal so +/// no `Self`-bearing position is missed. +pub fn self_reference_span(type_expr: &TypeExpr) -> Option { + match &type_expr.kind { + TypeExprKind::Path { + segments, + generic_args, + associated_type_bindings, + .. + } => { + if segments.first().is_some_and(|s| s.as_str() == "Self") { + return Some(type_expr.span); + } + generic_args + .iter() + .find_map(self_reference_span) + .or_else(|| { + associated_type_bindings + .iter() + .find_map(|binding| self_reference_span(&binding.ty)) + }) + } + TypeExprKind::AssociatedTypeProjection { + base, interface, .. + } => self_reference_span(base).or_else(|| { + interface + .as_ref() + .and_then(|interface| self_reference_span(interface)) + }), + TypeExprKind::List { inner, .. } | TypeExprKind::Optional { inner, .. } => { + self_reference_span(inner) + } + TypeExprKind::Map { key, value, .. } => { + self_reference_span(key).or_else(|| self_reference_span(value)) + } + TypeExprKind::Union { variants, .. } => variants.iter().find_map(self_reference_span), + TypeExprKind::Function { + params, + ret, + throws, + .. + } => params + .iter() + .find_map(|param| self_reference_span(¶m.ty)) + .or_else(|| self_reference_span(ret)) + .or_else(|| throws.as_ref().and_then(|ty| self_reference_span(ty))), + _ => None, + } +} + fn substitute_self(type_expr: &TypeExpr, replacement: &TypeExpr) -> TypeExpr { match &type_expr.kind { TypeExprKind::Path { diff --git a/baml_language/crates/baml_lsp2_actions/src/check.rs b/baml_language/crates/baml_lsp2_actions/src/check.rs index 5f1d05a5823..5c90728e547 100644 --- a/baml_language/crates/baml_lsp2_actions/src/check.rs +++ b/baml_language/crates/baml_lsp2_actions/src/check.rs @@ -6191,6 +6191,7 @@ fn tir_type_error_to_diagnostic_id( TirTypeError::ArrayFilledMutableLiteralAliasing => DiagnosticId::ArrayFilledAliasing, TirTypeError::InvalidUnaryOp { .. } => DiagnosticId::InvalidOperator, TirTypeError::UnresolvedType { .. } => DiagnosticId::UnknownType, + TirTypeError::SelfInBodyPosition { .. } => DiagnosticId::UnknownType, TirTypeError::NonInterfaceProjectionQualifier => DiagnosticId::TypeMismatch, TirTypeError::UnknownAssociatedType { .. } => DiagnosticId::UnknownType, TirTypeError::AmbiguousAssociatedTypeProjection { .. } => DiagnosticId::TypeMismatch, diff --git a/baml_language/crates/baml_tests/baml_src/ns_generic_match_subtype/generic_match_subtype.baml b/baml_language/crates/baml_tests/baml_src/ns_generic_match_subtype/generic_match_subtype.baml index 72150c69b28..e7fa7058035 100644 --- a/baml_language/crates/baml_tests/baml_src/ns_generic_match_subtype/generic_match_subtype.baml +++ b/baml_language/crates/baml_tests/baml_src/ns_generic_match_subtype/generic_match_subtype.baml @@ -120,3 +120,46 @@ function concrete_discriminates_string_fn() -> string { test "generic_match_concrete_discriminates_string" { assert.equal(concrete_discriminates_string_fn(), "string") } + +// ─── 5. Concrete parametric arms on the TypeTag switch path ─────────────────── +// The same discrimination as §4, but with enough tag-eligible arms that the +// lowering prefers the TypeTag jump-table switch (4+ *distinct* tags — +// colliding tags dedup before the count, so this needs five arms). A class's +// type tag ignores its type args, so before the switch tag-sufficiency gate +// `Foo` and `Foo` shared one tag: the jump table kept only the +// first `Foo` arm and every `Foo` value jumped there. The gate detects the +// collision and falls back to the (arg-precise) chain. + +function pick_concrete_switch(x: Foo | Foo | int | string | bool) -> string { + match (x) { + let a: Foo => "int", + let b: Foo => "string", + let n: int => "prim int", + let s: string => "prim string", + let f: bool => "prim bool", + } +} + +function concrete_switch_int_fn() -> string { + pick_concrete_switch(Foo { value: 1 }) +} + +test "generic_match_switch_discriminates_int" { + assert.equal(concrete_switch_int_fn(), "int") +} + +function concrete_switch_string_fn() -> string { + pick_concrete_switch(Foo { value: "x" }) +} + +test "generic_match_switch_discriminates_string" { + assert.equal(concrete_switch_string_fn(), "string") +} + +function concrete_switch_prim_fn() -> string { + pick_concrete_switch("hello") +} + +test "generic_match_switch_prims_still_dispatch" { + assert.equal(concrete_switch_prim_fn(), "prim string") +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_generic_match_typevar/generic_match_typevar.baml b/baml_language/crates/baml_tests/baml_src/ns_generic_match_typevar/generic_match_typevar.baml new file mode 100644 index 00000000000..5ab4532e927 --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_generic_match_typevar/generic_match_typevar.baml @@ -0,0 +1,144 @@ +// End-to-end tests for typevar-carrying `match` arms and `is` tests: bare `T`, +// `T[]`, and `map`. +// +// A typevar arm compares the value against the *realized* type bound to the +// typevar in the enclosing call frame (TYPE_SYSTEM.md "Type Variables": at any +// run-time usage site a type variable corresponds to exactly one realized +// type). Before the structural-routing fix, a bare `T` arm lowered to a +// constant-false test (silent arm miss — the value fell through to a later +// arm), and a `T[]` arm lowered to the coarse "any list" tag. +// +// Note the arms are genuinely dynamic: `pick("hi")` takes the `T` arm, +// not the later `string` arm, because with `T = string` the value is a member +// of the first arm's type-set. This is intended set-membership semantics, not +// an ordering bug. +// +// BUG(static-coverage): the unreachable-arm analysis treats a typevar arm as +// covering the whole scrutinee, so an unguarded arm after `let t: T` is +// rejected as unreachable (E0063) — even though at runtime `T` covers only its +// realized type and later arms are reachable (e.g. `pick("hi")` must reach +// the `string` arm). The `if true` guards below exist solely to keep the later +// arms statically reachable; remove them when the coverage analysis models +// typevar arms as covering only their own type-set. + +// ─── 1. bare `T` arm (non-last), both polarities ────────────────────────────── + +function pick(x: T | string) -> string { + match (x) { + let t: T if true => "T", + let s: string => "string", + _ => "other", + } +} + +function pick_int_value_fn() -> string { + pick(42) +} + +test "typevar_arm_matches_realized_int" { + assert.equal(pick_int_value_fn(), "T") +} + +function pick_string_value_fn() -> string { + pick("hi") +} + +test "typevar_arm_rejects_non_member" { + assert.equal(pick_string_value_fn(), "string") +} + +function pick_dynamic_capture_fn() -> string { + pick("hi") +} + +test "typevar_arm_dynamic_first_match_capture" { + assert.equal(pick_dynamic_capture_fn(), "T") +} + +// ─── 2. `T[]` arm: element compared against the realized binding ────────────── + +// (No trailing wildcard here: the coverage analysis deems the `string[]` arm +// to cover the `T[]` member too, so a wildcard after it is rejected as +// unreachable.) +function pick_list(x: T[] | string[]) -> string { + match (x) { + let t: T[] if true => "T[]", + let s: string[] => "string[]", + } +} + +function pick_list_int_fn() -> string { + pick_list([1, 2]) +} + +test "typevar_list_arm_matches_realized_element" { + assert.equal(pick_list_int_fn(), "T[]") +} + +function pick_list_string_fn() -> string { + pick_list(["a", "b"]) +} + +test "typevar_list_arm_rejects_other_element" { + assert.equal(pick_list_string_fn(), "string[]") +} + +function pick_list_dynamic_fn() -> string { + pick_list(["a", "b"]) +} + +test "typevar_list_arm_dynamic_first_match_capture" { + assert.equal(pick_list_dynamic_fn(), "T[]") +} + +// ─── 3. `map` arm ────────────────────────────────────────────────── + +// (No trailing wildcard here: the coverage analysis deems the +// `map` arm to cover the `map` member too, so a +// wildcard after it is rejected as unreachable.) +function pick_map(x: map | map) -> string { + match (x) { + let t: map if true => "map", + let s: map => "map", + } +} + +function pick_map_int_fn() -> string { + let m: map = { "k": 1 }; + pick_map(m) +} + +test "typevar_map_arm_matches_realized_value_type" { + assert.equal(pick_map_int_fn(), "map") +} + +function pick_map_string_fn() -> string { + let m: map = { "k": "v" }; + pick_map(m) +} + +test "typevar_map_arm_rejects_other_value_type" { + assert.equal(pick_map_string_fn(), "map") +} + +// ─── 4. `is T` ──────────────────────────────────────────────────────────────── + +function is_t(x: T | string) -> bool { + x is T +} + +function is_t_member_fn() -> bool { + is_t(42) +} + +test "is_typevar_member" { + assert.is_true(is_t_member_fn()) +} + +function is_t_non_member_fn() -> bool { + is_t("hi") +} + +test "is_typevar_non_member" { + assert.is_true(!is_t_non_member_fn()) +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_match_container_types/match_container_types.baml b/baml_language/crates/baml_tests/baml_src/ns_match_container_types/match_container_types.baml new file mode 100644 index 00000000000..2a6fc43738e --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_match_container_types/match_container_types.baml @@ -0,0 +1,229 @@ +// End-to-end tests for element-discriminating container type tests in `match` +// and `is`. +// +// Before the MIR/emit structural-routing fix, `int[]` and `string[]` both +// lowered their runtime type test to a single coarse `LIST` type tag (element +// type discarded), so a `string[]` value wrongly matched an `int[]` arm — and a +// `map` arm accepted a `map` value. The fix routes an +// element-discriminating container template through the VM's structural value +// matcher (`ConstValue::Type`), which relates generic-argument positions +// invariantly: `int[]` is not `string[]`, `map` is not +// `map`. +// +// Locals holding container values are fine inside functions; test bodies call +// top-level helpers (the VM local-boxing caveat only bites class instances). + +// ─── 1. list element discrimination via `match` (2-arm chain) ───────────────── + +function classify_list(x: int[] | string[]) -> string { + match (x) { + let a: int[] => "ints", + let b: string[] => "strings", + } +} + +function classify_int_list_fn() -> string { + classify_list([1, 2, 3]) +} + +test "match_container_int_list" { + assert.equal(classify_int_list_fn(), "ints") +} + +function classify_string_list_fn() -> string { + classify_list(["a", "b"]) +} + +test "match_container_string_list" { + assert.equal(classify_string_list_fn(), "strings") +} + +// ─── 2. list element discrimination via `is` ────────────────────────────────── + +function int_list_is_int_array_fn() -> bool { + let x: int[] | string[] = [1, 2, 3]; + x is int[] +} + +test "is_int_list_is_int_array" { + assert.is_true(int_list_is_int_array_fn()) +} + +function string_list_is_int_array_fn() -> bool { + let x: int[] | string[] = ["a", "b"]; + x is int[] +} + +test "is_string_list_is_not_int_array" { + assert.is_true(!string_list_is_int_array_fn()) +} + +// ─── 3. map value-type discrimination via `match` ───────────────────────────── + +function classify_map(x: map | map) -> string { + match (x) { + let a: map => "int values", + let b: map => "string values", + } +} + +function classify_int_map_fn() -> string { + let m: map = { "k": 1 }; + classify_map(m) +} + +test "match_container_int_map" { + assert.equal(classify_int_map_fn(), "int values") +} + +function classify_string_map_fn() -> string { + let m: map = { "k": "v" }; + classify_map(m) +} + +test "match_container_string_map" { + assert.equal(classify_string_map_fn(), "string values") +} + +// ─── 4. list element discrimination on the TypeTag switch path ──────────────── +// With enough tag-eligible arms the lowering prefers the TypeTag jump-table +// switch (4+ *distinct* tags — colliding tags dedup before the count, so this +// needs five arms). Before the switch tag-sufficiency gate, `int[]` and +// `string[]` collapsed to one coarse LIST tag: the jump table kept only the +// first list arm and every list value jumped there, so a `string[]` value +// silently took the `int[]` arm. The gate detects the collision and falls back +// to the (element-precise) chain. + +function classify_switch(x: int[] | string[] | int | string | bool) -> string { + match (x) { + let a: int[] => "ints", + let b: string[] => "strings", + let n: int => "int", + let s: string => "string", + let f: bool => "bool", + } +} + +function classify_switch_int_list_fn() -> string { + classify_switch([1, 2, 3]) +} + +test "match_switch_int_list" { + assert.equal(classify_switch_int_list_fn(), "ints") +} + +function classify_switch_string_list_fn() -> string { + classify_switch(["a", "b"]) +} + +test "match_switch_string_list" { + assert.equal(classify_switch_string_list_fn(), "strings") +} + +function classify_switch_prims_fn() -> string { + classify_switch(42) +} + +test "match_switch_prims_still_dispatch" { + assert.equal(classify_switch_prims_fn(), "int") +} + +// ─── 5. invariance pins ─────────────────────────────────────────────────────── +// Generic-argument positions are invariant (TYPE_SYSTEM.md "Variance"): a list +// whose runtime element type is `int` is NOT a member of `(int | string)[]`, +// while a list constructed *at* element type `int | string` (the literal adopts +// the ascribed element type at construction) is. +// +// BUG(static-coverage): the unreachable-arm analysis uses a lax +// (element-covariant) container relation, so *either* ordering of these two +// arms flags the second as unreachable — even though the runtime relation is +// invariant and each arm captures exactly one of the two list types. The +// `if true` guard exists solely to keep the second arm statically reachable; +// remove it when container coverage goes invariant. + +function classify_union_element(u: (int | string)[] | int[]) -> string { + match (u) { + let i: int[] if true => "ints", + let m: (int | string)[] => "mixed", + } +} + +function int_list_vs_union_element_arm_fn() -> string { + let a: int[] = [1, 2]; + classify_union_element(a) +} + +test "match_invariance_int_list_not_member_of_union_element_arm" { + assert.equal(int_list_vs_union_element_arm_fn(), "ints") +} + +function union_element_constructed_matches_fn() -> string { + let v: (int | string)[] = [1, 2]; + classify_union_element(v) +} + +test "match_invariance_union_element_constructed_list_matches" { + assert.equal(union_element_constructed_matches_fn(), "mixed") +} + +// ─── 6. `unknown[]` is invariant, not "any list" ────────────────────────────── +// Invariance applies to the top type like any other element type: `unknown[]` +// denotes lists whose element type is `unknown`, so an `int[]` value is NOT a +// member (the match-anything container position is the wildcard hole `_`, not +// the `unknown` type). A list constructed *at* element type `unknown` (the +// literal adopts the ascribed element type) IS a member. + +function int_list_is_not_unknown_list_fn() -> bool { + let a: int[] = [1, 2]; + // Widen to `unknown` (the top type — sound under any variance) before + // testing. Tested directly on the `int[]`-typed local, TIR's covariant + // container narrowing — a defect under removal; containers are never + // covariant — rewrites the pattern to `int[]`, turning the very test we + // want to pin into a tautology. `unknown ∩ unknown[]` keeps the written + // pattern, and stays correct once narrowing is invariant (at which point + // the direct form works too). + let u: unknown = a; + u is unknown[] +} + +test "is_int_list_is_not_unknown_list" { + assert.is_true(!int_list_is_not_unknown_list_fn()) +} + +function unknown_list_is_unknown_list_fn() -> bool { + let v: unknown[] = [1, 2]; + v is unknown[] +} + +test "is_unknown_constructed_list_is_unknown_list" { + assert.is_true(unknown_list_is_unknown_list_fn()) +} + +// The same invariance through a match arm: an `unknown[]` arm does not capture +// an `int[]` value. (The `if true` guard keeps the second arm statically +// reachable — the lax coverage analysis treats `unknown[]` as covering it.) +function classify_unknown_vs_int(u: unknown[] | int[]) -> string { + match (u) { + let a: unknown[] if true => "unknowns", + let i: int[] => "ints", + _ => "other", + } +} + +function unknown_arm_rejects_int_list_fn() -> string { + let a: int[] = [1, 2]; + classify_unknown_vs_int(a) +} + +test "match_unknown_list_arm_rejects_int_list" { + assert.equal(unknown_arm_rejects_int_list_fn(), "ints") +} + +function unknown_arm_captures_unknown_list_fn() -> string { + let v: unknown[] = [1, 2]; + classify_unknown_vs_int(v) +} + +test "match_unknown_list_arm_captures_unknown_list" { + assert.equal(unknown_arm_captures_unknown_list_fn(), "unknowns") +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_match_enum_types/match_enum_types.baml b/baml_language/crates/baml_tests/baml_src/ns_match_enum_types/match_enum_types.baml new file mode 100644 index 00000000000..949e24a021f --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_match_enum_types/match_enum_types.baml @@ -0,0 +1,79 @@ +// End-to-end tests for enum-*type* discrimination in `match` and `is`. +// +// Before the enum-identity fix, a bare enum type test lowered to the single +// shared `ENUM` type tag (`type_tag_for_ty` / `realized_type_tag`), which cannot +// tell one enum from another. So `match (x: Color | Status)` routed a `Status` +// value to a `Color` arm — on the chain (coarse tag), and worse on the TypeTag +// switch (both arms deduped onto the one `ENUM` tag, dropping the second arm). +// +// The fix tests enum-pointer identity (`is Color` compares the value's enum +// object) so the chain discriminates `Color` from `Status`, and gates the switch +// (`switch_member_tag_sufficient`) so an enum arm bails to the precise chain when +// a second enum type shares the tag — while a lone enum type keeps the fast tag. +// +// (Enum *variant* matching — `Status.Active` — is unaffected: it dispatches on +// the enum discriminant, a separate precise path.) + +enum Color { Red, Green } +enum Status { Active, Inactive } + +// ─── 1. chain path: 2-arm enum-type discrimination ──────────────────────────── + +function classify(x: Color | Status) -> string { + match (x) { + let c: Color => "color", + let s: Status => "status", + } +} + +function classify_color_fn() -> string { classify(Color.Red) } +function classify_status_fn() -> string { classify(Status.Active) } + +test "match_enum_type_color" { assert.equal(classify_color_fn(), "color") } +test "match_enum_type_status" { assert.equal(classify_status_fn(), "status") } + +// ─── 2. `is` enum-type test ─────────────────────────────────────────────────── + +function status_is_color_fn() -> bool { + let x: Color | Status = Status.Active; + x is Color +} +test "is_status_is_not_color" { assert.is_true(!status_is_color_fn()) } + +// ─── 3. TypeTag switch path: enum collision bails to the precise chain ──────── +// With enough tag-eligible arms the lowering prefers the TypeTag jump-table +// switch. `Color` and `Status` share the one `ENUM` tag, so the switch gate +// detects the collision and falls back to the enum-precise chain rather than +// deduping the second enum arm away. + +function classify_switch(x: Color | Status | int | string | bool) -> string { + match (x) { + let c: Color => "color", + let s: Status => "status", + let n: int => "int", + let st: string => "string", + let b: bool => "bool", + } +} + +function classify_switch_status_fn() -> string { classify_switch(Status.Inactive) } +function classify_switch_int_fn() -> string { classify_switch(7) } + +test "match_switch_enum_status" { assert.equal(classify_switch_status_fn(), "status") } +test "match_switch_enum_prims_still_dispatch" { assert.equal(classify_switch_int_fn(), "int") } + +// ─── 4. single enum type keeps the fast tag (no regression) ─────────────────── +// Only one enum type reaches this match, so the coarse `ENUM` tag has nothing to +// conflate with and the switch stays a jump table. + +function classify_one_enum(x: Color | int | string | bool) -> string { + match (x) { + let c: Color => "color", + let n: int => "int", + let st: string => "string", + let b: bool => "bool", + } +} + +function classify_one_enum_color_fn() -> string { classify_one_enum(Color.Green) } +test "match_single_enum_switch_color" { assert.equal(classify_one_enum_color_fn(), "color") } diff --git a/baml_language/crates/baml_tests/baml_src/ns_match_self_workaround/match_self_workaround.baml b/baml_language/crates/baml_tests/baml_src/ns_match_self_workaround/match_self_workaround.baml new file mode 100644 index 00000000000..24cbfa3fa41 --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_match_self_workaround/match_self_workaround.baml @@ -0,0 +1,70 @@ +// The sound workaround for `Self` in a class-method body: name the enclosing +// type explicitly. `Self` is a signature-only construct for now — a +// body-position `Self` is a compile error (pinned in the `self_in_body` +// diagnostics project). In a class method the equivalent is to write the type +// out. +// +// This pins the *working side* of that restriction: for a NON-generic class, +// naming the enclosing type in a body `match`/`is` routes through the ordinary +// class-identity test and works. These stay green as `Self` support expands. +// +// BUG(class-typevar-body-match): the analogous GENERIC-class workaround — +// writing `Box` (the class's own type param `T`) in a method-body `match`/`is` +// — does NOT yet work: the runtime dispatch-guard for a class-level type +// parameter fails to match even the receiver's own instantiation, so a +// `Box` value on a `Box` receiver takes the fallthrough arm. This is a +// pre-existing gap (Unit-2 / B-634 typevar matching was only ever exercised with +// *function* generics; `reflect.type_of()` reads the frame's `T` fine, but +// the guard match does not), independent of the `Self` restriction. It is the +// same "expand `Self` support" bucket: resolving a generic class's `Self`/`T` in +// a body requires this runtime path to work first. Generic tests belong here +// once it does. + +class Shape { + sides: int, + function same_kind(self, other: Shape | int) -> string { + match (other) { + let s: Shape => "shape", + _ => "other", + } + } + function is_shape(self, other: Shape | int) -> bool { + other is Shape + } +} + +function shape_matches_shape_fn() -> string { + let s = Shape { sides: 3 }; + s.same_kind(Shape { sides: 4 }) +} + +test "self_workaround_match_captures_enclosing_type" { + assert.equal(shape_matches_shape_fn(), "shape") +} + +function shape_matches_int_fn() -> string { + let s = Shape { sides: 3 }; + s.same_kind(42) +} + +test "self_workaround_match_rejects_other_type" { + assert.equal(shape_matches_int_fn(), "other") +} + +function shape_is_shape_fn() -> bool { + let s = Shape { sides: 3 }; + s.is_shape(Shape { sides: 4 }) +} + +test "self_workaround_is_enclosing_type" { + assert.is_true(shape_is_shape_fn()) +} + +function shape_is_not_int_fn() -> bool { + let s = Shape { sides: 3 }; + s.is_shape(7) +} + +test "self_workaround_is_rejects_other_type" { + assert.is_true(!shape_is_not_int_fn()) +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_toml/toml.baml b/baml_language/crates/baml_tests/baml_src/ns_toml/toml.baml index b3b6632e767..0f674bb5cde 100644 --- a/baml_language/crates/baml_tests/baml_src/ns_toml/toml.baml +++ b/baml_language/crates/baml_tests/baml_src/ns_toml/toml.baml @@ -94,7 +94,12 @@ test "toml_item_from_json_rejects_null" { // header), and a boxed local passed to the union-typed `Item` parameter // fails the match arms. function toml_item_to_json_array() -> string { - let item: baml.toml.Item = [baml.time.ZonedDateTime.parse("2020-01-01T00:00:00Z"), 7]; + // Declared `Item[]` (not `Item`) so the literal adopts `Item` as its element + // type — a genuine array-of-items. Ascribing `Item` instead would infer the + // narrow element join `ZonedDateTime | int`, which is *not* invariantly an + // `Item[]`, so the `Item[]` match arm would (correctly) reject it. It is still + // an `Item` at the top level, so the `item_to_json(item: Item)` call holds. + let item: baml.toml.Item[] = [baml.time.ZonedDateTime.parse("2020-01-01T00:00:00Z"), 7]; baml.json.stringify(baml.toml.item_to_json(item)) } test "toml_item_to_json_array_with_datetime" { diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/self_in_body/main.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/self_in_body/main.baml new file mode 100644 index 00000000000..c313fcdf62b --- /dev/null +++ b/baml_language/crates/baml_tests/projects/diagnostic_errors/self_in_body/main.baml @@ -0,0 +1,54 @@ +// `Self` is a signature-only construct. In a method *body* type position it has +// no substitution (a class body cannot name its own instantiation as a value +// type, and an interface default method cannot name the implementor), so every +// body-position `Self` is a compile error — rather than silently resolving to a +// misbehaving `TypeVar("Self")` (interface default methods) or matching any +// instantiation (generic class bodies). +// +// The sound alternative in a class method is to name the enclosing type +// explicitly, e.g. `Box` (exercised as a runtime test in +// `ns_match_self_workaround`). + +// ── Class-body method: match arm, `is`, and turbofish ──────────────────────── + +class Box { + v: T, + function which(self, other: Box | Box) -> string { + match (other) { + let s: Self => "self", + _ => "other", + } + } + function is_self(self, other: Box | Box) -> bool { + other is Self + } + function let_self(self, other: Box) -> int { + let a: Self = other; + 1 + } + function nested_self(self, w: Box) -> int { + match (w) { + let s: Box => 1, + _ => 0, + } + } + function turbofish_self(self) -> string { + reflect.type_of().to_string() + } +} + +// ── Interface default-method body (the previously-silent hole) ─────────────── + +interface Tester { + function eq(self, other: Self) -> bool + function check(self, other: Self) -> bool { + other is Self + } + function describe(self) -> string { + reflect.type_of().to_string() + } + function bind_self(self, other: Self) -> bool { + let a: Self = other; + true + } +} diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap index 3757b7154c1..df6353465c7 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap @@ -219,6 +219,18 @@ function $init_test(registry: unknown) -> null { load_var ?1 call ? pop 1 + load_var ?1 + call ? + pop 1 + load_var ?1 + call ? + pop 1 + load_var ?1 + call ? + pop 1 + load_var ?1 + call ? + pop 1 load_const ? return } diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/csv.snap b/baml_language/crates/baml_tests/snapshots/baml_src/csv.snap index f5ea8fdd5af..119a7e0b69b 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/csv.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/csv.snap @@ -1986,7 +1986,7 @@ function csv.csv_excel_profile() -> string { load_const "x" load_type null | bool | int | bigint | float | string alloc_array 1 - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] alloc_array 1 load_const null load_const null @@ -8286,7 +8286,7 @@ function csv.csv_stringify_records_roundtrip() -> string { load_const "x\ny" load_type null | bool | int | bigint | float | string alloc_array 2 - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] alloc_array 2 load_const call baml.csv.stringify_records diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_subtype.snap b/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_subtype.snap index f1684063dd8..1720c1d22bd 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_subtype.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_subtype.snap @@ -32,6 +32,24 @@ function generic_match_subtype.$init_test_ns_generic_match_subtype_generic_match load_const null call testing.TestCollector.register_test pop 1 + load_var registry + load_const "generic_match_switch_discriminates_int" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "generic_match_switch_discriminates_string" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "generic_match_switch_prims_still_dispatch" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 load_const null return } @@ -70,6 +88,28 @@ function generic_match_subtype.concrete_discriminates_string_fn() -> string { return } +function generic_match_subtype.concrete_switch_int_fn() -> string { + load_const 1 + load_type int + init_instance<1> user.generic_match_subtype.Foo .value + call user.generic_match_subtype.pick_concrete_switch + return +} + +function generic_match_subtype.concrete_switch_prim_fn() -> string { + load_const "hello" + call user.generic_match_subtype.pick_concrete_switch + return +} + +function generic_match_subtype.concrete_switch_string_fn() -> string { + load_const "x" + load_type string + init_instance<1> user.generic_match_subtype.Foo .value + call user.generic_match_subtype.pick_concrete_switch + return +} + function generic_match_subtype.generic_match_falls_through_on_non_fn() -> int { alloc_instance user.generic_match_subtype.Non store_var o @@ -194,6 +234,53 @@ function generic_match_subtype.pick_concrete(x: generic_match_subtype.Foo | return } +function generic_match_subtype.pick_concrete_switch(x: generic_match_subtype.Foo | generic_match_subtype.Foo | int | string | bool) -> string { + load_var x + is_type generic_match_subtype.Foo<...> + pop_jump_if_false L0 + jump L7 + + L0: + load_var x + is_type generic_match_subtype.Foo<...> + pop_jump_if_false L1 + jump L6 + + L1: + load_var x + is_type int + pop_jump_if_false L2 + jump L5 + + L2: + load_var x + is_type string + pop_jump_if_false L3 + jump L4 + + L3: + load_const "prim bool" + jump L8 + + L4: + load_const "prim string" + jump L8 + + L5: + load_const "prim int" + jump L8 + + L6: + load_const "string" + jump L8 + + L7: + load_const "int" + + L8: + return +} + function generic_match_subtype.unwrap_or(o: generic_match_subtype.Opt | generic_match_subtype.Non, default: T) -> T { load_var o is_type generic_match_subtype.Opt<...> diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_typevar.snap b/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_typevar.snap new file mode 100644 index 00000000000..8d188e24e30 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/baml_src/generic_match_typevar.snap @@ -0,0 +1,250 @@ +--- +source: crates/baml_tests/tests/baml_src.rs +--- +function generic_match_typevar.$init_test_ns_generic_match_typevar_generic_match_typevar(registry: testing.TestCollector) -> null { + load_var registry + load_const "typevar_arm_matches_realized_int" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_arm_rejects_non_member" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_arm_dynamic_first_match_capture" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_list_arm_matches_realized_element" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_list_arm_rejects_other_element" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_list_arm_dynamic_first_match_capture" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_map_arm_matches_realized_value_type" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "typevar_map_arm_rejects_other_value_type" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_typevar_member" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_typevar_non_member" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_const null + return +} + +function generic_match_typevar.is_t(x: T | string) -> bool { + load_var x + is_type #0? + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} + +function generic_match_typevar.is_t_member_fn() -> bool { + load_type int + load_const 42 + call user.generic_match_typevar.is_t + return +} + +function generic_match_typevar.is_t_non_member_fn() -> bool { + load_type int + load_const "hi" + call user.generic_match_typevar.is_t + return +} + +function generic_match_typevar.pick(x: T | string) -> string { + load_var x + is_type #0? + pop_jump_if_false L0 + load_const true + pop_jump_if_false L0 + jump L3 + + L0: + load_var x + is_type string + pop_jump_if_false L1 + jump L2 + + L1: + load_const "other" + jump L4 + + L2: + load_const "string" + jump L4 + + L3: + load_const "T" + + L4: + return +} + +function generic_match_typevar.pick_dynamic_capture_fn() -> string { + load_type string + load_const "hi" + call user.generic_match_typevar.pick + return +} + +function generic_match_typevar.pick_int_value_fn() -> string { + load_type int + load_const 42 + call user.generic_match_typevar.pick + return +} + +function generic_match_typevar.pick_list(x: T[] | string[]) -> string { + load_var x + is_type #0?[] + pop_jump_if_false L0 + load_const true + pop_jump_if_false L0 + jump L1 + + L0: + load_var x + is_type string[] + pop_jump_if_false L3 + load_const "string[]" + jump L2 + + L1: + load_const "T[]" + + L2: + return + + L3: + unreachable +} + +function generic_match_typevar.pick_list_dynamic_fn() -> string { + load_type string + load_const "a" + load_const "b" + load_type string + alloc_array 2 + call user.generic_match_typevar.pick_list + return +} + +function generic_match_typevar.pick_list_int_fn() -> string { + load_type int + load_const 1 + load_const 2 + load_type int + alloc_array 2 + call user.generic_match_typevar.pick_list + return +} + +function generic_match_typevar.pick_list_string_fn() -> string { + load_type int + load_const "a" + load_const "b" + load_type string + alloc_array 2 + call user.generic_match_typevar.pick_list + return +} + +function generic_match_typevar.pick_map(x: map | map) -> string { + load_var x + is_type map + pop_jump_if_false L0 + load_const true + pop_jump_if_false L0 + jump L1 + + L0: + load_var x + is_type map + pop_jump_if_false L3 + load_const "map" + jump L2 + + L1: + load_const "map" + + L2: + return + + L3: + unreachable +} + +function generic_match_typevar.pick_map_int_fn() -> string { + load_type int + load_const 1 + load_const "k" + load_type string + load_type int + alloc_map 1 + call user.generic_match_typevar.pick_map + return +} + +function generic_match_typevar.pick_map_string_fn() -> string { + load_type int + load_const "v" + load_const "k" + load_type string + load_type string + alloc_map 1 + call user.generic_match_typevar.pick_map + return +} + +function generic_match_typevar.pick_string_value_fn() -> string { + load_type int + load_const "hi" + call user.generic_match_typevar.pick + return +} diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap b/baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap index 436d319f8c2..a8f0ab10ffe 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/inferred_generic_type_args.snap @@ -242,7 +242,7 @@ function inferred_generic_type_args.Counter.element_type(self: inferred_generic_ function inferred_generic_type_args.Cursor.take_one(self: inferred_generic_type_args.Cursor) -> inferred_generic_type_args.Cursor { load_var self - load_type inferred_generic_type_args.Cursor + load_type inferred_generic_type_args.Cursor<#0> load_const "head" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -360,7 +360,7 @@ function inferred_generic_type_args.NextIter.collect2(self: inferred_generic_typ load_const true pop_jump_if_false L2 load_var self - load_type inferred_generic_type_args.NextIter + load_type inferred_generic_type_args.NextIter<#0, #1> load_const "next2" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -438,7 +438,7 @@ function inferred_generic_type_args.ProbeB.TypeProbe.seen(self: inferred_generic function inferred_generic_type_args.SelfEcho.nested_self_type(self: inferred_generic_type_args.SelfEcho) -> string { load_var self - load_type Self + load_type _ alloc_array 1 call user.inferred_generic_type_args.echo_type return diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/iter.snap b/baml_language/crates/baml_tests/snapshots/baml_src/iter.snap index d3db1958fec..493cc4fe97d 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/iter.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/iter.snap @@ -353,7 +353,7 @@ function iter.iter_array_as_iterable_param_inner(xs: baml.iter.Iterable int[] { L10: load_var _2 - is_type T[] + is_type _[] pop_jump_if_false L46 load_type int load_var _2 @@ -4113,7 +4113,7 @@ function iter.iter_for_in_range() -> int[] { L10: load_var _2 - is_type T[] + is_type _[] pop_jump_if_false L46 load_type int load_var _2 @@ -4441,7 +4441,7 @@ function iter.iter_for_in_repeat() -> int[] { L10: load_var _2 - is_type T[] + is_type _[] pop_jump_if_false L46 load_type int load_var _2 diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap b/baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap index 879aae54882..63cd7498b9a 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap @@ -100,7 +100,7 @@ function iter_impl_generics_only.core.Chain$stream.Iterator.next(self: ite pop_jump_if_false L2 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _4 @@ -122,7 +122,7 @@ function iter_impl_generics_only.core.Chain$stream.Iterator.next(self: ite L2: load_var self load_field .1 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -144,7 +144,7 @@ function iter_impl_generics_only.core.Chain.Iterator.next(self: iter_impl_ pop_jump_if_false L2 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _4 @@ -166,7 +166,7 @@ function iter_impl_generics_only.core.Chain.Iterator.next(self: iter_impl_ L2: load_var self load_field .1 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -194,7 +194,7 @@ function iter_impl_generics_only.core.Filter$stream.Iterator.next(sel L2: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -238,7 +238,7 @@ function iter_impl_generics_only.core.Filter.Iterator.next(self: iter L2: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -282,7 +282,7 @@ function iter_impl_generics_only.core.FilterMap$stream.Iterator.next( L2: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -331,7 +331,7 @@ function iter_impl_generics_only.core.FilterMap.Iterator.next(self: i L2: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -578,13 +578,16 @@ function iter_impl_generics_only.core.FlatMap$stream.Iterator.ne jump L38 L35: + load_var _2 + is_type iter_impl_generics_only.core.Done + pop_jump_if_false L42 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _40 - load_var _40 + store_var _41 + load_var _41 is_type iter_impl_generics_only.core.Done pop_jump_if_false L36 jump L37 @@ -593,7 +596,7 @@ function iter_impl_generics_only.core.FlatMap$stream.Iterator.ne load_var2 4 1 load_field .1 call_indirect - load_type iter_impl_generics_only.core.Iterable + load_type iter_impl_generics_only.core.Iterable<#1, #4> load_const "iter" virtual_call nargs=1 ntypeargs=0 store_var it @@ -607,7 +610,7 @@ function iter_impl_generics_only.core.FlatMap$stream.Iterator.ne L38: load_var _2 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#1, #4> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _37 @@ -627,6 +630,9 @@ function iter_impl_generics_only.core.FlatMap$stream.Iterator.ne alloc_instance user.iter_impl_generics_only.core.Done store_field .2 jump L0 + + L42: + unreachable } function iter_impl_generics_only.core.FlatMap.Iterable.iter(self: iter_impl_generics_only.core.FlatMap) -> iter_impl_generics_only.core.Iterator { @@ -845,13 +851,16 @@ function iter_impl_generics_only.core.FlatMap.Iterator.next(self jump L38 L35: + load_var _2 + is_type iter_impl_generics_only.core.Done + pop_jump_if_false L42 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _40 - load_var _40 + store_var _41 + load_var _41 is_type iter_impl_generics_only.core.Done pop_jump_if_false L36 jump L37 @@ -860,7 +869,7 @@ function iter_impl_generics_only.core.FlatMap.Iterator.next(self load_var2 4 1 load_field .1 call_indirect - load_type iter_impl_generics_only.core.Iterable + load_type iter_impl_generics_only.core.Iterable<#1, #4> load_const "iter" virtual_call nargs=1 ntypeargs=0 store_var it @@ -874,7 +883,7 @@ function iter_impl_generics_only.core.FlatMap.Iterator.next(self L38: load_var _2 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#1, #4> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _37 @@ -894,6 +903,9 @@ function iter_impl_generics_only.core.FlatMap.Iterator.next(self alloc_instance user.iter_impl_generics_only.core.Done store_field .2 jump L0 + + L42: + unreachable } function iter_impl_generics_only.core.Flatten$stream.Iterable.iter(self: iter_impl_generics_only.core.Flatten$stream) -> iter_impl_generics_only.core.Iterator { @@ -1112,20 +1124,23 @@ function iter_impl_generics_only.core.Flatten$stream.Iterator.next(se jump L38 L35: + load_var _2 + is_type iter_impl_generics_only.core.Done + pop_jump_if_false L42 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator, E> + load_type iter_impl_generics_only.core.Iterator, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _40 - load_var _40 + store_var _41 + load_var _41 is_type iter_impl_generics_only.core.Done pop_jump_if_false L36 jump L37 L36: - load_var _40 - load_type iter_impl_generics_only.core.Iterable + load_var _41 + load_type iter_impl_generics_only.core.Iterable<#0, #2> load_const "iter" virtual_call nargs=1 ntypeargs=0 store_var it @@ -1139,7 +1154,7 @@ function iter_impl_generics_only.core.Flatten$stream.Iterator.next(se L38: load_var _2 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _37 @@ -1159,6 +1174,9 @@ function iter_impl_generics_only.core.Flatten$stream.Iterator.next(se alloc_instance user.iter_impl_generics_only.core.Done store_field .1 jump L0 + + L42: + unreachable } function iter_impl_generics_only.core.Flatten.Iterable.iter(self: iter_impl_generics_only.core.Flatten) -> iter_impl_generics_only.core.Iterator { @@ -1377,20 +1395,23 @@ function iter_impl_generics_only.core.Flatten.Iterator.next(self: ite jump L38 L35: + load_var _2 + is_type iter_impl_generics_only.core.Done + pop_jump_if_false L42 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator, E> + load_type iter_impl_generics_only.core.Iterator, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _40 - load_var _40 + store_var _41 + load_var _41 is_type iter_impl_generics_only.core.Done pop_jump_if_false L36 jump L37 L36: - load_var _40 - load_type iter_impl_generics_only.core.Iterable + load_var _41 + load_type iter_impl_generics_only.core.Iterable<#0, #2> load_const "iter" virtual_call nargs=1 ntypeargs=0 store_var it @@ -1404,7 +1425,7 @@ function iter_impl_generics_only.core.Flatten.Iterator.next(self: ite L38: load_var _2 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _37 @@ -1424,6 +1445,9 @@ function iter_impl_generics_only.core.Flatten.Iterator.next(self: ite alloc_instance user.iter_impl_generics_only.core.Done store_field .1 jump L0 + + L42: + unreachable } function iter_impl_generics_only.core.Iterator.chain(self: iter_impl_generics_only.core.Iterator, other: iter_impl_generics_only.core.Iterator) -> iter_impl_generics_only.core.Iterator { @@ -1444,7 +1468,7 @@ function iter_impl_generics_only.core.Iterator.collect(self: iter_impl_generics_ load_const true pop_jump_if_false L2 load_var self - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _4 @@ -1472,7 +1496,7 @@ function iter_impl_generics_only.core.Iterator.count(self: iter_impl_generics_on load_const true pop_jump_if_false L2 load_var self - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -1498,7 +1522,7 @@ function iter_impl_generics_only.core.Iterator.every(self: iter_impl_generics_on load_const true pop_jump_if_false L2 load_var self - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -1546,7 +1570,7 @@ function iter_impl_generics_only.core.Iterator.find(self: iter_impl_generics_onl load_const true pop_jump_if_false L2 load_var self - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -1611,7 +1635,7 @@ function iter_impl_generics_only.core.Iterator.reduce(self: iter_impl_generics_o load_const true pop_jump_if_false L2 load_var self - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _5 @@ -1637,7 +1661,7 @@ function iter_impl_generics_only.core.Iterator.some(self: iter_impl_generics_onl load_const true pop_jump_if_false L2 load_var self - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -1677,7 +1701,7 @@ function iter_impl_generics_only.core.Map$stream.Iterable.iter(self: function iter_impl_generics_only.core.Map$stream.Iterator.next(self: iter_impl_generics_only.core.Map$stream) -> R | iter_impl_generics_only.core.Done { load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -1707,7 +1731,7 @@ function iter_impl_generics_only.core.Map.Iterable.iter(self: iter_im function iter_impl_generics_only.core.Map.Iterator.next(self: iter_impl_generics_only.core.Map) -> R | iter_impl_generics_only.core.Done { load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #2> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -1756,7 +1780,7 @@ function iter_impl_generics_only.core.Peekable$stream.Iterator.next(self: L0: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -1794,7 +1818,7 @@ function iter_impl_generics_only.core.Peekable.Iterator.next(self: iter_im L0: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -1827,7 +1851,7 @@ function iter_impl_generics_only.core.Peekable.peek(self: iter_impl_generics_onl L0: load_var self load_field .iter - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var v @@ -2063,7 +2087,7 @@ function iter_impl_generics_only.core.StepBy$stream.Iterator.next(self: it L2: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -2072,7 +2096,7 @@ function iter_impl_generics_only.core.StepBy$stream.Iterator.next(self: it L3: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _9 @@ -2099,7 +2123,7 @@ function iter_impl_generics_only.core.StepBy$stream.Iterator.next(self: it store_field .2 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -2136,7 +2160,7 @@ function iter_impl_generics_only.core.StepBy.Iterator.next(self: iter_impl L2: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -2145,7 +2169,7 @@ function iter_impl_generics_only.core.StepBy.Iterator.next(self: iter_impl L3: load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _9 @@ -2172,7 +2196,7 @@ function iter_impl_generics_only.core.StepBy.Iterator.next(self: iter_impl store_field .2 load_var self load_field .0 - load_type iter_impl_generics_only.core.Iterator + load_type iter_impl_generics_only.core.Iterator<#0, #1> load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap b/baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap index 88a31a48481..3f094fa6c97 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap @@ -62,7 +62,7 @@ function json_alias.json_array_len() -> int { load_type int alloc_array 3 store_var_load_var arr - is_type baml.json.json[] + is_type _[] pop_jump_if_false L0 jump L1 @@ -86,7 +86,7 @@ function json_alias.json_map_has_key() -> bool { load_type string alloc_map 1 store_var_load_var m - is_type map + is_type map<_, _> pop_jump_if_false L0 jump L1 @@ -108,39 +108,74 @@ function json_alias.json_map_has_key() -> bool { function json_alias.match_json(j: baml.json.json) -> string { load_var j - type_tag - jump_table [L4, L2, L5, L6, L3, _, L1, L0], default L8 + load_const null + cmp_op == + pop_jump_if_false L0 + jump L11 + + L0: + load_var j + is_type bool + pop_jump_if_false L1 + jump L10 - L0: map - load_const "map" + L1: + load_var j + is_type int + pop_jump_if_false L2 + jump L9 + + L2: + load_var j + is_type float + pop_jump_if_false L3 + jump L8 + + L3: + load_var j + is_type string + pop_jump_if_false L4 jump L7 - L1: list + L4: + load_var j + is_type baml.json.json[] + pop_jump_if_false L5 + jump L6 + + L5: + load_var j + is_type map + pop_jump_if_false L13 + load_const "map" + jump L12 + + L6: load_const "array" - jump L7 + jump L12 - L2: string + L7: load_const "string" - jump L7 + jump L12 - L3: float + L8: load_const "float" - jump L7 + jump L12 - L4: int + L9: load_const "int" - jump L7 + jump L12 - L5: bool + L10: load_const "bool" - jump L7 + jump L12 - L6: null + L11: load_const "null" - L7: + L12: return - L8: + L13: unreachable } diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/json_parse_stringify.snap b/baml_language/crates/baml_tests/snapshots/baml_src/json_parse_stringify.snap index 068f4eafaa5..e03060557db 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/json_parse_stringify.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/json_parse_stringify.snap @@ -59,7 +59,7 @@ function json_parse_stringify.parse_array_len() -> int { call baml.json.parse store_var j load_var j - is_type baml.json.json[] + is_type _[] pop_jump_if_false L0 jump L1 @@ -117,7 +117,7 @@ function json_parse_stringify.parse_then_match_array_len() -> int { call baml.json.parse store_var j load_var j - is_type baml.json.json[] + is_type _[] pop_jump_if_false L0 jump L1 diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap b/baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap index e0c6a0ed75f..6b533a185f5 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/json_to_from_string.snap @@ -323,7 +323,7 @@ function json_to_from_string.from_string_json_passthrough_object_len() -> int { call baml.json.from_string store_var j load_var j - is_type map + is_type map<_, _> pop_jump_if_false L0 jump L1 diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/match_container_types.snap b/baml_language/crates/baml_tests/snapshots/baml_src/match_container_types.snap new file mode 100644 index 00000000000..160503290bc --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/baml_src/match_container_types.snap @@ -0,0 +1,439 @@ +--- +source: crates/baml_tests/tests/baml_src.rs +--- +function match_container_types.$init_test_ns_match_container_types_match_container_types(registry: testing.TestCollector) -> null { + load_var registry + load_const "match_container_int_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_container_string_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_int_list_is_int_array" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_string_list_is_not_int_array" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_container_int_map" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_container_string_map" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_switch_int_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_switch_string_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_switch_prims_still_dispatch" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_invariance_int_list_not_member_of_union_element_arm" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_invariance_union_element_constructed_list_matches" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_int_list_is_not_unknown_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_unknown_constructed_list_is_unknown_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_unknown_list_arm_rejects_int_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_unknown_list_arm_captures_unknown_list" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_const null + return +} + +function match_container_types.classify_int_list_fn() -> string { + load_const 1 + load_const 2 + load_const 3 + load_type int + alloc_array 3 + call user.match_container_types.classify_list + return +} + +function match_container_types.classify_int_map_fn() -> string { + load_const 1 + load_const "k" + load_type string + load_type int + alloc_map 1 + call user.match_container_types.classify_map + return +} + +function match_container_types.classify_list(x: int[] | string[]) -> string { + load_var x + is_type int[] + pop_jump_if_false L0 + jump L1 + + L0: + load_var x + is_type string[] + pop_jump_if_false L3 + load_const "strings" + jump L2 + + L1: + load_const "ints" + + L2: + return + + L3: + unreachable +} + +function match_container_types.classify_map(x: map | map) -> string { + load_var x + is_type map + pop_jump_if_false L0 + jump L1 + + L0: + load_var x + is_type map + pop_jump_if_false L3 + load_const "string values" + jump L2 + + L1: + load_const "int values" + + L2: + return + + L3: + unreachable +} + +function match_container_types.classify_string_list_fn() -> string { + load_const "a" + load_const "b" + load_type string + alloc_array 2 + call user.match_container_types.classify_list + return +} + +function match_container_types.classify_string_map_fn() -> string { + load_const "v" + load_const "k" + load_type string + load_type string + alloc_map 1 + call user.match_container_types.classify_map + return +} + +function match_container_types.classify_switch(x: int[] | string[] | int | string | bool) -> string { + load_var x + is_type int[] + pop_jump_if_false L0 + jump L7 + + L0: + load_var x + is_type string[] + pop_jump_if_false L1 + jump L6 + + L1: + load_var x + is_type int + pop_jump_if_false L2 + jump L5 + + L2: + load_var x + is_type string + pop_jump_if_false L3 + jump L4 + + L3: + load_var x + is_type bool + pop_jump_if_false L9 + load_const "bool" + jump L8 + + L4: + load_const "string" + jump L8 + + L5: + load_const "int" + jump L8 + + L6: + load_const "strings" + jump L8 + + L7: + load_const "ints" + + L8: + return + + L9: + unreachable +} + +function match_container_types.classify_switch_int_list_fn() -> string { + load_const 1 + load_const 2 + load_const 3 + load_type int + alloc_array 3 + call user.match_container_types.classify_switch + return +} + +function match_container_types.classify_switch_prims_fn() -> string { + load_const 42 + call user.match_container_types.classify_switch + return +} + +function match_container_types.classify_switch_string_list_fn() -> string { + load_const "a" + load_const "b" + load_type string + alloc_array 2 + call user.match_container_types.classify_switch + return +} + +function match_container_types.classify_union_element(u: (int | string)[] | int[]) -> string { + load_var u + is_type int[] + pop_jump_if_false L0 + load_const true + pop_jump_if_false L0 + jump L3 + + L0: + load_var u + is_type (int | string)[] + pop_jump_if_false L1 + jump L2 + + L1: + load_var u + is_type int[] + pop_jump_if_false L5 + + L2: + load_const "mixed" + jump L4 + + L3: + load_const "ints" + + L4: + return + + L5: + unreachable +} + +function match_container_types.classify_unknown_vs_int(u: unknown[] | int[]) -> string { + load_var u + is_type unknown[] + pop_jump_if_false L0 + load_const true + pop_jump_if_false L0 + jump L3 + + L0: + load_var u + is_type int[] + pop_jump_if_false L1 + jump L2 + + L1: + load_const "other" + jump L4 + + L2: + load_const "ints" + jump L4 + + L3: + load_const "unknowns" + + L4: + return +} + +function match_container_types.int_list_is_int_array_fn() -> bool { + load_const 1 + load_const 2 + load_const 3 + load_type int + alloc_array 3 + is_type int[] + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} + +function match_container_types.int_list_is_not_unknown_list_fn() -> bool { + load_const 1 + load_const 2 + load_type int + alloc_array 2 + is_type unknown[] + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} + +function match_container_types.int_list_vs_union_element_arm_fn() -> string { + load_const 1 + load_const 2 + load_type int + alloc_array 2 + call user.match_container_types.classify_union_element + return +} + +function match_container_types.string_list_is_int_array_fn() -> bool { + load_const "a" + load_const "b" + load_type string + alloc_array 2 + is_type int[] + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} + +function match_container_types.union_element_constructed_matches_fn() -> string { + load_const 1 + load_const 2 + load_type int | string + alloc_array 2 + call user.match_container_types.classify_union_element + return +} + +function match_container_types.unknown_arm_captures_unknown_list_fn() -> string { + load_const 1 + load_const 2 + load_type unknown + alloc_array 2 + call user.match_container_types.classify_unknown_vs_int + return +} + +function match_container_types.unknown_arm_rejects_int_list_fn() -> string { + load_const 1 + load_const 2 + load_type int + alloc_array 2 + call user.match_container_types.classify_unknown_vs_int + return +} + +function match_container_types.unknown_list_is_unknown_list_fn() -> bool { + load_const 1 + load_const 2 + load_type unknown + alloc_array 2 + is_type unknown[] + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/match_enum_types.snap b/baml_language/crates/baml_tests/snapshots/baml_src/match_enum_types.snap new file mode 100644 index 00000000000..f2377685df0 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/baml_src/match_enum_types.snap @@ -0,0 +1,186 @@ +--- +source: crates/baml_tests/tests/baml_src.rs +--- +function match_enum_types.$init_test_ns_match_enum_types_match_enum_types(registry: testing.TestCollector) -> null { + load_var registry + load_const "match_enum_type_color" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_enum_type_status" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "is_status_is_not_color" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_switch_enum_status" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_switch_enum_prims_still_dispatch" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "match_single_enum_switch_color" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_const null + return +} + +function match_enum_types.classify(x: match_enum_types.Color | match_enum_types.Status) -> string { + load_var x + is_type match_enum_types.Color + pop_jump_if_false L0 + jump L1 + + L0: + load_const "status" + jump L2 + + L1: + load_const "color" + + L2: + return +} + +function match_enum_types.classify_color_fn() -> string { + load_const user.match_enum_types.Color.Red + alloc_variant user.match_enum_types.Color + call user.match_enum_types.classify + return +} + +function match_enum_types.classify_one_enum(x: match_enum_types.Color | int | string | bool) -> string { + load_var x + type_tag + jump_table [L2, L1, L0, _, _, L3], default L5 + + L0: bool + load_const "bool" + jump L4 + + L1: string + load_const "string" + jump L4 + + L2: int + load_const "int" + jump L4 + + L3: enum + load_const "color" + + L4: + return + + L5: + unreachable +} + +function match_enum_types.classify_one_enum_color_fn() -> string { + load_const user.match_enum_types.Color.Green + alloc_variant user.match_enum_types.Color + call user.match_enum_types.classify_one_enum + return +} + +function match_enum_types.classify_status_fn() -> string { + load_const user.match_enum_types.Status.Active + alloc_variant user.match_enum_types.Status + call user.match_enum_types.classify + return +} + +function match_enum_types.classify_switch(x: match_enum_types.Color | match_enum_types.Status | int | string | bool) -> string { + load_var x + is_type match_enum_types.Color + pop_jump_if_false L0 + jump L7 + + L0: + load_var x + is_type match_enum_types.Status + pop_jump_if_false L1 + jump L6 + + L1: + load_var x + is_type int + pop_jump_if_false L2 + jump L5 + + L2: + load_var x + is_type string + pop_jump_if_false L3 + jump L4 + + L3: + load_const "bool" + jump L8 + + L4: + load_const "string" + jump L8 + + L5: + load_const "int" + jump L8 + + L6: + load_const "status" + jump L8 + + L7: + load_const "color" + + L8: + return +} + +function match_enum_types.classify_switch_int_fn() -> string { + load_const 7 + call user.match_enum_types.classify_switch + return +} + +function match_enum_types.classify_switch_status_fn() -> string { + load_const user.match_enum_types.Status.Inactive + alloc_variant user.match_enum_types.Status + call user.match_enum_types.classify_switch + return +} + +function match_enum_types.status_is_color_fn() -> bool { + load_const user.match_enum_types.Status.Active + alloc_variant user.match_enum_types.Status + is_type match_enum_types.Color + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/match_self_workaround.snap b/baml_language/crates/baml_tests/snapshots/baml_src/match_self_workaround.snap new file mode 100644 index 00000000000..2d32a4970b4 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/baml_src/match_self_workaround.snap @@ -0,0 +1,99 @@ +--- +source: crates/baml_tests/tests/baml_src.rs +--- +function match_self_workaround.$init_test_ns_match_self_workaround_match_self_workaround(registry: testing.TestCollector) -> null { + load_var registry + load_const "self_workaround_match_captures_enclosing_type" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "self_workaround_match_rejects_other_type" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "self_workaround_is_enclosing_type" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_var registry + load_const "self_workaround_is_rejects_other_type" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test + pop 1 + load_const null + return +} + +function match_self_workaround.Shape.is_shape(self: match_self_workaround.Shape, other: match_self_workaround.Shape | int) -> bool { + load_var other + is_type match_self_workaround.Shape + pop_jump_if_false L0 + jump L1 + + L0: + load_const false + jump L2 + + L1: + load_const true + + L2: + return +} + +function match_self_workaround.Shape.same_kind(self: match_self_workaround.Shape, other: match_self_workaround.Shape | int) -> string { + load_var other + is_type match_self_workaround.Shape + pop_jump_if_false L0 + jump L1 + + L0: + load_const "other" + jump L2 + + L1: + load_const "shape" + + L2: + return +} + +function match_self_workaround.shape_is_not_int_fn() -> bool { + load_const 3 + init_instance user.match_self_workaround.Shape .sides + load_const 7 + call user.match_self_workaround.Shape.is_shape + return +} + +function match_self_workaround.shape_is_shape_fn() -> bool { + load_const 3 + init_instance user.match_self_workaround.Shape .sides + load_const 4 + init_instance user.match_self_workaround.Shape .sides + call user.match_self_workaround.Shape.is_shape + return +} + +function match_self_workaround.shape_matches_int_fn() -> string { + load_const 3 + init_instance user.match_self_workaround.Shape .sides + load_const 42 + call user.match_self_workaround.Shape.same_kind + return +} + +function match_self_workaround.shape_matches_shape_fn() -> string { + load_const 3 + init_instance user.match_self_workaround.Shape .sides + load_const 4 + init_instance user.match_self_workaround.Shape .sides + call user.match_self_workaround.Shape.same_kind + return +} diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/patterns_new_runtime.snap b/baml_language/crates/baml_tests/snapshots/baml_src/patterns_new_runtime.snap index 6b406e77b07..8ff69506e87 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/patterns_new_runtime.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/patterns_new_runtime.snap @@ -747,7 +747,7 @@ function patterns_new_runtime.dispatch_union_cb(cb: ((int) -> int throws never) function patterns_new_runtime.exact_length(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -759,7 +759,7 @@ function patterns_new_runtime.exact_length(xs: int[]) -> int { L0: load_var xs - is_type int[] + is_type _[] pop_jump_if_false L1 load_var xs container_len @@ -771,7 +771,7 @@ function patterns_new_runtime.exact_length(xs: int[]) -> int { L1: load_var xs - is_type int[] + is_type _[] pop_jump_if_false L2 load_var xs container_len @@ -1663,7 +1663,7 @@ function patterns_new_runtime.pick_sheet(s: patterns_new_runtime.Sheet1) -> int function patterns_new_runtime.prefix_length(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -1850,7 +1850,7 @@ function patterns_new_runtime.score_abcde(v: patterns_new_runtime.ABCDE_A | patt function patterns_new_runtime.score_bool_slice(xs: bool[]) -> int { load_var xs - is_type bool[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -1868,7 +1868,7 @@ function patterns_new_runtime.score_bool_slice(xs: bool[]) -> int { L0: load_var xs - is_type bool[] + is_type _[] pop_jump_if_false L1 load_var xs container_len @@ -1890,7 +1890,7 @@ function patterns_new_runtime.score_bool_slice(xs: bool[]) -> int { L1: load_var xs - is_type bool[] + is_type _[] pop_jump_if_false L2 load_var xs container_len @@ -1951,7 +1951,7 @@ function patterns_new_runtime.score_depth(v: patterns_new_runtime.DepthClass | p function patterns_new_runtime.score_empty_prefix_rest(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -1980,7 +1980,7 @@ function patterns_new_runtime.score_exact_length_45() -> int { load_type int alloc_array 2 store_var_load_var _1 - is_type int[] + is_type _[] pop_jump_if_false L0 load_var _1 container_len @@ -1992,7 +1992,7 @@ function patterns_new_runtime.score_exact_length_45() -> int { L0: load_var _1 - is_type int[] + is_type _[] pop_jump_if_false L1 load_var _1 container_len @@ -2204,7 +2204,7 @@ function patterns_new_runtime.score_int_string_array(v: int[] | string[]) -> int function patterns_new_runtime.score_ints(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2228,7 +2228,7 @@ function patterns_new_runtime.score_ints(xs: int[]) -> int { L0: load_var xs - is_type int[] + is_type _[] pop_jump_if_false L1 load_var xs container_len @@ -2364,7 +2364,7 @@ function patterns_new_runtime.score_or_array_refine(v: int[] | string[]) -> int function patterns_new_runtime.score_or_nested_2d(rows: int[][]) -> int { load_var rows - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var rows container_len @@ -2388,7 +2388,7 @@ function patterns_new_runtime.score_or_nested_2d(rows: int[][]) -> int { L0: load_var rows - is_type int[][] + is_type _[] pop_jump_if_false L1 load_var rows container_len @@ -2435,7 +2435,7 @@ function patterns_new_runtime.score_or_nested_2d(rows: int[][]) -> int { function patterns_new_runtime.score_or_share(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2447,7 +2447,7 @@ function patterns_new_runtime.score_or_share(xs: int[]) -> int { L0: load_var xs - is_type int[] + is_type _[] pop_jump_if_false L1 load_var xs container_len @@ -2478,10 +2478,10 @@ function patterns_new_runtime.score_or_share(xs: int[]) -> int { function patterns_new_runtime.score_outer_chain(xs: int[][]) -> int { load_var xs - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var xs - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2624,7 +2624,7 @@ function patterns_new_runtime.score_personid(v: patterns_new_runtime.PersonIdA | function patterns_new_runtime.score_prefix_two_suffix(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2697,7 +2697,7 @@ function patterns_new_runtime.score_refine_a(v: patterns_new_runtime.RefineA | p function patterns_new_runtime.score_rest_copy(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2731,7 +2731,7 @@ function patterns_new_runtime.score_rest_copy(xs: int[]) -> int { function patterns_new_runtime.score_strings_len(xs: string[]) -> int { load_var xs - is_type string[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2760,7 +2760,7 @@ function patterns_new_runtime.score_strings_len(xs: string[]) -> int { function patterns_new_runtime.score_suffix_rest(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2801,7 +2801,7 @@ function patterns_new_runtime.score_suffix_rest(xs: int[]) -> int { function patterns_new_runtime.score_team2(teams: patterns_new_runtime.Team2[]) -> int { load_var teams - is_type patterns_new_runtime.Team2[] + is_type _[] pop_jump_if_false L0 load_var teams container_len @@ -2830,7 +2830,7 @@ function patterns_new_runtime.score_team2(teams: patterns_new_runtime.Team2[]) - L0: load_var teams - is_type patterns_new_runtime.Team2[] + is_type _[] pop_jump_if_false L1 load_var teams container_len @@ -2946,7 +2946,7 @@ function patterns_new_runtime.score_team4(team: patterns_new_runtime.Team4) -> i function patterns_new_runtime.score_two_prefix(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -2990,7 +2990,7 @@ function patterns_new_runtime.score_two_prefix(xs: int[]) -> int { function patterns_new_runtime.score_user_score(users: patterns_new_runtime.UserScore[]) -> int { load_var users - is_type patterns_new_runtime.UserScore[] + is_type _[] pop_jump_if_false L0 load_var users container_len @@ -3025,7 +3025,7 @@ function patterns_new_runtime.score_user_score(users: patterns_new_runtime.UserS function patterns_new_runtime.score_wildcard_second(xs: int[]) -> int { load_var xs - is_type int[] + is_type _[] pop_jump_if_false L0 load_var xs container_len @@ -3050,7 +3050,7 @@ function patterns_new_runtime.score_wildcard_second(xs: int[]) -> int { function patterns_new_runtime.score_wildcard_user(users: patterns_new_runtime.UserScore[]) -> int { load_var users - is_type patterns_new_runtime.UserScore[] + is_type _[] pop_jump_if_false L0 load_var users container_len @@ -3081,7 +3081,7 @@ function patterns_new_runtime.score_wildcard_user(users: patterns_new_runtime.Us function patterns_new_runtime.score_wrap2(v: int[][] | patterns_new_runtime.Wrap2) -> int { load_var v - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var v container_len @@ -5790,7 +5790,7 @@ function patterns_new_runtime.test_match_alphabet() -> int { load_type int alloc_array 26 store_var_load_var _1 - is_type int[] + is_type _[] pop_jump_if_false L0 load_var _1 container_len @@ -5919,7 +5919,7 @@ function patterns_new_runtime.test_match_array_shadows_outer() -> int { load_type int alloc_array 2 store_var_load_var _3 - is_type int[] + is_type _[] pop_jump_if_false L0 load_var _3 container_len @@ -5996,10 +5996,10 @@ function patterns_new_runtime.test_match_chain_trailing_binding() -> int { load_type int[] alloc_array 1 store_var_load_var _1 - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var _1 - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var _1 container_len @@ -6139,7 +6139,7 @@ function patterns_new_runtime.test_match_nested_array_projects_inner() -> int { load_type int[] alloc_array 1 store_var_load_var _1 - is_type int[][] + is_type _[] pop_jump_if_false L0 load_var _1 container_len diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/toml.snap b/baml_language/crates/baml_tests/snapshots/baml_src/toml.snap index 9e549e2a455..360110b5dfc 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/toml.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/toml.snap @@ -58,7 +58,7 @@ function toml.toml_item_to_json_array() -> string { load_const "2020-01-01T00:00:00Z" call baml.time.ZonedDateTime.parse load_const 7 - load_type baml.time.ZonedDateTime | int + load_type baml.toml.Item alloc_array 2 call baml.toml.item_to_json call baml.json.stringify diff --git a/baml_language/crates/baml_tests/snapshots/compiler2_mir/baml_tests__compiler2_mir__match_or_mixed_array_class_binding_uses_branch_local_rest_type.snap b/baml_language/crates/baml_tests/snapshots/compiler2_mir/baml_tests__compiler2_mir__match_or_mixed_array_class_binding_uses_branch_local_rest_type.snap index 847b6bef79b..8b7795b6f85 100644 --- a/baml_language/crates/baml_tests/snapshots/compiler2_mir/baml_tests__compiler2_mir__match_or_mixed_array_class_binding_uses_branch_local_rest_type.snap +++ b/baml_language/crates/baml_tests/snapshots/compiler2_mir/baml_tests__compiler2_mir__match_or_mixed_array_class_binding_uses_branch_local_rest_type.snap @@ -39,7 +39,7 @@ fn user.f(v: NumberBag | int[][]) -> int { } bb1: { - _7 = is_type(copy _1, int[][]); + _7 = is_type(copy _1, _[]); branch copy _7 -> [bb2, bb6]; } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap b/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap index dae074b8b7b..fd010c93abe 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap @@ -181,7 +181,7 @@ fn baml.Sortable$for$T[].sort(self: T[]) -> Self { bb2: { _5 = make_closure lambda[0]<1 type_args>(); - _6 = load_type(T.CompareError); + _6 = load_type(#0.CompareError); _7 = load_type(#0); _0 = call const fn baml.Array.sort_by(copy _1, copy _5) -> [bb4]; } @@ -1023,7 +1023,7 @@ fn baml.Array.sort_by_key(self: baml.Array, key: (T) -> U throws E) -> T[] { bb33: { _55 = make_closure lambda[0]<3 type_args>(copy _9); - _56 = load_type(U.CompareError); + _56 = load_type(#1.CompareError); _57 = load_type(int); _54 = call const fn baml.Array.sort_by(copy _47, copy _55) -> [bb34]; } @@ -2182,7 +2182,7 @@ fn baml.csv.decode(source: string | uint8array, options: baml.csv.ReaderOptions } bb4: { - _0 = virtual_call collect as baml.iter.Iterator(copy _5) -> [bb8, unwind: bb5]; + _0 = virtual_call collect as baml.iter.Iterator(copy _5) -> [bb8, unwind: bb5]; } bb5: { @@ -3128,7 +3128,7 @@ fn baml.csv.read(path: string, options: baml.csv.ReaderOptions | null) -> T[] { } bb4: { - _5 = virtual_call collect as baml.iter.Iterator(copy _6) -> [bb5]; + _5 = virtual_call collect as baml.iter.Iterator(copy _6) -> [bb5]; } bb5: { @@ -3328,57 +3328,57 @@ fn baml.csv.stringify_records(records: (null | bool | int | bigint | float | str } bb3: { - _7 = load_type(null | bool | int | bigint | float | string[]); + _7 = load_type((null | bool | int | bigint | float | string)[]); _6 = call const fn baml.iter.Iterable$for$T[].iter(copy _1) -> [bb4, unwind: bb28]; } bb4: { - _9 = is_type(copy _6, baml.iter.Flatten<_, null | bool | int | bigint | float | string[], never, never>); + _9 = is_type(copy _6, baml.iter.Flatten<_, (null | bool | int | bigint | float | string)[], never, never>); branch copy _9 -> [bb24, bb5]; } bb5: { - _11 = is_type(copy _6, baml.iter.ArrayIterator); + _11 = is_type(copy _6, baml.iter.ArrayIterator<(null | bool | int | bigint | float | string)[]>); branch copy _11 -> [bb23, bb6]; } bb6: { - _13 = is_type(copy _6, baml.iter.StepBy); + _13 = is_type(copy _6, baml.iter.StepBy<(null | bool | int | bigint | float | string)[], never>); branch copy _13 -> [bb22, bb7]; } bb7: { - _16 = is_type(copy _6, baml.iter.Chain); + _16 = is_type(copy _6, baml.iter.Chain<(null | bool | int | bigint | float | string)[], never, never>); branch copy _16 -> [bb21, bb8]; } bb8: { - _20 = is_type(copy _6, baml.iter.Repeat); + _20 = is_type(copy _6, baml.iter.Repeat<(null | bool | int | bigint | float | string)[]>); branch copy _20 -> [bb20, bb9]; } bb9: { - _22 = is_type(copy _6, baml.iter.Map<_, null | bool | int | bigint | float | string[], never, never>); + _22 = is_type(copy _6, baml.iter.Map<_, (null | bool | int | bigint | float | string)[], never, never>); branch copy _22 -> [bb19, bb10]; } bb10: { - _24 = is_type(copy _6, baml.iter.Filter); + _24 = is_type(copy _6, baml.iter.Filter<(null | bool | int | bigint | float | string)[], never, never>); branch copy _24 -> [bb18, bb11]; } bb11: { - _28 = is_type(copy _6, baml.iter.FilterMap<_, null | bool | int | bigint | float | string[], never, never>); + _28 = is_type(copy _6, baml.iter.FilterMap<_, (null | bool | int | bigint | float | string)[], never, never>); branch copy _28 -> [bb17, bb12]; } bb12: { - _30 = is_type(copy _6, baml.iter.FlatMap<_, null | bool | int | bigint | float | string[], never, never, never>); + _30 = is_type(copy _6, baml.iter.FlatMap<_, (null | bool | int | bigint | float | string)[], never, never, never>); branch copy _30 -> [bb16, bb13]; } bb13: { - _32 = is_type(copy _6, baml.iter.Peekable); + _32 = is_type(copy _6, baml.iter.Peekable<(null | bool | int | bigint | float | string)[], never>); branch copy _32 -> [bb15, bb14]; } @@ -3387,7 +3387,7 @@ fn baml.csv.stringify_records(records: (null | bool | int | bigint | float | str } bb15: { - _33 = load_type(null | bool | int | bigint | float | string[]); + _33 = load_type((null | bool | int | bigint | float | string)[]); _34 = load_type(never); _8 = call const fn baml.iter.Peekable.Iterator.next(copy _6) -> [bb25, unwind: bb28]; } @@ -3403,7 +3403,7 @@ fn baml.csv.stringify_records(records: (null | bool | int | bigint | float | str } bb18: { - _25 = load_type(null | bool | int | bigint | float | string[]); + _25 = load_type((null | bool | int | bigint | float | string)[]); _26 = load_type(never); _27 = load_type(never); _8 = call const fn baml.iter.Filter.Iterator.next(copy _6) -> [bb25, unwind: bb28]; @@ -3415,25 +3415,25 @@ fn baml.csv.stringify_records(records: (null | bool | int | bigint | float | str } bb20: { - _21 = load_type(null | bool | int | bigint | float | string[]); + _21 = load_type((null | bool | int | bigint | float | string)[]); _8 = call const fn baml.iter.Repeat.Iterator.next(copy _6) -> [bb25, unwind: bb28]; } bb21: { - _17 = load_type(null | bool | int | bigint | float | string[]); + _17 = load_type((null | bool | int | bigint | float | string)[]); _18 = load_type(never); _19 = load_type(never); _8 = call const fn baml.iter.Chain.Iterator.next(copy _6) -> [bb25, unwind: bb28]; } bb22: { - _14 = load_type(null | bool | int | bigint | float | string[]); + _14 = load_type((null | bool | int | bigint | float | string)[]); _15 = load_type(never); _8 = call const fn baml.iter.StepBy.Iterator.next(copy _6) -> [bb25, unwind: bb28]; } bb23: { - _12 = load_type(null | bool | int | bigint | float | string[]); + _12 = load_type((null | bool | int | bigint | float | string)[]); _8 = call const fn baml.iter.ArrayIterator.Iterator.next(copy _6) -> [bb25, unwind: bb28]; } @@ -3871,7 +3871,7 @@ fn .() -> null { _1 = make_closure lambda[0]<2 type_args>(copy capture[0]); _2 = load_type(#0); _3 = load_type(#1); - _4 = load_type(future); + _4 = load_type(future<#0, #1>); _0 = call const fn baml.Array.map(copy capture[0], copy _1) -> [bb1]; } @@ -3922,7 +3922,7 @@ fn ., 1)>(f: null) -> null { _5 = make_closure lambda[0]<2 type_args>(); _6 = load_type(bool); _7 = load_type(#1); - _8 = load_type(future); + _8 = load_type(future<#0, #1>); _4 = call const fn baml.Array.map(copy capture[0], copy _5) -> [bb4]; } @@ -4028,7 +4028,7 @@ fn .() -> null { _1 = make_closure lambda[0]<2 type_args>(); _2 = load_type(#0); _3 = load_type(#1); - _4 = load_type(future); + _4 = load_type(future<#0, #1>); _0 = call const fn baml.Array.map(copy capture[0], copy _1) -> [bb1]; } @@ -4154,7 +4154,7 @@ fn .() -> null { bb3: { _24 = copy _5; - _25 = load_type(future); + _25 = load_type(future<#0, #1>); _23 = call const fn baml.Array.slice(copy _2, const 0_i64, copy _24) -> [bb4]; } @@ -4166,12 +4166,12 @@ fn .() -> null { } bb5: { - _30 = load_type(future); + _30 = load_type(future<#0, #1>); _26 = call const fn baml.Array.slice(copy _2, copy _27, copy _29) -> [bb6]; } bb6: { - _31 = load_type(future); + _31 = load_type(future<#0, #1>); _2 = call const fn baml.Array.concat(copy _23, copy _26) -> [bb7]; } @@ -4249,7 +4249,7 @@ fn .() -> null { _53 = make_closure lambda[0]<2 type_args>(); _54 = load_type(bool); _55 = load_type(never); - _56 = load_type(future); + _56 = load_type(future<#0, #1>); _52 = call const fn baml.Array.map(copy capture[0], copy _53) -> [bb22]; } @@ -4396,7 +4396,7 @@ fn .() -> null { _4 = make_closure lambda[0]<2 type_args>(); _5 = load_type(bool); _6 = load_type(#1); - _7 = load_type(future); + _7 = load_type(future<#0, #1>); _3 = call const fn baml.Array.map(copy capture[0], copy _4) -> [bb2]; } @@ -4831,7 +4831,7 @@ fn baml.iter.Iterator.chain(self: baml.iter.Iterator, other: baml.iter.Iterable< } bb13: { - _30 = is_type(copy _2, T[]); + _30 = is_type(copy _2, _[]); branch copy _30 -> [bb17, bb14]; } @@ -4949,7 +4949,7 @@ fn baml.iter.Iterator.collect(self: baml.iter.Iterator) -> Item[] { } bb2: { - _4 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _4 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -4991,7 +4991,7 @@ fn baml.iter.Iterator.count(self: baml.iter.Iterator) -> int { } bb2: { - _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -5035,7 +5035,7 @@ fn baml.iter.Iterator.every(self: baml.iter.Iterator, predicate: (Item) -> bool } bb2: { - _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -5121,7 +5121,7 @@ fn baml.iter.Iterator.find(self: baml.iter.Iterator, predicate: (Item) -> bool t } bb2: { - _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -5209,7 +5209,7 @@ fn baml.iter.Iterator.for_each(self: baml.iter.Iterator, fn: (Item) -> void thro } bb2: { - _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -5583,7 +5583,7 @@ fn baml.iter.Filter.Iterator.next(self: null) -> T | baml.iter.Done { bb3: { _3 = copy _1.0; - _2 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb4]; + _2 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb4]; } bb4: { @@ -5665,40 +5665,41 @@ fn baml.iter.Flatten.Iterator.next(self: null) -> R | baml.iter.Done { let _43: R | baml.iter.Done let _44: bool let _45: R // y - let _46: I | baml.iter.Done - let _47: baml.iter.Iterator - let _48: bool - let _49: I // x - let _50: baml.iter.Iterator // it - let _51: bool - let _52: unknown - let _53: bool + let _46: bool + let _47: I | baml.iter.Done + let _48: baml.iter.Iterator + let _49: bool + let _50: I // x + let _51: baml.iter.Iterator // it + let _52: bool + let _53: unknown let _54: bool - let _55: type - let _56: bool - let _57: type + let _55: bool + let _56: type + let _57: bool let _58: type - let _59: bool - let _60: unknown - let _61: bool - let _62: type - let _63: bool - let _64: unknown - let _65: bool - let _66: unknown - let _67: bool - let _68: unknown - let _69: bool - let _70: unknown - let _71: bool - let _72: type + let _59: type + let _60: bool + let _61: unknown + let _62: bool + let _63: type + let _64: bool + let _65: unknown + let _66: bool + let _67: unknown + let _68: bool + let _69: unknown + let _70: bool + let _71: unknown + let _72: bool let _73: type - let _74: bool - let _75: type - let _76: bool + let _74: type + let _75: bool + let _76: type let _77: bool - let _78: type - let _79: bool + let _78: bool + let _79: type + let _80: bool bb0: { goto -> bb1; @@ -5710,400 +5711,409 @@ fn baml.iter.Flatten.Iterator.next(self: null) -> R | baml.iter.Done { bb2: { _0 = baml.iter.Done { }; - goto -> bb80; + goto -> bb82; } bb3: { _2 = copy _1.1; _3 = is_type(copy _2, baml.iter.Flatten<_, #1?, _, _>); - branch copy _3 -> [bb77, bb4]; + branch copy _3 -> [bb79, bb4]; } bb4: { _4 = is_type(copy _2, baml.iter.Flatten<_, #1?, _, _>); - branch copy _4 -> [bb77, bb5]; + branch copy _4 -> [bb79, bb5]; } bb5: { _5 = is_type(copy _2, baml.iter.Flatten<_, #1?, _, _>); - branch copy _5 -> [bb77, bb6]; + branch copy _5 -> [bb79, bb6]; } bb6: { _6 = is_type(copy _2, baml.iter.Range); - branch copy _6 -> [bb77, bb7]; + branch copy _6 -> [bb79, bb7]; } bb7: { _7 = is_type(copy _2, baml.iter.Range); - branch copy _7 -> [bb77, bb8]; + branch copy _7 -> [bb79, bb8]; } bb8: { _8 = is_type(copy _2, baml.iter.Range); - branch copy _8 -> [bb77, bb9]; + branch copy _8 -> [bb79, bb9]; } bb9: { _9 = is_type(copy _2, baml.iter.ArrayIterator<#1?>); - branch copy _9 -> [bb77, bb10]; + branch copy _9 -> [bb79, bb10]; } bb10: { _10 = is_type(copy _2, baml.iter.ArrayIterator<#1?>); - branch copy _10 -> [bb77, bb11]; + branch copy _10 -> [bb79, bb11]; } bb11: { _11 = is_type(copy _2, baml.iter.ArrayIterator<#1?>); - branch copy _11 -> [bb77, bb12]; + branch copy _11 -> [bb79, bb12]; } bb12: { _12 = is_type(copy _2, baml.iter.StepBy<#1?, #3?>); - branch copy _12 -> [bb77, bb13]; + branch copy _12 -> [bb79, bb13]; } bb13: { _13 = is_type(copy _2, baml.iter.StepBy<#1?, #3?>); - branch copy _13 -> [bb77, bb14]; + branch copy _13 -> [bb79, bb14]; } bb14: { _14 = is_type(copy _2, baml.iter.StepBy<#1?, #3?>); - branch copy _14 -> [bb77, bb15]; + branch copy _14 -> [bb79, bb15]; } bb15: { _15 = is_type(copy _2, baml.iter.Chain<#1?, _, _>); - branch copy _15 -> [bb77, bb16]; + branch copy _15 -> [bb79, bb16]; } bb16: { _16 = is_type(copy _2, baml.iter.Chain<#1?, _, _>); - branch copy _16 -> [bb77, bb17]; + branch copy _16 -> [bb79, bb17]; } bb17: { _17 = is_type(copy _2, baml.iter.Chain<#1?, _, _>); - branch copy _17 -> [bb77, bb18]; + branch copy _17 -> [bb79, bb18]; } bb18: { _18 = is_type(copy _2, baml.iter.Repeat<#1?>); - branch copy _18 -> [bb77, bb19]; + branch copy _18 -> [bb79, bb19]; } bb19: { _19 = is_type(copy _2, baml.iter.Repeat<#1?>); - branch copy _19 -> [bb77, bb20]; + branch copy _19 -> [bb79, bb20]; } bb20: { _20 = is_type(copy _2, baml.iter.Repeat<#1?>); - branch copy _20 -> [bb77, bb21]; + branch copy _20 -> [bb79, bb21]; } bb21: { _21 = is_type(copy _2, baml.iter.Map<_, #1?, _, _>); - branch copy _21 -> [bb77, bb22]; + branch copy _21 -> [bb79, bb22]; } bb22: { _22 = is_type(copy _2, baml.iter.Map<_, #1?, _, _>); - branch copy _22 -> [bb77, bb23]; + branch copy _22 -> [bb79, bb23]; } bb23: { _23 = is_type(copy _2, baml.iter.Map<_, #1?, _, _>); - branch copy _23 -> [bb77, bb24]; + branch copy _23 -> [bb79, bb24]; } bb24: { _24 = is_type(copy _2, baml.iter.Filter<#1?, _, _>); - branch copy _24 -> [bb77, bb25]; + branch copy _24 -> [bb79, bb25]; } bb25: { _25 = is_type(copy _2, baml.iter.Filter<#1?, _, _>); - branch copy _25 -> [bb77, bb26]; + branch copy _25 -> [bb79, bb26]; } bb26: { _26 = is_type(copy _2, baml.iter.Filter<#1?, _, _>); - branch copy _26 -> [bb77, bb27]; + branch copy _26 -> [bb79, bb27]; } bb27: { _27 = is_type(copy _2, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _27 -> [bb77, bb28]; + branch copy _27 -> [bb79, bb28]; } bb28: { _28 = is_type(copy _2, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _28 -> [bb77, bb29]; + branch copy _28 -> [bb79, bb29]; } bb29: { _29 = is_type(copy _2, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _29 -> [bb77, bb30]; + branch copy _29 -> [bb79, bb30]; } bb30: { _30 = is_type(copy _2, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _30 -> [bb77, bb31]; + branch copy _30 -> [bb79, bb31]; } bb31: { _31 = is_type(copy _2, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _31 -> [bb77, bb32]; + branch copy _31 -> [bb79, bb32]; } bb32: { _32 = is_type(copy _2, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _32 -> [bb77, bb33]; + branch copy _32 -> [bb79, bb33]; } bb33: { _33 = is_type(copy _2, baml.iter.Peekable<#1?, #3?>); - branch copy _33 -> [bb77, bb34]; + branch copy _33 -> [bb79, bb34]; } bb34: { _34 = is_type(copy _2, baml.iter.Peekable<#1?, #3?>); - branch copy _34 -> [bb77, bb35]; + branch copy _34 -> [bb79, bb35]; } bb35: { _35 = is_type(copy _2, baml.iter.Peekable<#1?, #3?>); - branch copy _35 -> [bb77, bb36]; + branch copy _35 -> [bb79, bb36]; } bb36: { _36 = is_type(copy _2, baml.csv.CsvRows<#1?>); - branch copy _36 -> [bb77, bb37]; + branch copy _36 -> [bb79, bb37]; } bb37: { _37 = is_type(copy _2, baml.csv.CsvRows<#1?>); - branch copy _37 -> [bb77, bb38]; + branch copy _37 -> [bb79, bb38]; } bb38: { _38 = is_type(copy _2, baml.csv.CsvRows<#1?>); - branch copy _38 -> [bb77, bb39]; + branch copy _38 -> [bb79, bb39]; } bb39: { _39 = is_type(copy _2, baml.csv.CsvReader); - branch copy _39 -> [bb77, bb40]; + branch copy _39 -> [bb79, bb40]; } bb40: { _40 = is_type(copy _2, baml.csv.CsvReader); - branch copy _40 -> [bb77, bb41]; + branch copy _40 -> [bb79, bb41]; } bb41: { _41 = is_type(copy _2, baml.csv.CsvReader); - branch copy _41 -> [bb77, bb42]; + branch copy _41 -> [bb79, bb42]; } bb42: { - _47 = copy _1.0; - _46 = virtual_call next as baml.iter.Iterator(copy _47) -> [bb43]; + _46 = is_type(copy _2, baml.iter.Done); + branch copy _46 -> [bb44, bb43]; } bb43: { - _48 = is_type(copy _46, baml.iter.Done); - branch copy _48 -> [bb76, bb44]; + unreachable; } bb44: { - _49 = copy _46; - _51 = is_type(copy _49, baml.iter.Flatten<_, #1?, _, _>); - branch copy _51 -> [bb74, bb45]; + _48 = copy _1.0; + _47 = virtual_call next as baml.iter.Iterator(copy _48) -> [bb45]; } bb45: { - _53 = is_type(copy _49, baml.iter.Range); - branch copy _53 -> [bb73, bb46]; + _49 = is_type(copy _47, baml.iter.Done); + branch copy _49 -> [bb78, bb46]; } bb46: { - _54 = is_type(copy _49, baml.iter.ArrayIterator<#1?>); - branch copy _54 -> [bb72, bb47]; + _50 = copy _47; + _52 = is_type(copy _50, baml.iter.Flatten<_, #1?, _, _>); + branch copy _52 -> [bb76, bb47]; } bb47: { - _56 = is_type(copy _49, baml.iter.StepBy<#1?, #3?>); - branch copy _56 -> [bb71, bb48]; + _54 = is_type(copy _50, baml.iter.Range); + branch copy _54 -> [bb75, bb48]; } bb48: { - _59 = is_type(copy _49, baml.iter.Chain<#1?, _, _>); - branch copy _59 -> [bb70, bb49]; + _55 = is_type(copy _50, baml.iter.ArrayIterator<#1?>); + branch copy _55 -> [bb74, bb49]; } bb49: { - _61 = is_type(copy _49, baml.iter.Repeat<#1?>); - branch copy _61 -> [bb69, bb50]; + _57 = is_type(copy _50, baml.iter.StepBy<#1?, #3?>); + branch copy _57 -> [bb73, bb50]; } bb50: { - _63 = is_type(copy _49, baml.iter.Map<_, #1?, _, _>); - branch copy _63 -> [bb68, bb51]; + _60 = is_type(copy _50, baml.iter.Chain<#1?, _, _>); + branch copy _60 -> [bb72, bb51]; } bb51: { - _65 = is_type(copy _49, baml.iter.Filter<#1?, _, _>); - branch copy _65 -> [bb67, bb52]; + _62 = is_type(copy _50, baml.iter.Repeat<#1?>); + branch copy _62 -> [bb71, bb52]; } bb52: { - _67 = is_type(copy _49, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _67 -> [bb66, bb53]; + _64 = is_type(copy _50, baml.iter.Map<_, #1?, _, _>); + branch copy _64 -> [bb70, bb53]; } bb53: { - _69 = is_type(copy _49, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _69 -> [bb65, bb54]; + _66 = is_type(copy _50, baml.iter.Filter<#1?, _, _>); + branch copy _66 -> [bb69, bb54]; } bb54: { - _71 = is_type(copy _49, baml.iter.Peekable<#1?, #3?>); - branch copy _71 -> [bb64, bb55]; + _68 = is_type(copy _50, baml.iter.FilterMap<_, #1?, _, _>); + branch copy _68 -> [bb68, bb55]; } bb55: { - _74 = is_type(copy _49, baml.csv.CsvRows<#1?>); - branch copy _74 -> [bb63, bb56]; + _70 = is_type(copy _50, baml.iter.FlatMap<_, #1?, _, _, _>); + branch copy _70 -> [bb67, bb56]; } bb56: { - _76 = is_type(copy _49, baml.csv.CsvReader); - branch copy _76 -> [bb62, bb57]; + _72 = is_type(copy _50, baml.iter.Peekable<#1?, #3?>); + branch copy _72 -> [bb66, bb57]; } bb57: { - _77 = is_type(copy _49, T[]); - branch copy _77 -> [bb61, bb58]; + _75 = is_type(copy _50, baml.csv.CsvRows<#1?>); + branch copy _75 -> [bb65, bb58]; } bb58: { - _79 = is_type(copy _49, string); - branch copy _79 -> [bb60, bb59]; + _77 = is_type(copy _50, baml.csv.CsvReader); + branch copy _77 -> [bb64, bb59]; } bb59: { - unreachable; + _78 = is_type(copy _50, _[]); + branch copy _78 -> [bb63, bb60]; } bb60: { - _50 = call const fn baml.iter.Iterable$for$string.iter(copy _49) -> [bb75]; + _80 = is_type(copy _50, string); + branch copy _80 -> [bb62, bb61]; } bb61: { - _78 = load_type(#1); - _50 = call const fn baml.iter.Iterable$for$T[].iter(copy _49) -> [bb75]; + unreachable; } bb62: { - _50 = call const fn baml.csv.CsvReader.root.iter.Iterable.iter(copy _49) -> [bb75]; + _51 = call const fn baml.iter.Iterable$for$string.iter(copy _50) -> [bb77]; } bb63: { - _75 = load_type(#1); - _50 = call const fn baml.csv.CsvRows.root.iter.Iterable.iter(copy _49) -> [bb75]; + _79 = load_type(#1); + _51 = call const fn baml.iter.Iterable$for$T[].iter(copy _50) -> [bb77]; } bb64: { - _72 = load_type(#1); - _73 = load_type(#3); - _50 = call const fn baml.iter.Peekable.Iterable.iter(copy _49) -> [bb75]; + _51 = call const fn baml.csv.CsvReader.root.iter.Iterable.iter(copy _50) -> [bb77]; } bb65: { - _70 = make_bound_method baml.iter.FlatMap.Iterable.iter(copy _49); - _50 = call copy _70() -> [bb75]; + _76 = load_type(#1); + _51 = call const fn baml.csv.CsvRows.root.iter.Iterable.iter(copy _50) -> [bb77]; } bb66: { - _68 = make_bound_method baml.iter.FilterMap.Iterable.iter(copy _49); - _50 = call copy _68() -> [bb75]; + _73 = load_type(#1); + _74 = load_type(#3); + _51 = call const fn baml.iter.Peekable.Iterable.iter(copy _50) -> [bb77]; } bb67: { - _66 = make_bound_method baml.iter.Filter.Iterable.iter(copy _49); - _50 = call copy _66() -> [bb75]; + _71 = make_bound_method baml.iter.FlatMap.Iterable.iter(copy _50); + _51 = call copy _71() -> [bb77]; } bb68: { - _64 = make_bound_method baml.iter.Map.Iterable.iter(copy _49); - _50 = call copy _64() -> [bb75]; + _69 = make_bound_method baml.iter.FilterMap.Iterable.iter(copy _50); + _51 = call copy _69() -> [bb77]; } bb69: { - _62 = load_type(#1); - _50 = call const fn baml.iter.Repeat.Iterable.iter(copy _49) -> [bb75]; + _67 = make_bound_method baml.iter.Filter.Iterable.iter(copy _50); + _51 = call copy _67() -> [bb77]; } bb70: { - _60 = make_bound_method baml.iter.Chain.Iterable.iter(copy _49); - _50 = call copy _60() -> [bb75]; + _65 = make_bound_method baml.iter.Map.Iterable.iter(copy _50); + _51 = call copy _65() -> [bb77]; } bb71: { - _57 = load_type(#1); - _58 = load_type(#3); - _50 = call const fn baml.iter.StepBy.Iterable.iter(copy _49) -> [bb75]; + _63 = load_type(#1); + _51 = call const fn baml.iter.Repeat.Iterable.iter(copy _50) -> [bb77]; } bb72: { - _55 = load_type(#1); - _50 = call const fn baml.iter.ArrayIterator.Iterable.iter(copy _49) -> [bb75]; + _61 = make_bound_method baml.iter.Chain.Iterable.iter(copy _50); + _51 = call copy _61() -> [bb77]; } bb73: { - _50 = call const fn baml.iter.Range.Iterable.iter(copy _49) -> [bb75]; + _58 = load_type(#1); + _59 = load_type(#3); + _51 = call const fn baml.iter.StepBy.Iterable.iter(copy _50) -> [bb77]; } bb74: { - _52 = make_bound_method baml.iter.Flatten.Iterable.iter(copy _49); - _50 = call copy _52() -> [bb75]; + _56 = load_type(#1); + _51 = call const fn baml.iter.ArrayIterator.Iterable.iter(copy _50) -> [bb77]; } bb75: { - _1.1 = copy _50; - goto -> bb1; + _51 = call const fn baml.iter.Range.Iterable.iter(copy _50) -> [bb77]; } bb76: { - _0 = baml.iter.Done { }; - goto -> bb80; + _53 = make_bound_method baml.iter.Flatten.Iterable.iter(copy _50); + _51 = call copy _53() -> [bb77]; } bb77: { - _42 = copy _2; - _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb78]; + _1.1 = copy _51; + goto -> bb1; } bb78: { - _44 = is_type(copy _43, baml.iter.Done); - branch copy _44 -> [bb81, bb79]; + _0 = baml.iter.Done { }; + goto -> bb82; } bb79: { + _42 = copy _2; + _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb80]; + } + + bb80: { + _44 = is_type(copy _43, baml.iter.Done); + branch copy _44 -> [bb83, bb81]; + } + + bb81: { _45 = copy _43; _0 = copy _45; - goto -> bb80; + goto -> bb82; } - bb80: { + bb82: { return; } - bb81: { + bb83: { _1.1 = baml.iter.Done { }; goto -> bb1; } @@ -6129,7 +6139,7 @@ fn baml.iter.Chain.Iterator.next(self: null) -> T | baml.iter.Done { bb1: { _5 = copy _1.0; - _4 = virtual_call next as baml.iter.Iterator(copy _5) -> [bb2]; + _4 = virtual_call next as baml.iter.Iterator(copy _5) -> [bb2]; } bb2: { @@ -6150,7 +6160,7 @@ fn baml.iter.Chain.Iterator.next(self: null) -> T | baml.iter.Done { bb5: { _8 = copy _1.1; - _0 = virtual_call next as baml.iter.Iterator(copy _8) -> [bb6]; + _0 = virtual_call next as baml.iter.Iterator(copy _8) -> [bb6]; } bb6: { @@ -6221,7 +6231,7 @@ fn baml.iter.Map.Iterator.next(self: null) -> R | baml.iter.Done { bb0: { _3 = copy _1.0; - _2 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb1]; + _2 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb1]; } bb1: { @@ -6294,43 +6304,44 @@ fn baml.iter.FlatMap.Iterator.next(self: null) -> R | baml.iter.Done { let _43: R | baml.iter.Done let _44: bool let _45: R // y - let _46: T | baml.iter.Done - let _47: baml.iter.Iterator - let _48: bool - let _49: T // x - let _50: baml.iter.Iterator // it - let _51: baml.iter.Iterable - let _52: (T) -> baml.iter.Iterable throws E2 - let _53: T - let _54: bool - let _55: unknown - let _56: bool + let _46: bool + let _47: T | baml.iter.Done + let _48: baml.iter.Iterator + let _49: bool + let _50: T // x + let _51: baml.iter.Iterator // it + let _52: baml.iter.Iterable + let _53: (T) -> baml.iter.Iterable throws E2 + let _54: T + let _55: bool + let _56: unknown let _57: bool - let _58: type - let _59: bool - let _60: type + let _58: bool + let _59: type + let _60: bool let _61: type - let _62: bool - let _63: unknown - let _64: bool - let _65: type - let _66: bool - let _67: unknown - let _68: bool - let _69: unknown - let _70: bool - let _71: unknown - let _72: bool - let _73: unknown - let _74: bool - let _75: type + let _62: type + let _63: bool + let _64: unknown + let _65: bool + let _66: type + let _67: bool + let _68: unknown + let _69: bool + let _70: unknown + let _71: bool + let _72: unknown + let _73: bool + let _74: unknown + let _75: bool let _76: type - let _77: bool - let _78: type - let _79: bool + let _77: type + let _78: bool + let _79: type let _80: bool - let _81: type - let _82: bool + let _81: bool + let _82: type + let _83: bool bb0: { goto -> bb1; @@ -6342,406 +6353,415 @@ fn baml.iter.FlatMap.Iterator.next(self: null) -> R | baml.iter.Done { bb2: { _0 = baml.iter.Done { }; - goto -> bb81; + goto -> bb83; } bb3: { _2 = copy _1.2; _3 = is_type(copy _2, baml.iter.Flatten<_, #1?, _, _>); - branch copy _3 -> [bb78, bb4]; + branch copy _3 -> [bb80, bb4]; } bb4: { _4 = is_type(copy _2, baml.iter.Flatten<_, #1?, _, _>); - branch copy _4 -> [bb78, bb5]; + branch copy _4 -> [bb80, bb5]; } bb5: { _5 = is_type(copy _2, baml.iter.Flatten<_, #1?, _, _>); - branch copy _5 -> [bb78, bb6]; + branch copy _5 -> [bb80, bb6]; } bb6: { _6 = is_type(copy _2, baml.iter.Range); - branch copy _6 -> [bb78, bb7]; + branch copy _6 -> [bb80, bb7]; } bb7: { _7 = is_type(copy _2, baml.iter.Range); - branch copy _7 -> [bb78, bb8]; + branch copy _7 -> [bb80, bb8]; } bb8: { _8 = is_type(copy _2, baml.iter.Range); - branch copy _8 -> [bb78, bb9]; + branch copy _8 -> [bb80, bb9]; } bb9: { _9 = is_type(copy _2, baml.iter.ArrayIterator<#1?>); - branch copy _9 -> [bb78, bb10]; + branch copy _9 -> [bb80, bb10]; } bb10: { _10 = is_type(copy _2, baml.iter.ArrayIterator<#1?>); - branch copy _10 -> [bb78, bb11]; + branch copy _10 -> [bb80, bb11]; } bb11: { _11 = is_type(copy _2, baml.iter.ArrayIterator<#1?>); - branch copy _11 -> [bb78, bb12]; + branch copy _11 -> [bb80, bb12]; } bb12: { _12 = is_type(copy _2, baml.iter.StepBy<#1?, #4?>); - branch copy _12 -> [bb78, bb13]; + branch copy _12 -> [bb80, bb13]; } bb13: { _13 = is_type(copy _2, baml.iter.StepBy<#1?, #4?>); - branch copy _13 -> [bb78, bb14]; + branch copy _13 -> [bb80, bb14]; } bb14: { _14 = is_type(copy _2, baml.iter.StepBy<#1?, #4?>); - branch copy _14 -> [bb78, bb15]; + branch copy _14 -> [bb80, bb15]; } bb15: { _15 = is_type(copy _2, baml.iter.Chain<#1?, _, _>); - branch copy _15 -> [bb78, bb16]; + branch copy _15 -> [bb80, bb16]; } bb16: { _16 = is_type(copy _2, baml.iter.Chain<#1?, _, _>); - branch copy _16 -> [bb78, bb17]; + branch copy _16 -> [bb80, bb17]; } bb17: { _17 = is_type(copy _2, baml.iter.Chain<#1?, _, _>); - branch copy _17 -> [bb78, bb18]; + branch copy _17 -> [bb80, bb18]; } bb18: { _18 = is_type(copy _2, baml.iter.Repeat<#1?>); - branch copy _18 -> [bb78, bb19]; + branch copy _18 -> [bb80, bb19]; } bb19: { _19 = is_type(copy _2, baml.iter.Repeat<#1?>); - branch copy _19 -> [bb78, bb20]; + branch copy _19 -> [bb80, bb20]; } bb20: { _20 = is_type(copy _2, baml.iter.Repeat<#1?>); - branch copy _20 -> [bb78, bb21]; + branch copy _20 -> [bb80, bb21]; } bb21: { _21 = is_type(copy _2, baml.iter.Map<_, #1?, _, _>); - branch copy _21 -> [bb78, bb22]; + branch copy _21 -> [bb80, bb22]; } bb22: { _22 = is_type(copy _2, baml.iter.Map<_, #1?, _, _>); - branch copy _22 -> [bb78, bb23]; + branch copy _22 -> [bb80, bb23]; } bb23: { _23 = is_type(copy _2, baml.iter.Map<_, #1?, _, _>); - branch copy _23 -> [bb78, bb24]; + branch copy _23 -> [bb80, bb24]; } bb24: { _24 = is_type(copy _2, baml.iter.Filter<#1?, _, _>); - branch copy _24 -> [bb78, bb25]; + branch copy _24 -> [bb80, bb25]; } bb25: { _25 = is_type(copy _2, baml.iter.Filter<#1?, _, _>); - branch copy _25 -> [bb78, bb26]; + branch copy _25 -> [bb80, bb26]; } bb26: { _26 = is_type(copy _2, baml.iter.Filter<#1?, _, _>); - branch copy _26 -> [bb78, bb27]; + branch copy _26 -> [bb80, bb27]; } bb27: { _27 = is_type(copy _2, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _27 -> [bb78, bb28]; + branch copy _27 -> [bb80, bb28]; } bb28: { _28 = is_type(copy _2, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _28 -> [bb78, bb29]; + branch copy _28 -> [bb80, bb29]; } bb29: { _29 = is_type(copy _2, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _29 -> [bb78, bb30]; + branch copy _29 -> [bb80, bb30]; } bb30: { _30 = is_type(copy _2, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _30 -> [bb78, bb31]; + branch copy _30 -> [bb80, bb31]; } bb31: { _31 = is_type(copy _2, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _31 -> [bb78, bb32]; + branch copy _31 -> [bb80, bb32]; } bb32: { _32 = is_type(copy _2, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _32 -> [bb78, bb33]; + branch copy _32 -> [bb80, bb33]; } bb33: { _33 = is_type(copy _2, baml.iter.Peekable<#1?, #4?>); - branch copy _33 -> [bb78, bb34]; + branch copy _33 -> [bb80, bb34]; } bb34: { _34 = is_type(copy _2, baml.iter.Peekable<#1?, #4?>); - branch copy _34 -> [bb78, bb35]; + branch copy _34 -> [bb80, bb35]; } bb35: { _35 = is_type(copy _2, baml.iter.Peekable<#1?, #4?>); - branch copy _35 -> [bb78, bb36]; + branch copy _35 -> [bb80, bb36]; } bb36: { _36 = is_type(copy _2, baml.csv.CsvRows<#1?>); - branch copy _36 -> [bb78, bb37]; + branch copy _36 -> [bb80, bb37]; } bb37: { _37 = is_type(copy _2, baml.csv.CsvRows<#1?>); - branch copy _37 -> [bb78, bb38]; + branch copy _37 -> [bb80, bb38]; } bb38: { _38 = is_type(copy _2, baml.csv.CsvRows<#1?>); - branch copy _38 -> [bb78, bb39]; + branch copy _38 -> [bb80, bb39]; } bb39: { _39 = is_type(copy _2, baml.csv.CsvReader); - branch copy _39 -> [bb78, bb40]; + branch copy _39 -> [bb80, bb40]; } bb40: { _40 = is_type(copy _2, baml.csv.CsvReader); - branch copy _40 -> [bb78, bb41]; + branch copy _40 -> [bb80, bb41]; } bb41: { _41 = is_type(copy _2, baml.csv.CsvReader); - branch copy _41 -> [bb78, bb42]; + branch copy _41 -> [bb80, bb42]; } bb42: { - _47 = copy _1.0; - _46 = virtual_call next as baml.iter.Iterator(copy _47) -> [bb43]; + _46 = is_type(copy _2, baml.iter.Done); + branch copy _46 -> [bb44, bb43]; } bb43: { - _48 = is_type(copy _46, baml.iter.Done); - branch copy _48 -> [bb77, bb44]; + unreachable; } bb44: { - _49 = copy _46; - _52 = copy _1.1; - _53 = copy _49; - _51 = call copy _52(copy _53) -> [bb45]; + _48 = copy _1.0; + _47 = virtual_call next as baml.iter.Iterator(copy _48) -> [bb45]; } bb45: { - _54 = is_type(copy _51, baml.iter.Flatten<_, #1?, _, _>); - branch copy _54 -> [bb75, bb46]; + _49 = is_type(copy _47, baml.iter.Done); + branch copy _49 -> [bb79, bb46]; } bb46: { - _56 = is_type(copy _51, baml.iter.Range); - branch copy _56 -> [bb74, bb47]; + _50 = copy _47; + _53 = copy _1.1; + _54 = copy _50; + _52 = call copy _53(copy _54) -> [bb47]; } bb47: { - _57 = is_type(copy _51, baml.iter.ArrayIterator<#1?>); - branch copy _57 -> [bb73, bb48]; + _55 = is_type(copy _52, baml.iter.Flatten<_, #1?, _, _>); + branch copy _55 -> [bb77, bb48]; } bb48: { - _59 = is_type(copy _51, baml.iter.StepBy<#1?, #4?>); - branch copy _59 -> [bb72, bb49]; + _57 = is_type(copy _52, baml.iter.Range); + branch copy _57 -> [bb76, bb49]; } bb49: { - _62 = is_type(copy _51, baml.iter.Chain<#1?, _, _>); - branch copy _62 -> [bb71, bb50]; + _58 = is_type(copy _52, baml.iter.ArrayIterator<#1?>); + branch copy _58 -> [bb75, bb50]; } bb50: { - _64 = is_type(copy _51, baml.iter.Repeat<#1?>); - branch copy _64 -> [bb70, bb51]; + _60 = is_type(copy _52, baml.iter.StepBy<#1?, #4?>); + branch copy _60 -> [bb74, bb51]; } bb51: { - _66 = is_type(copy _51, baml.iter.Map<_, #1?, _, _>); - branch copy _66 -> [bb69, bb52]; + _63 = is_type(copy _52, baml.iter.Chain<#1?, _, _>); + branch copy _63 -> [bb73, bb52]; } bb52: { - _68 = is_type(copy _51, baml.iter.Filter<#1?, _, _>); - branch copy _68 -> [bb68, bb53]; + _65 = is_type(copy _52, baml.iter.Repeat<#1?>); + branch copy _65 -> [bb72, bb53]; } bb53: { - _70 = is_type(copy _51, baml.iter.FilterMap<_, #1?, _, _>); - branch copy _70 -> [bb67, bb54]; + _67 = is_type(copy _52, baml.iter.Map<_, #1?, _, _>); + branch copy _67 -> [bb71, bb54]; } bb54: { - _72 = is_type(copy _51, baml.iter.FlatMap<_, #1?, _, _, _>); - branch copy _72 -> [bb66, bb55]; + _69 = is_type(copy _52, baml.iter.Filter<#1?, _, _>); + branch copy _69 -> [bb70, bb55]; } bb55: { - _74 = is_type(copy _51, baml.iter.Peekable<#1?, #4?>); - branch copy _74 -> [bb65, bb56]; + _71 = is_type(copy _52, baml.iter.FilterMap<_, #1?, _, _>); + branch copy _71 -> [bb69, bb56]; } bb56: { - _77 = is_type(copy _51, baml.csv.CsvRows<#1?>); - branch copy _77 -> [bb64, bb57]; + _73 = is_type(copy _52, baml.iter.FlatMap<_, #1?, _, _, _>); + branch copy _73 -> [bb68, bb57]; } bb57: { - _79 = is_type(copy _51, baml.csv.CsvReader); - branch copy _79 -> [bb63, bb58]; + _75 = is_type(copy _52, baml.iter.Peekable<#1?, #4?>); + branch copy _75 -> [bb67, bb58]; } bb58: { - _80 = is_type(copy _51, T[]); - branch copy _80 -> [bb62, bb59]; + _78 = is_type(copy _52, baml.csv.CsvRows<#1?>); + branch copy _78 -> [bb66, bb59]; } bb59: { - _82 = is_type(copy _51, string); - branch copy _82 -> [bb61, bb60]; + _80 = is_type(copy _52, baml.csv.CsvReader); + branch copy _80 -> [bb65, bb60]; } bb60: { - unreachable; + _81 = is_type(copy _52, _[]); + branch copy _81 -> [bb64, bb61]; } bb61: { - _50 = call const fn baml.iter.Iterable$for$string.iter(copy _51) -> [bb76]; + _83 = is_type(copy _52, string); + branch copy _83 -> [bb63, bb62]; } bb62: { - _81 = load_type(#1); - _50 = call const fn baml.iter.Iterable$for$T[].iter(copy _51) -> [bb76]; + unreachable; } bb63: { - _50 = call const fn baml.csv.CsvReader.root.iter.Iterable.iter(copy _51) -> [bb76]; + _51 = call const fn baml.iter.Iterable$for$string.iter(copy _52) -> [bb78]; } bb64: { - _78 = load_type(#1); - _50 = call const fn baml.csv.CsvRows.root.iter.Iterable.iter(copy _51) -> [bb76]; + _82 = load_type(#1); + _51 = call const fn baml.iter.Iterable$for$T[].iter(copy _52) -> [bb78]; } bb65: { - _75 = load_type(#1); - _76 = load_type(#4); - _50 = call const fn baml.iter.Peekable.Iterable.iter(copy _51) -> [bb76]; + _51 = call const fn baml.csv.CsvReader.root.iter.Iterable.iter(copy _52) -> [bb78]; } bb66: { - _73 = make_bound_method baml.iter.FlatMap.Iterable.iter(copy _51); - _50 = call copy _73() -> [bb76]; + _79 = load_type(#1); + _51 = call const fn baml.csv.CsvRows.root.iter.Iterable.iter(copy _52) -> [bb78]; } bb67: { - _71 = make_bound_method baml.iter.FilterMap.Iterable.iter(copy _51); - _50 = call copy _71() -> [bb76]; + _76 = load_type(#1); + _77 = load_type(#4); + _51 = call const fn baml.iter.Peekable.Iterable.iter(copy _52) -> [bb78]; } bb68: { - _69 = make_bound_method baml.iter.Filter.Iterable.iter(copy _51); - _50 = call copy _69() -> [bb76]; + _74 = make_bound_method baml.iter.FlatMap.Iterable.iter(copy _52); + _51 = call copy _74() -> [bb78]; } bb69: { - _67 = make_bound_method baml.iter.Map.Iterable.iter(copy _51); - _50 = call copy _67() -> [bb76]; + _72 = make_bound_method baml.iter.FilterMap.Iterable.iter(copy _52); + _51 = call copy _72() -> [bb78]; } bb70: { - _65 = load_type(#1); - _50 = call const fn baml.iter.Repeat.Iterable.iter(copy _51) -> [bb76]; + _70 = make_bound_method baml.iter.Filter.Iterable.iter(copy _52); + _51 = call copy _70() -> [bb78]; } bb71: { - _63 = make_bound_method baml.iter.Chain.Iterable.iter(copy _51); - _50 = call copy _63() -> [bb76]; + _68 = make_bound_method baml.iter.Map.Iterable.iter(copy _52); + _51 = call copy _68() -> [bb78]; } bb72: { - _60 = load_type(#1); - _61 = load_type(#4); - _50 = call const fn baml.iter.StepBy.Iterable.iter(copy _51) -> [bb76]; + _66 = load_type(#1); + _51 = call const fn baml.iter.Repeat.Iterable.iter(copy _52) -> [bb78]; } bb73: { - _58 = load_type(#1); - _50 = call const fn baml.iter.ArrayIterator.Iterable.iter(copy _51) -> [bb76]; + _64 = make_bound_method baml.iter.Chain.Iterable.iter(copy _52); + _51 = call copy _64() -> [bb78]; } bb74: { - _50 = call const fn baml.iter.Range.Iterable.iter(copy _51) -> [bb76]; + _61 = load_type(#1); + _62 = load_type(#4); + _51 = call const fn baml.iter.StepBy.Iterable.iter(copy _52) -> [bb78]; } bb75: { - _55 = make_bound_method baml.iter.Flatten.Iterable.iter(copy _51); - _50 = call copy _55() -> [bb76]; + _59 = load_type(#1); + _51 = call const fn baml.iter.ArrayIterator.Iterable.iter(copy _52) -> [bb78]; } bb76: { - _1.2 = copy _50; - goto -> bb1; + _51 = call const fn baml.iter.Range.Iterable.iter(copy _52) -> [bb78]; } bb77: { - _0 = baml.iter.Done { }; - goto -> bb81; + _56 = make_bound_method baml.iter.Flatten.Iterable.iter(copy _52); + _51 = call copy _56() -> [bb78]; } bb78: { - _42 = copy _2; - _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb79]; + _1.2 = copy _51; + goto -> bb1; } bb79: { - _44 = is_type(copy _43, baml.iter.Done); - branch copy _44 -> [bb82, bb80]; + _0 = baml.iter.Done { }; + goto -> bb83; } bb80: { + _42 = copy _2; + _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb81]; + } + + bb81: { + _44 = is_type(copy _43, baml.iter.Done); + branch copy _44 -> [bb84, bb82]; + } + + bb82: { _45 = copy _43; _0 = copy _45; - goto -> bb81; + goto -> bb83; } - bb81: { + bb83: { return; } - bb82: { + bb84: { _1.2 = baml.iter.Done { }; goto -> bb1; } @@ -6783,12 +6803,12 @@ fn baml.iter.StepBy.Iterator.next(self: null) -> T | baml.iter.Done { bb3: { _12 = copy _1.0; - _0 = virtual_call next as baml.iter.Iterator(copy _12) -> [bb9]; + _0 = virtual_call next as baml.iter.Iterator(copy _12) -> [bb9]; } bb4: { _10 = copy _1.0; - _9 = virtual_call next as baml.iter.Iterator(copy _10) -> [bb5]; + _9 = virtual_call next as baml.iter.Iterator(copy _10) -> [bb5]; } bb5: { @@ -6809,7 +6829,7 @@ fn baml.iter.StepBy.Iterator.next(self: null) -> T | baml.iter.Done { bb8: { _1.2 = const false; _3 = copy _1.0; - _0 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb9]; + _0 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb9]; } bb9: { @@ -6888,7 +6908,7 @@ fn baml.iter.FilterMap.Iterator.next(self: null) -> R | baml.iter.Done { bb3: { _3 = copy _1.0; - _2 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb4]; + _2 = virtual_call next as baml.iter.Iterator(copy _3) -> [bb4]; } bb4: { @@ -6939,7 +6959,7 @@ fn baml.iter.Peekable.Iterator.next(self: null) -> T | baml.iter.Done { bb1: { _4 = copy _1.0; - _0 = virtual_call next as baml.iter.Iterator(copy _4) -> [bb3]; + _0 = virtual_call next as baml.iter.Iterator(copy _4) -> [bb3]; } bb2: { @@ -6970,7 +6990,7 @@ fn baml.iter.Peekable.peek(self: baml.iter.Peekable) -> T | baml.iter.Done { bb1: { _4 = copy _1.0; - _3 = virtual_call next as baml.iter.Iterator(copy _4) -> [bb2]; + _3 = virtual_call next as baml.iter.Iterator(copy _4) -> [bb2]; } bb2: { @@ -7030,7 +7050,7 @@ fn baml.iter.Iterator.reduce(self: baml.iter.Iterator, fn: (A, Item) -> A throws } bb2: { - _5 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _5 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -7075,7 +7095,7 @@ fn baml.iter.Iterator.some(self: baml.iter.Iterator, predicate: (Item) -> bool t } bb2: { - _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; + _3 = virtual_call next as baml.iter.Iterator(copy _1) -> [bb3]; } bb3: { @@ -10596,12 +10616,12 @@ fn baml.ops.Equals.neq(self: baml.ops.Equals, other: Self) -> bool { } bb7: { - _11 = is_type(copy _1, T[]); + _11 = is_type(copy _1, _[]); branch copy _11 -> [bb11, bb8]; } bb8: { - _13 = is_type(copy _1, map); + _13 = is_type(copy _1, map); branch copy _13 -> [bb10, bb9]; } @@ -13699,43 +13719,94 @@ fn baml.toml.item_from_json(json: baml.json.json) -> baml.toml.Item { // Locals: let _0: baml.toml.Item // _0 // return let _1: baml.json.json // json // param - let _2: int - let _3: baml.json.JsonDecodeError - let _4: type - let _5: type - let _6: type + let _2: bool + let _3: bool + let _4: bool + let _5: bool + let _6: bool + let _7: baml.json.JsonDecodeError + let _8: bool + let _9: bool + let _10: type + let _11: type + let _12: type bb0: { - _2 = type_tag(_1); - switch copy _2 [bool: bb5, int: bb5, float: bb5, string: bb5, null: bb4, map: bb3, list: bb2, otherwise: bb1] (exhaustive); + _2 = is_type(copy _1, bool); + branch copy _2 -> [bb14, bb1]; } bb1: { - unreachable; + _3 = is_type(copy _1, int); + branch copy _3 -> [bb13, bb2]; } bb2: { - _4 = load_type(baml.toml.Item); - _5 = load_type(baml.json.JsonDecodeError); - _6 = load_type(baml.json.json); - _0 = call const fn baml.Array.map(copy _1, const fn baml.toml.item_from_json) -> [bb6]; + _4 = is_type(copy _1, float); + branch copy _4 -> [bb12, bb3]; } bb3: { - _0 = call const fn baml.toml.Table.baml.FromJson.from_json(copy _1) -> [bb6]; + _5 = is_type(copy _1, string); + branch copy _5 -> [bb11, bb4]; } bb4: { - _3 = baml.json.JsonDecodeError { const "TOML does not support null", const "" }; - throw copy _3; + _6 = copy _1 == const null; + branch copy _6 -> [bb10, bb5]; } bb5: { - _0 = copy _1; - goto -> bb6; + _8 = is_type(copy _1, map); + branch copy _8 -> [bb9, bb6]; } bb6: { + _9 = is_type(copy _1, baml.json.json[]); + branch copy _9 -> [bb8, bb7]; + } + + bb7: { + unreachable; + } + + bb8: { + _10 = load_type(baml.toml.Item); + _11 = load_type(baml.json.JsonDecodeError); + _12 = load_type(baml.json.json); + _0 = call const fn baml.Array.map(copy _1, const fn baml.toml.item_from_json) -> [bb15]; + } + + bb9: { + _0 = call const fn baml.toml.Table.baml.FromJson.from_json(copy _1) -> [bb15]; + } + + bb10: { + _7 = baml.json.JsonDecodeError { const "TOML does not support null", const "" }; + throw copy _7; + } + + bb11: { + _0 = copy _1; + goto -> bb15; + } + + bb12: { + _0 = copy _1; + goto -> bb15; + } + + bb13: { + _0 = copy _1; + goto -> bb15; + } + + bb14: { + _0 = copy _1; + goto -> bb15; + } + + bb15: { return; } } @@ -13744,7 +13815,7 @@ fn baml.toml.item_to_json(item: baml.toml.Item) -> baml.json.json { // Locals: let _0: baml.json.json // _0 // return let _1: baml.toml.Item // item // param - let _2: int + let _2: bool let _3: bool let _4: bool let _5: bool @@ -13757,14 +13828,14 @@ fn baml.toml.item_to_json(item: baml.toml.Item) -> baml.json.json { let _12: bool let _13: bool let _14: bool - let _15: type - let _16: type - let _17: type + let _15: bool + let _16: bool + let _17: bool let _18: bool let _19: bool - let _20: bool - let _21: bool - let _22: bool + let _20: type + let _21: type + let _22: type let _23: bool let _24: bool let _25: bool @@ -13808,589 +13879,658 @@ fn baml.toml.item_to_json(item: baml.toml.Item) -> baml.json.json { let _63: bool let _64: bool let _65: bool + let _66: bool + let _67: bool + let _68: bool + let _69: bool + let _70: bool + let _71: bool + let _72: bool + let _73: bool + let _74: bool bb0: { - _2 = type_tag(_1); - switch copy _2 [bool: bb128, int: bb128, float: bb128, string: bb128, Table: bb103, list: bb102, ZonedDateTime: bb77, PlainDateTime: bb52, PlainDate: bb27, PlainTime: bb2, otherwise: bb1] (exhaustive); + _2 = is_type(copy _1, bool); + branch copy _2 -> [bb140, bb1]; } bb1: { - unreachable; + _3 = is_type(copy _1, int); + branch copy _3 -> [bb139, bb2]; } bb2: { - _54 = is_type(copy _1, baml.toml.Table); - branch copy _54 -> [bb26, bb3]; + _4 = is_type(copy _1, float); + branch copy _4 -> [bb138, bb3]; } bb3: { - _55 = is_type(copy _1, baml.toml.Table); - branch copy _55 -> [bb25, bb4]; + _5 = is_type(copy _1, string); + branch copy _5 -> [bb137, bb4]; } bb4: { - _56 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _56 -> [bb24, bb5]; + _6 = is_type(copy _1, baml.toml.Table); + branch copy _6 -> [bb112, bb5]; } bb5: { - _57 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _57 -> [bb23, bb6]; + _19 = is_type(copy _1, baml.toml.Item[]); + branch copy _19 -> [bb111, bb6]; } bb6: { - _58 = is_type(copy _1, baml.time.PlainTime); - branch copy _58 -> [bb22, bb7]; + _23 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _23 -> [bb86, bb7]; } bb7: { - _59 = is_type(copy _1, baml.time.PlainTime); - branch copy _59 -> [bb21, bb8]; + _36 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _36 -> [bb61, bb8]; } bb8: { - _60 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _60 -> [bb20, bb9]; + _49 = is_type(copy _1, baml.time.PlainDate); + branch copy _49 -> [bb36, bb9]; } bb9: { - _61 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _61 -> [bb19, bb10]; + _62 = is_type(copy _1, baml.time.PlainTime); + branch copy _62 -> [bb11, bb10]; } bb10: { - _62 = is_type(copy _1, baml.time.Instant); - branch copy _62 -> [bb18, bb11]; + unreachable; } bb11: { - _63 = is_type(copy _1, baml.time.Instant); - branch copy _63 -> [bb17, bb12]; + _63 = is_type(copy _1, baml.toml.Table); + branch copy _63 -> [bb35, bb12]; } bb12: { - _64 = is_type(copy _1, baml.time.PlainDate); - branch copy _64 -> [bb16, bb13]; + _64 = is_type(copy _1, baml.toml.Table); + branch copy _64 -> [bb34, bb13]; } bb13: { - _65 = is_type(copy _1, baml.time.PlainDate); - branch copy _65 -> [bb15, bb14]; + _65 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _65 -> [bb33, bb14]; } bb14: { - unreachable; + _66 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _66 -> [bb32, bb15]; } bb15: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _67 = is_type(copy _1, baml.time.PlainTime); + branch copy _67 -> [bb31, bb16]; } bb16: { - _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb129]; + _68 = is_type(copy _1, baml.time.PlainTime); + branch copy _68 -> [bb30, bb17]; } bb17: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _69 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _69 -> [bb29, bb18]; } bb18: { - _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb129]; + _70 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _70 -> [bb28, bb19]; } bb19: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _71 = is_type(copy _1, baml.time.Instant); + branch copy _71 -> [bb27, bb20]; } bb20: { - _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _72 = is_type(copy _1, baml.time.Instant); + branch copy _72 -> [bb26, bb21]; } bb21: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _73 = is_type(copy _1, baml.time.PlainDate); + branch copy _73 -> [bb25, bb22]; } bb22: { - _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _74 = is_type(copy _1, baml.time.PlainDate); + branch copy _74 -> [bb24, bb23]; } bb23: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + unreachable; } bb24: { - _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb25: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb141]; } bb26: { - _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb27: { - _42 = is_type(copy _1, baml.toml.Table); - branch copy _42 -> [bb51, bb28]; + _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb141]; } bb28: { - _43 = is_type(copy _1, baml.toml.Table); - branch copy _43 -> [bb50, bb29]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb29: { - _44 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _44 -> [bb49, bb30]; + _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb30: { - _45 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _45 -> [bb48, bb31]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb31: { - _46 = is_type(copy _1, baml.time.PlainTime); - branch copy _46 -> [bb47, bb32]; + _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb32: { - _47 = is_type(copy _1, baml.time.PlainTime); - branch copy _47 -> [bb46, bb33]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb33: { - _48 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _48 -> [bb45, bb34]; + _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb34: { - _49 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _49 -> [bb44, bb35]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb35: { - _50 = is_type(copy _1, baml.time.Instant); - branch copy _50 -> [bb43, bb36]; + _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb141]; } bb36: { - _51 = is_type(copy _1, baml.time.Instant); - branch copy _51 -> [bb42, bb37]; + _50 = is_type(copy _1, baml.toml.Table); + branch copy _50 -> [bb60, bb37]; } bb37: { - _52 = is_type(copy _1, baml.time.PlainDate); - branch copy _52 -> [bb41, bb38]; + _51 = is_type(copy _1, baml.toml.Table); + branch copy _51 -> [bb59, bb38]; } bb38: { - _53 = is_type(copy _1, baml.time.PlainDate); - branch copy _53 -> [bb40, bb39]; + _52 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _52 -> [bb58, bb39]; } bb39: { - unreachable; + _53 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _53 -> [bb57, bb40]; } bb40: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _54 = is_type(copy _1, baml.time.PlainTime); + branch copy _54 -> [bb56, bb41]; } bb41: { - _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb129]; + _55 = is_type(copy _1, baml.time.PlainTime); + branch copy _55 -> [bb55, bb42]; } bb42: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _56 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _56 -> [bb54, bb43]; } bb43: { - _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb129]; + _57 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _57 -> [bb53, bb44]; } bb44: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _58 = is_type(copy _1, baml.time.Instant); + branch copy _58 -> [bb52, bb45]; } bb45: { - _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _59 = is_type(copy _1, baml.time.Instant); + branch copy _59 -> [bb51, bb46]; } bb46: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _60 = is_type(copy _1, baml.time.PlainDate); + branch copy _60 -> [bb50, bb47]; } bb47: { - _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _61 = is_type(copy _1, baml.time.PlainDate); + branch copy _61 -> [bb49, bb48]; } bb48: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + unreachable; } bb49: { - _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb50: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb141]; } bb51: { - _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb52: { - _30 = is_type(copy _1, baml.toml.Table); - branch copy _30 -> [bb76, bb53]; + _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb141]; } bb53: { - _31 = is_type(copy _1, baml.toml.Table); - branch copy _31 -> [bb75, bb54]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb54: { - _32 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _32 -> [bb74, bb55]; + _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb55: { - _33 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _33 -> [bb73, bb56]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb56: { - _34 = is_type(copy _1, baml.time.PlainTime); - branch copy _34 -> [bb72, bb57]; + _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb57: { - _35 = is_type(copy _1, baml.time.PlainTime); - branch copy _35 -> [bb71, bb58]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb58: { - _36 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _36 -> [bb70, bb59]; + _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb59: { - _37 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _37 -> [bb69, bb60]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb60: { - _38 = is_type(copy _1, baml.time.Instant); - branch copy _38 -> [bb68, bb61]; + _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb141]; } bb61: { - _39 = is_type(copy _1, baml.time.Instant); - branch copy _39 -> [bb67, bb62]; + _37 = is_type(copy _1, baml.toml.Table); + branch copy _37 -> [bb85, bb62]; } bb62: { - _40 = is_type(copy _1, baml.time.PlainDate); - branch copy _40 -> [bb66, bb63]; + _38 = is_type(copy _1, baml.toml.Table); + branch copy _38 -> [bb84, bb63]; } bb63: { - _41 = is_type(copy _1, baml.time.PlainDate); - branch copy _41 -> [bb65, bb64]; + _39 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _39 -> [bb83, bb64]; } bb64: { - unreachable; + _40 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _40 -> [bb82, bb65]; } bb65: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _41 = is_type(copy _1, baml.time.PlainTime); + branch copy _41 -> [bb81, bb66]; } bb66: { - _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb129]; + _42 = is_type(copy _1, baml.time.PlainTime); + branch copy _42 -> [bb80, bb67]; } bb67: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _43 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _43 -> [bb79, bb68]; } bb68: { - _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb129]; + _44 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _44 -> [bb78, bb69]; } bb69: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _45 = is_type(copy _1, baml.time.Instant); + branch copy _45 -> [bb77, bb70]; } bb70: { - _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _46 = is_type(copy _1, baml.time.Instant); + branch copy _46 -> [bb76, bb71]; } bb71: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _47 = is_type(copy _1, baml.time.PlainDate); + branch copy _47 -> [bb75, bb72]; } bb72: { - _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _48 = is_type(copy _1, baml.time.PlainDate); + branch copy _48 -> [bb74, bb73]; } bb73: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + unreachable; } bb74: { - _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb75: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb141]; } bb76: { - _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb77: { - _18 = is_type(copy _1, baml.toml.Table); - branch copy _18 -> [bb101, bb78]; + _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb141]; } bb78: { - _19 = is_type(copy _1, baml.toml.Table); - branch copy _19 -> [bb100, bb79]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb79: { - _20 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _20 -> [bb99, bb80]; + _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb80: { - _21 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _21 -> [bb98, bb81]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb81: { - _22 = is_type(copy _1, baml.time.PlainTime); - branch copy _22 -> [bb97, bb82]; + _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb82: { - _23 = is_type(copy _1, baml.time.PlainTime); - branch copy _23 -> [bb96, bb83]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb83: { - _24 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _24 -> [bb95, bb84]; + _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb84: { - _25 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _25 -> [bb94, bb85]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb85: { - _26 = is_type(copy _1, baml.time.Instant); - branch copy _26 -> [bb93, bb86]; + _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb141]; } bb86: { - _27 = is_type(copy _1, baml.time.Instant); - branch copy _27 -> [bb92, bb87]; + _24 = is_type(copy _1, baml.toml.Table); + branch copy _24 -> [bb110, bb87]; } bb87: { - _28 = is_type(copy _1, baml.time.PlainDate); - branch copy _28 -> [bb91, bb88]; + _25 = is_type(copy _1, baml.toml.Table); + branch copy _25 -> [bb109, bb88]; } bb88: { - _29 = is_type(copy _1, baml.time.PlainDate); - branch copy _29 -> [bb90, bb89]; + _26 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _26 -> [bb108, bb89]; } bb89: { - unreachable; + _27 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _27 -> [bb107, bb90]; } bb90: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _28 = is_type(copy _1, baml.time.PlainTime); + branch copy _28 -> [bb106, bb91]; } bb91: { - _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb129]; + _29 = is_type(copy _1, baml.time.PlainTime); + branch copy _29 -> [bb105, bb92]; } bb92: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _30 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _30 -> [bb104, bb93]; } bb93: { - _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb129]; + _31 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _31 -> [bb103, bb94]; } bb94: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _32 = is_type(copy _1, baml.time.Instant); + branch copy _32 -> [bb102, bb95]; } bb95: { - _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _33 = is_type(copy _1, baml.time.Instant); + branch copy _33 -> [bb101, bb96]; } bb96: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _34 = is_type(copy _1, baml.time.PlainDate); + branch copy _34 -> [bb100, bb97]; } bb97: { - _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _35 = is_type(copy _1, baml.time.PlainDate); + branch copy _35 -> [bb99, bb98]; } bb98: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + unreachable; } bb99: { - _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb100: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb141]; } bb101: { - _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb102: { - _15 = load_type(baml.json.json); - _16 = load_type(baml.json.JsonSerializationError); - _17 = load_type(baml.toml.Item); - _0 = call const fn baml.Array.map(copy _1, const fn baml.toml.item_to_json) -> [bb129]; + _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb141]; } bb103: { - _3 = is_type(copy _1, baml.toml.Table); - branch copy _3 -> [bb127, bb104]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb104: { - _4 = is_type(copy _1, baml.toml.Table); - branch copy _4 -> [bb126, bb105]; + _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb105: { - _5 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _5 -> [bb125, bb106]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb106: { - _6 = is_type(copy _1, baml.time.PlainDateTime); - branch copy _6 -> [bb124, bb107]; + _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb107: { - _7 = is_type(copy _1, baml.time.PlainTime); - branch copy _7 -> [bb123, bb108]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb108: { - _8 = is_type(copy _1, baml.time.PlainTime); - branch copy _8 -> [bb122, bb109]; + _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; } bb109: { - _9 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _9 -> [bb121, bb110]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb110: { - _10 = is_type(copy _1, baml.time.ZonedDateTime); - branch copy _10 -> [bb120, bb111]; + _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb141]; } bb111: { - _11 = is_type(copy _1, baml.time.Instant); - branch copy _11 -> [bb119, bb112]; + _20 = load_type(baml.json.json); + _21 = load_type(baml.json.JsonSerializationError); + _22 = load_type(baml.toml.Item); + _0 = call const fn baml.Array.map(copy _1, const fn baml.toml.item_to_json) -> [bb141]; } bb112: { - _12 = is_type(copy _1, baml.time.Instant); - branch copy _12 -> [bb118, bb113]; + _7 = is_type(copy _1, baml.toml.Table); + branch copy _7 -> [bb136, bb113]; } bb113: { - _13 = is_type(copy _1, baml.time.PlainDate); - branch copy _13 -> [bb117, bb114]; + _8 = is_type(copy _1, baml.toml.Table); + branch copy _8 -> [bb135, bb114]; } bb114: { - _14 = is_type(copy _1, baml.time.PlainDate); - branch copy _14 -> [bb116, bb115]; + _9 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _9 -> [bb134, bb115]; } bb115: { - unreachable; + _10 = is_type(copy _1, baml.time.PlainDateTime); + branch copy _10 -> [bb133, bb116]; } bb116: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _11 = is_type(copy _1, baml.time.PlainTime); + branch copy _11 -> [bb132, bb117]; } bb117: { - _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb129]; + _12 = is_type(copy _1, baml.time.PlainTime); + branch copy _12 -> [bb131, bb118]; } bb118: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _13 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _13 -> [bb130, bb119]; } bb119: { - _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb129]; + _14 = is_type(copy _1, baml.time.ZonedDateTime); + branch copy _14 -> [bb129, bb120]; } bb120: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _15 = is_type(copy _1, baml.time.Instant); + branch copy _15 -> [bb128, bb121]; } bb121: { - _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _16 = is_type(copy _1, baml.time.Instant); + branch copy _16 -> [bb127, bb122]; } bb122: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _17 = is_type(copy _1, baml.time.PlainDate); + branch copy _17 -> [bb126, bb123]; } bb123: { - _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _18 = is_type(copy _1, baml.time.PlainDate); + branch copy _18 -> [bb125, bb124]; } bb124: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + unreachable; } bb125: { - _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb126: { - _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.time.PlainDate.baml.ToJson.to_json(copy _1) -> [bb141]; } bb127: { - _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb129]; + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; } bb128: { - _0 = copy _1; - goto -> bb129; + _0 = call const fn baml.time.Instant.baml.ToJson.to_json(copy _1) -> [bb141]; } bb129: { + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb130: { + _0 = call const fn baml.time.ZonedDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb131: { + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb132: { + _0 = call const fn baml.time.PlainTime.baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb133: { + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb134: { + _0 = call const fn baml.time.PlainDateTime.baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb135: { + _0 = call const fn baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb136: { + _0 = call const fn baml.toml.Table.baml.ToJson.to_json(copy _1) -> [bb141]; + } + + bb137: { + _0 = copy _1; + goto -> bb141; + } + + bb138: { + _0 = copy _1; + goto -> bb141; + } + + bb139: { + _0 = copy _1; + goto -> bb141; + } + + bb140: { + _0 = copy _1; + goto -> bb141; + } + + bb141: { return; } } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap b/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap index 3bee4876b47..2a91008d3f1 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap @@ -656,7 +656,7 @@ function baml.Array.sort_by_key(self: baml.Array, key: (T) -> U throws E) -> L26: load_type int - load_type U.CompareError + load_type #1.CompareError load_var order load_type #0 load_type #1 @@ -1393,7 +1393,7 @@ function baml.Sortable$for$T[].sort(self: T[]) -> T[] { L0: load_type #0 - load_type T.CompareError + load_type #0.CompareError load_var self load_type #0 make_closure ., captures=0, ntypeargs=1 @@ -2691,7 +2691,7 @@ function baml.csv.decode(source: string | uint8array, options: baml.csv.ReaderOp load_type #0 load_var _6 call baml.csv.CsvReader.rows - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "collect" virtual_call nargs=1 ntypeargs=0 jump L3 @@ -3074,7 +3074,7 @@ function baml.csv.read(path: string, options: baml.csv.ReaderOptions | null) -> load_type #0 load_var r call baml.csv.CsvReader.rows - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "collect" virtual_call nargs=1 ntypeargs=0 store_var result @@ -3151,7 +3151,7 @@ function baml.csv.stringify_records(records: (null | bool | int | bigint | float load_var options call baml.csv.buffer store_var w - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_var records call baml.iter.Iterable$for$T[].iter store_var _6 @@ -3214,7 +3214,7 @@ function baml.csv.stringify_records(records: (null | bool | int | bigint | float load_var _6 is_type baml.iter.Peekable<...> pop_jump_if_false L26 - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_type never load_var _6 call baml.iter.Peekable.Iterator.next @@ -3236,7 +3236,7 @@ function baml.csv.stringify_records(records: (null | bool | int | bigint | float jump L20 L13: - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_type never load_type never load_var _6 @@ -3252,14 +3252,14 @@ function baml.csv.stringify_records(records: (null | bool | int | bigint | float jump L20 L15: - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_var _6 call baml.iter.Repeat.Iterator.next store_var _8 jump L20 L16: - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_type never load_type never load_var _6 @@ -3268,7 +3268,7 @@ function baml.csv.stringify_records(records: (null | bool | int | bigint | float jump L20 L17: - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_type never load_var _6 call baml.iter.StepBy.Iterator.next @@ -3276,7 +3276,7 @@ function baml.csv.stringify_records(records: (null | bool | int | bigint | float jump L20 L18: - load_type null | bool | int | bigint | float | string[] + load_type (null | bool | int | bigint | float | string)[] load_var _6 call baml.iter.ArrayIterator.Iterator.next store_var _8 @@ -3955,7 +3955,7 @@ function baml.iter.Chain$stream.Iterator.next(self: baml.iter.Chain$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _4 @@ -3977,7 +3977,7 @@ function baml.iter.Chain$stream.Iterator.next(self: baml.iter.Chain$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -3999,7 +3999,7 @@ function baml.iter.Chain.Iterator.next(self: baml.iter.Chain) -> T | b pop_jump_if_false L2 load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _4 @@ -4021,7 +4021,7 @@ function baml.iter.Chain.Iterator.next(self: baml.iter.Chain) -> T | b L2: load_var self load_field .1 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -4049,7 +4049,7 @@ function baml.iter.Filter$stream.Iterator.next(self: baml.iter.Filter$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -4093,7 +4093,7 @@ function baml.iter.Filter.Iterator.next(self: baml.iter.Filter) -> T | L2: load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -4137,7 +4137,7 @@ function baml.iter.FilterMap$stream.Iterator.next(self: baml.iter.FilterMap$stre L2: load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -4186,7 +4186,7 @@ function baml.iter.FilterMap.Iterator.next(self: baml.iter.FilterMap + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -4469,13 +4469,16 @@ function baml.iter.FlatMap$stream.Iterator.next(self: baml.iter.FlatMap$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _46 - load_var _46 + store_var _47 + load_var _47 is_type baml.iter.Done pop_jump_if_false L42 jump L72 @@ -4484,115 +4487,115 @@ function baml.iter.FlatMap$stream.Iterator.next(self: baml.iter.FlatMap$stream pop_jump_if_false L43 jump L70 L43: - load_var _51 + load_var _52 is_type baml.iter.Range pop_jump_if_false L44 jump L69 L44: - load_var _51 + load_var _52 is_type baml.iter.ArrayIterator<...> pop_jump_if_false L45 jump L68 L45: - load_var _51 + load_var _52 is_type baml.iter.StepBy<...> pop_jump_if_false L46 jump L67 L46: - load_var _51 + load_var _52 is_type baml.iter.Chain<...> pop_jump_if_false L47 jump L66 L47: - load_var _51 + load_var _52 is_type baml.iter.Repeat<...> pop_jump_if_false L48 jump L65 L48: - load_var _51 + load_var _52 is_type baml.iter.Map<...> pop_jump_if_false L49 jump L64 L49: - load_var _51 + load_var _52 is_type baml.iter.Filter<...> pop_jump_if_false L50 jump L63 L50: - load_var _51 + load_var _52 is_type baml.iter.FilterMap<...> pop_jump_if_false L51 jump L62 L51: - load_var _51 + load_var _52 is_type baml.iter.FlatMap<...> pop_jump_if_false L52 jump L61 L52: - load_var _51 + load_var _52 is_type baml.iter.Peekable<...> pop_jump_if_false L53 jump L60 L53: - load_var _51 + load_var _52 is_type baml.csv.CsvRows<...> pop_jump_if_false L54 jump L59 L54: - load_var _51 + load_var _52 is_type baml.csv.CsvReader pop_jump_if_false L55 jump L58 L55: - load_var _51 - is_type T[] + load_var _52 + is_type _[] pop_jump_if_false L56 jump L57 L56: - load_var _51 + load_var _52 is_type string pop_jump_if_false L77 - load_var _51 + load_var _52 call baml.iter.Iterable$for$string.iter store_var it jump L71 L57: load_type #1 - load_var _51 + load_var _52 call baml.iter.Iterable$for$T[].iter store_var it jump L71 L58: - load_var _51 + load_var _52 call baml.csv.CsvReader.root.iter.Iterable.iter store_var it jump L71 L59: load_type #1 - load_var _51 + load_var _52 call baml.csv.CsvRows.root.iter.Iterable.iter store_var it jump L71 @@ -4600,34 +4603,34 @@ function baml.iter.FlatMap$stream.Iterator.next(self: baml.iter.FlatMap$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _43 @@ -4962,13 +4965,16 @@ function baml.iter.FlatMap.Iterator.next(self: baml.iter.FlatMap + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _46 - load_var _46 + store_var _47 + load_var _47 is_type baml.iter.Done pop_jump_if_false L42 jump L72 @@ -4977,115 +4983,115 @@ function baml.iter.FlatMap.Iterator.next(self: baml.iter.FlatMap pop_jump_if_false L43 jump L70 L43: - load_var _51 + load_var _52 is_type baml.iter.Range pop_jump_if_false L44 jump L69 L44: - load_var _51 + load_var _52 is_type baml.iter.ArrayIterator<...> pop_jump_if_false L45 jump L68 L45: - load_var _51 + load_var _52 is_type baml.iter.StepBy<...> pop_jump_if_false L46 jump L67 L46: - load_var _51 + load_var _52 is_type baml.iter.Chain<...> pop_jump_if_false L47 jump L66 L47: - load_var _51 + load_var _52 is_type baml.iter.Repeat<...> pop_jump_if_false L48 jump L65 L48: - load_var _51 + load_var _52 is_type baml.iter.Map<...> pop_jump_if_false L49 jump L64 L49: - load_var _51 + load_var _52 is_type baml.iter.Filter<...> pop_jump_if_false L50 jump L63 L50: - load_var _51 + load_var _52 is_type baml.iter.FilterMap<...> pop_jump_if_false L51 jump L62 L51: - load_var _51 + load_var _52 is_type baml.iter.FlatMap<...> pop_jump_if_false L52 jump L61 L52: - load_var _51 + load_var _52 is_type baml.iter.Peekable<...> pop_jump_if_false L53 jump L60 L53: - load_var _51 + load_var _52 is_type baml.csv.CsvRows<...> pop_jump_if_false L54 jump L59 L54: - load_var _51 + load_var _52 is_type baml.csv.CsvReader pop_jump_if_false L55 jump L58 L55: - load_var _51 - is_type T[] + load_var _52 + is_type _[] pop_jump_if_false L56 jump L57 L56: - load_var _51 + load_var _52 is_type string pop_jump_if_false L77 - load_var _51 + load_var _52 call baml.iter.Iterable$for$string.iter store_var it jump L71 L57: load_type #1 - load_var _51 + load_var _52 call baml.iter.Iterable$for$T[].iter store_var it jump L71 L58: - load_var _51 + load_var _52 call baml.csv.CsvReader.root.iter.Iterable.iter store_var it jump L71 L59: load_type #1 - load_var _51 + load_var _52 call baml.csv.CsvRows.root.iter.Iterable.iter store_var it jump L71 @@ -5093,34 +5099,34 @@ function baml.iter.FlatMap.Iterator.next(self: baml.iter.FlatMap + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _43 @@ -5455,19 +5461,22 @@ function baml.iter.Flatten$stream.Iterator.next(self: baml.iter.Flatten$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _46 - load_var _46 + store_var _47 + load_var _47 is_type baml.iter.Done pop_jump_if_false L42 jump L72 L42: - load_var _46 + load_var _47 store_var_load_var x is_type baml.iter.Flatten<...> pop_jump_if_false L43 @@ -5547,7 +5556,7 @@ function baml.iter.Flatten$stream.Iterator.next(self: baml.iter.Flatten$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _43 @@ -5945,19 +5954,22 @@ function baml.iter.Flatten.Iterator.next(self: baml.iter.Flatten) - jump L73 L41: + load_var _2 + is_type baml.iter.Done + pop_jump_if_false L77 load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 - store_var _46 - load_var _46 + store_var _47 + load_var _47 is_type baml.iter.Done pop_jump_if_false L42 jump L72 L42: - load_var _46 + load_var _47 store_var_load_var x is_type baml.iter.Flatten<...> pop_jump_if_false L43 @@ -6037,7 +6049,7 @@ function baml.iter.Flatten.Iterator.next(self: baml.iter.Flatten) - L55: load_var x - is_type T[] + is_type _[] pop_jump_if_false L56 jump L57 @@ -6158,7 +6170,7 @@ function baml.iter.Flatten.Iterator.next(self: baml.iter.Flatten) - L73: load_var _2 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _43 @@ -6280,7 +6292,7 @@ function baml.iter.Iterator.chain(self: baml.iter.Iterator, other: baml.iter.Ite L12: load_var other - is_type T[] + is_type _[] pop_jump_if_false L13 jump L14 @@ -6412,7 +6424,7 @@ function baml.iter.Iterator.collect(self: baml.iter.Iterator) -> Self.Item[] { load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _4 @@ -6440,7 +6452,7 @@ function baml.iter.Iterator.count(self: baml.iter.Iterator) -> int { load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -6466,7 +6478,7 @@ function baml.iter.Iterator.every(self: baml.iter.Iterator, predicate: (Self.Ite load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -6514,7 +6526,7 @@ function baml.iter.Iterator.find(self: baml.iter.Iterator, predicate: (Self.Item load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -6556,7 +6568,7 @@ function baml.iter.Iterator.for_each(self: baml.iter.Iterator, fn: (Self.Item) - load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -6604,7 +6616,7 @@ function baml.iter.Iterator.reduce(self: baml.iter.Iterator, fn: (A, Self.Item) load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _5 @@ -6630,7 +6642,7 @@ function baml.iter.Iterator.some(self: baml.iter.Iterator, predicate: (Self.Item load_const true pop_jump_if_false L2 load_var self - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _3 @@ -6670,7 +6682,7 @@ function baml.iter.Map$stream.Iterable.iter(self: baml.iter.Map$stream) -> R | baml.iter.Done { load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -6700,7 +6712,7 @@ function baml.iter.Map.Iterable.iter(self: baml.iter.Map) -> baml.i function baml.iter.Map.Iterator.next(self: baml.iter.Map) -> R | baml.iter.Done { load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _2 @@ -6736,7 +6748,7 @@ function baml.iter.Peekable$stream.Iterator.next(self: baml.iter.Peekable$stream L0: load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -6774,7 +6786,7 @@ function baml.iter.Peekable.Iterator.next(self: baml.iter.Peekable) -> T | L0: load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -6807,7 +6819,7 @@ function baml.iter.Peekable.peek(self: baml.iter.Peekable) -> T | baml.ite L0: load_var self load_field .source - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var v @@ -7042,7 +7054,7 @@ function baml.iter.StepBy$stream.Iterator.next(self: baml.iter.StepBy$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -7051,7 +7063,7 @@ function baml.iter.StepBy$stream.Iterator.next(self: baml.iter.StepBy$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _9 @@ -7078,7 +7090,7 @@ function baml.iter.StepBy$stream.Iterator.next(self: baml.iter.StepBy$stream + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -7115,7 +7127,7 @@ function baml.iter.StepBy.Iterator.next(self: baml.iter.StepBy) -> T | bam L2: load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -7124,7 +7136,7 @@ function baml.iter.StepBy.Iterator.next(self: baml.iter.StepBy) -> T | bam L3: load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _9 @@ -7151,7 +7163,7 @@ function baml.iter.StepBy.Iterator.next(self: baml.iter.StepBy) -> T | bam store_field .2 load_var self load_field .0 - load_type baml.iter.Iterator + load_type baml.iter.Iterator load_const "next" virtual_call nargs=1 ntypeargs=0 store_var _0 @@ -9769,13 +9781,13 @@ function baml.ops.Equals.neq(self: baml.ops.Equals, other: Self) -> bool { L6: load_var self - is_type T[] + is_type _[] pop_jump_if_false L7 jump L8 L7: load_var self - is_type map + is_type map pop_jump_if_false L17 load_type unknown load_var2 1 2 @@ -12507,706 +12519,816 @@ function baml.toml.Table.parse(s: string) -> baml.toml.Table { function baml.toml.item_from_json(json: baml.json.json) -> baml.toml.Item { load_var json - type_tag - jump_table [L3, L3, L3, L2, L3, _, L0, L1], default L5 + is_type bool + pop_jump_if_false L0 + jump L11 + + L0: + load_var json + is_type int + pop_jump_if_false L1 + jump L10 - L0: list + L1: + load_var json + is_type float + pop_jump_if_false L2 + jump L9 + + L2: + load_var json + is_type string + pop_jump_if_false L3 + jump L8 + + L3: + load_var json + load_const null + cmp_op == + pop_jump_if_false L4 + jump L7 + + L4: + load_var json + is_type map + pop_jump_if_false L5 + jump L6 + + L5: + load_var json + is_type baml.json.json[] + pop_jump_if_false L13 load_type baml.json.json load_type baml.toml.Item load_type baml.json.JsonDecodeError load_var json load_global baml.toml.item_from_json call baml.Array.map - jump L4 + jump L12 - L1: map + L6: load_var json call baml.toml.Table.baml.FromJson.from_json - jump L4 + jump L12 - L2: null + L7: load_const "TOML does not support null" load_const "" init_instance baml.json.JsonDecodeError .message, .path throw - L3: float + L8: load_var json + jump L12 - L4: + L9: + load_var json + jump L12 + + L10: + load_var json + jump L12 + + L11: + load_var json + + L12: return - L5: + L13: unreachable } function baml.toml.item_to_json(item: baml.toml.Item) -> baml.json.json { load_var item - type_tag - dense_tag [bool, int, float, string, Table, list, ZonedDateTime, PlainDateTime, PlainDate, PlainTime] - jump_table [L116, L116, L116, L116, L93, L92, L69, L46, L23, L0], default L118 + is_type bool + pop_jump_if_false L0 + jump L127 - L0: PlainTime + L0: load_var item - is_type baml.toml.Table + is_type int pop_jump_if_false L1 - jump L22 + jump L126 L1: load_var item - is_type baml.toml.Table + is_type float pop_jump_if_false L2 - jump L21 + jump L125 L2: load_var item - is_type baml.time.PlainDateTime + is_type string pop_jump_if_false L3 - jump L20 + jump L124 L3: load_var item - is_type baml.time.PlainDateTime + is_type baml.toml.Table pop_jump_if_false L4 - jump L19 + jump L101 L4: load_var item - is_type baml.time.PlainTime + is_type baml.toml.Item[] pop_jump_if_false L5 - jump L18 + jump L100 L5: load_var item - is_type baml.time.PlainTime + is_type baml.time.ZonedDateTime pop_jump_if_false L6 - jump L17 + jump L77 L6: load_var item - is_type baml.time.ZonedDateTime + is_type baml.time.PlainDateTime pop_jump_if_false L7 - jump L16 + jump L54 L7: load_var item - is_type baml.time.ZonedDateTime + is_type baml.time.PlainDate pop_jump_if_false L8 - jump L15 + jump L31 L8: load_var item - is_type baml.time.Instant + is_type baml.time.PlainTime + pop_jump_if_false L129 + load_var item + is_type baml.toml.Table pop_jump_if_false L9 - jump L14 + jump L30 L9: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L10 - jump L13 + jump L29 L10: load_var item - is_type baml.time.PlainDate + is_type baml.time.PlainDateTime pop_jump_if_false L11 - jump L12 + jump L28 L11: load_var item - is_type baml.time.PlainDate - pop_jump_if_false L118 - load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDateTime + pop_jump_if_false L12 + jump L27 L12: load_var item - call baml.time.PlainDate.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L13 + jump L26 L13: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L14 + jump L25 L14: load_var item - call baml.time.Instant.baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L15 + jump L24 L15: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L16 + jump L23 L16: load_var item - call baml.time.ZonedDateTime.baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L17 + jump L22 L17: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L18 + jump L21 L18: load_var item - call baml.time.PlainTime.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDate + pop_jump_if_false L19 + jump L20 L19: + load_var item + is_type baml.time.PlainDate + pop_jump_if_false L129 load_var item call baml.ToJson.to_json - jump L117 + jump L128 L20: load_var item - call baml.time.PlainDateTime.baml.ToJson.to_json - jump L117 + call baml.time.PlainDate.baml.ToJson.to_json + jump L128 L21: load_var item call baml.ToJson.to_json - jump L117 + jump L128 L22: load_var item - call baml.toml.Table.baml.ToJson.to_json - jump L117 + call baml.time.Instant.baml.ToJson.to_json + jump L128 - L23: PlainDate + L23: load_var item - is_type baml.toml.Table - pop_jump_if_false L24 - jump L45 + call baml.ToJson.to_json + jump L128 L24: load_var item - is_type baml.toml.Table - pop_jump_if_false L25 - jump L44 + call baml.time.ZonedDateTime.baml.ToJson.to_json + jump L128 L25: load_var item - is_type baml.time.PlainDateTime - pop_jump_if_false L26 - jump L43 + call baml.ToJson.to_json + jump L128 L26: load_var item - is_type baml.time.PlainDateTime - pop_jump_if_false L27 - jump L42 + call baml.time.PlainTime.baml.ToJson.to_json + jump L128 L27: load_var item - is_type baml.time.PlainTime - pop_jump_if_false L28 - jump L41 + call baml.ToJson.to_json + jump L128 L28: load_var item - is_type baml.time.PlainTime - pop_jump_if_false L29 - jump L40 + call baml.time.PlainDateTime.baml.ToJson.to_json + jump L128 L29: load_var item - is_type baml.time.ZonedDateTime - pop_jump_if_false L30 - jump L39 + call baml.ToJson.to_json + jump L128 L30: load_var item - is_type baml.time.ZonedDateTime - pop_jump_if_false L31 - jump L38 + call baml.toml.Table.baml.ToJson.to_json + jump L128 L31: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L32 - jump L37 + jump L53 L32: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L33 - jump L36 + jump L52 L33: load_var item - is_type baml.time.PlainDate + is_type baml.time.PlainDateTime pop_jump_if_false L34 - jump L35 + jump L51 L34: load_var item - is_type baml.time.PlainDate - pop_jump_if_false L118 - load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDateTime + pop_jump_if_false L35 + jump L50 L35: load_var item - call baml.time.PlainDate.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L36 + jump L49 L36: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L37 + jump L48 L37: load_var item - call baml.time.Instant.baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L38 + jump L47 L38: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L39 + jump L46 L39: load_var item - call baml.time.ZonedDateTime.baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L40 + jump L45 L40: load_var item - call baml.ToJson.to_json - jump L117 - - L41: + is_type baml.time.Instant + pop_jump_if_false L41 + jump L44 + + L41: load_var item - call baml.time.PlainTime.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDate + pop_jump_if_false L42 + jump L43 L42: + load_var item + is_type baml.time.PlainDate + pop_jump_if_false L129 load_var item call baml.ToJson.to_json - jump L117 + jump L128 L43: load_var item - call baml.time.PlainDateTime.baml.ToJson.to_json - jump L117 + call baml.time.PlainDate.baml.ToJson.to_json + jump L128 L44: load_var item call baml.ToJson.to_json - jump L117 + jump L128 L45: load_var item - call baml.toml.Table.baml.ToJson.to_json - jump L117 + call baml.time.Instant.baml.ToJson.to_json + jump L128 - L46: PlainDateTime + L46: load_var item - is_type baml.toml.Table - pop_jump_if_false L47 - jump L68 + call baml.ToJson.to_json + jump L128 L47: load_var item - is_type baml.toml.Table - pop_jump_if_false L48 - jump L67 + call baml.time.ZonedDateTime.baml.ToJson.to_json + jump L128 L48: load_var item - is_type baml.time.PlainDateTime - pop_jump_if_false L49 - jump L66 + call baml.ToJson.to_json + jump L128 L49: load_var item - is_type baml.time.PlainDateTime - pop_jump_if_false L50 - jump L65 + call baml.time.PlainTime.baml.ToJson.to_json + jump L128 L50: load_var item - is_type baml.time.PlainTime - pop_jump_if_false L51 - jump L64 + call baml.ToJson.to_json + jump L128 L51: load_var item - is_type baml.time.PlainTime - pop_jump_if_false L52 - jump L63 + call baml.time.PlainDateTime.baml.ToJson.to_json + jump L128 L52: load_var item - is_type baml.time.ZonedDateTime - pop_jump_if_false L53 - jump L62 + call baml.ToJson.to_json + jump L128 L53: load_var item - is_type baml.time.ZonedDateTime - pop_jump_if_false L54 - jump L61 + call baml.toml.Table.baml.ToJson.to_json + jump L128 L54: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L55 - jump L60 + jump L76 L55: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L56 - jump L59 + jump L75 L56: load_var item - is_type baml.time.PlainDate + is_type baml.time.PlainDateTime pop_jump_if_false L57 - jump L58 + jump L74 L57: load_var item - is_type baml.time.PlainDate - pop_jump_if_false L118 - load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDateTime + pop_jump_if_false L58 + jump L73 L58: load_var item - call baml.time.PlainDate.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L59 + jump L72 L59: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L60 + jump L71 L60: load_var item - call baml.time.Instant.baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L61 + jump L70 L61: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L62 + jump L69 L62: load_var item - call baml.time.ZonedDateTime.baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L63 + jump L68 L63: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L64 + jump L67 L64: load_var item - call baml.time.PlainTime.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDate + pop_jump_if_false L65 + jump L66 L65: + load_var item + is_type baml.time.PlainDate + pop_jump_if_false L129 load_var item call baml.ToJson.to_json - jump L117 + jump L128 L66: load_var item - call baml.time.PlainDateTime.baml.ToJson.to_json - jump L117 + call baml.time.PlainDate.baml.ToJson.to_json + jump L128 L67: load_var item call baml.ToJson.to_json - jump L117 + jump L128 L68: load_var item - call baml.toml.Table.baml.ToJson.to_json - jump L117 + call baml.time.Instant.baml.ToJson.to_json + jump L128 - L69: ZonedDateTime + L69: load_var item - is_type baml.toml.Table - pop_jump_if_false L70 - jump L91 + call baml.ToJson.to_json + jump L128 L70: load_var item - is_type baml.toml.Table - pop_jump_if_false L71 - jump L90 + call baml.time.ZonedDateTime.baml.ToJson.to_json + jump L128 L71: load_var item - is_type baml.time.PlainDateTime - pop_jump_if_false L72 - jump L89 + call baml.ToJson.to_json + jump L128 L72: load_var item - is_type baml.time.PlainDateTime - pop_jump_if_false L73 - jump L88 + call baml.time.PlainTime.baml.ToJson.to_json + jump L128 L73: load_var item - is_type baml.time.PlainTime - pop_jump_if_false L74 - jump L87 + call baml.ToJson.to_json + jump L128 L74: load_var item - is_type baml.time.PlainTime - pop_jump_if_false L75 - jump L86 + call baml.time.PlainDateTime.baml.ToJson.to_json + jump L128 L75: load_var item - is_type baml.time.ZonedDateTime - pop_jump_if_false L76 - jump L85 + call baml.ToJson.to_json + jump L128 L76: load_var item - is_type baml.time.ZonedDateTime - pop_jump_if_false L77 - jump L84 + call baml.toml.Table.baml.ToJson.to_json + jump L128 L77: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L78 - jump L83 + jump L99 L78: load_var item - is_type baml.time.Instant + is_type baml.toml.Table pop_jump_if_false L79 - jump L82 + jump L98 L79: load_var item - is_type baml.time.PlainDate + is_type baml.time.PlainDateTime pop_jump_if_false L80 - jump L81 + jump L97 L80: load_var item - is_type baml.time.PlainDate - pop_jump_if_false L118 - load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDateTime + pop_jump_if_false L81 + jump L96 L81: load_var item - call baml.time.PlainDate.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L82 + jump L95 L82: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.PlainTime + pop_jump_if_false L83 + jump L94 L83: load_var item - call baml.time.Instant.baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L84 + jump L93 L84: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.ZonedDateTime + pop_jump_if_false L85 + jump L92 L85: load_var item - call baml.time.ZonedDateTime.baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L86 + jump L91 L86: load_var item - call baml.ToJson.to_json - jump L117 + is_type baml.time.Instant + pop_jump_if_false L87 + jump L90 L87: load_var item - call baml.time.PlainTime.baml.ToJson.to_json - jump L117 + is_type baml.time.PlainDate + pop_jump_if_false L88 + jump L89 L88: + load_var item + is_type baml.time.PlainDate + pop_jump_if_false L129 load_var item call baml.ToJson.to_json - jump L117 + jump L128 L89: load_var item - call baml.time.PlainDateTime.baml.ToJson.to_json - jump L117 + call baml.time.PlainDate.baml.ToJson.to_json + jump L128 L90: load_var item call baml.ToJson.to_json - jump L117 + jump L128 L91: + load_var item + call baml.time.Instant.baml.ToJson.to_json + jump L128 + + L92: + load_var item + call baml.ToJson.to_json + jump L128 + + L93: + load_var item + call baml.time.ZonedDateTime.baml.ToJson.to_json + jump L128 + + L94: + load_var item + call baml.ToJson.to_json + jump L128 + + L95: + load_var item + call baml.time.PlainTime.baml.ToJson.to_json + jump L128 + + L96: + load_var item + call baml.ToJson.to_json + jump L128 + + L97: + load_var item + call baml.time.PlainDateTime.baml.ToJson.to_json + jump L128 + + L98: + load_var item + call baml.ToJson.to_json + jump L128 + + L99: load_var item call baml.toml.Table.baml.ToJson.to_json - jump L117 + jump L128 - L92: list + L100: load_type baml.toml.Item load_type baml.json.json load_type baml.json.JsonSerializationError load_var item load_global baml.toml.item_to_json call baml.Array.map - jump L117 + jump L128 - L93: Table + L101: load_var item is_type baml.toml.Table - pop_jump_if_false L94 - jump L115 + pop_jump_if_false L102 + jump L123 - L94: + L102: load_var item is_type baml.toml.Table - pop_jump_if_false L95 - jump L114 + pop_jump_if_false L103 + jump L122 - L95: + L103: load_var item is_type baml.time.PlainDateTime - pop_jump_if_false L96 - jump L113 + pop_jump_if_false L104 + jump L121 - L96: + L104: load_var item is_type baml.time.PlainDateTime - pop_jump_if_false L97 - jump L112 + pop_jump_if_false L105 + jump L120 - L97: + L105: load_var item is_type baml.time.PlainTime - pop_jump_if_false L98 - jump L111 + pop_jump_if_false L106 + jump L119 - L98: + L106: load_var item is_type baml.time.PlainTime - pop_jump_if_false L99 - jump L110 + pop_jump_if_false L107 + jump L118 - L99: + L107: load_var item is_type baml.time.ZonedDateTime - pop_jump_if_false L100 - jump L109 + pop_jump_if_false L108 + jump L117 - L100: + L108: load_var item is_type baml.time.ZonedDateTime - pop_jump_if_false L101 - jump L108 + pop_jump_if_false L109 + jump L116 - L101: + L109: load_var item is_type baml.time.Instant - pop_jump_if_false L102 - jump L107 + pop_jump_if_false L110 + jump L115 - L102: + L110: load_var item is_type baml.time.Instant - pop_jump_if_false L103 - jump L106 + pop_jump_if_false L111 + jump L114 - L103: + L111: load_var item is_type baml.time.PlainDate - pop_jump_if_false L104 - jump L105 + pop_jump_if_false L112 + jump L113 - L104: + L112: load_var item is_type baml.time.PlainDate - pop_jump_if_false L118 + pop_jump_if_false L129 load_var item call baml.ToJson.to_json - jump L117 + jump L128 - L105: + L113: load_var item call baml.time.PlainDate.baml.ToJson.to_json - jump L117 + jump L128 - L106: + L114: load_var item call baml.ToJson.to_json - jump L117 + jump L128 - L107: + L115: load_var item call baml.time.Instant.baml.ToJson.to_json - jump L117 + jump L128 - L108: + L116: load_var item call baml.ToJson.to_json - jump L117 + jump L128 - L109: + L117: load_var item call baml.time.ZonedDateTime.baml.ToJson.to_json - jump L117 + jump L128 - L110: + L118: load_var item call baml.ToJson.to_json - jump L117 + jump L128 - L111: + L119: load_var item call baml.time.PlainTime.baml.ToJson.to_json - jump L117 + jump L128 - L112: + L120: load_var item call baml.ToJson.to_json - jump L117 + jump L128 - L113: + L121: load_var item call baml.time.PlainDateTime.baml.ToJson.to_json - jump L117 + jump L128 - L114: + L122: load_var item call baml.ToJson.to_json - jump L117 + jump L128 - L115: + L123: load_var item call baml.toml.Table.baml.ToJson.to_json - jump L117 + jump L128 - L116: string + L124: load_var item + jump L128 - L117: + L125: + load_var item + jump L128 + + L126: + load_var item + jump L128 + + L127: + load_var item + + L128: return - L118: + L129: unreachable } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__04_5_mir.snap b/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__04_5_mir.snap index ad469d7af9a..1fdcb60ca06 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__04_5_mir.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__04_5_mir.snap @@ -20,53 +20,89 @@ fn user.MatchJson(j: baml.json.json) -> string { // Locals: let _0: string // _0 // return let _1: baml.json.json // j // param - let _2: int + let _2: bool + let _3: bool + let _4: bool + let _5: bool + let _6: bool + let _7: bool + let _8: bool bb0: { - _2 = type_tag(_1); - switch copy _2 [null: bb8, bool: bb7, int: bb6, float: bb5, string: bb4, list: bb3, map: bb2, otherwise: bb1] (exhaustive); + _2 = copy _1 == const null; + branch copy _2 -> [bb14, bb1]; } bb1: { - unreachable; + _3 = is_type(copy _1, bool); + branch copy _3 -> [bb13, bb2]; } bb2: { - _0 = const "map"; - goto -> bb9; + _4 = is_type(copy _1, int); + branch copy _4 -> [bb12, bb3]; } bb3: { - _0 = const "array"; - goto -> bb9; + _5 = is_type(copy _1, float); + branch copy _5 -> [bb11, bb4]; } bb4: { - _0 = const "string"; - goto -> bb9; + _6 = is_type(copy _1, string); + branch copy _6 -> [bb10, bb5]; } bb5: { - _0 = const "float"; - goto -> bb9; + _7 = is_type(copy _1, baml.json.json[]); + branch copy _7 -> [bb9, bb6]; } bb6: { - _0 = const "int"; - goto -> bb9; + _8 = is_type(copy _1, map); + branch copy _8 -> [bb8, bb7]; } bb7: { - _0 = const "bool"; - goto -> bb9; + unreachable; } bb8: { - _0 = const "null"; - goto -> bb9; + _0 = const "map"; + goto -> bb15; } bb9: { + _0 = const "array"; + goto -> bb15; + } + + bb10: { + _0 = const "string"; + goto -> bb15; + } + + bb11: { + _0 = const "float"; + goto -> bb15; + } + + bb12: { + _0 = const "int"; + goto -> bb15; + } + + bb13: { + _0 = const "bool"; + goto -> bb15; + } + + bb14: { + _0 = const "null"; + goto -> bb15; + } + + bb15: { return; } } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__06_codegen.snap b/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__06_codegen.snap index 051dca903d1..8995c6874d6 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__06_codegen.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/json_alias_basic/baml_tests__compiles__json_alias_basic__06_codegen.snap @@ -18,40 +18,75 @@ function user.MakeWrapper() -> Wrapper { function user.MatchJson(j: baml.json.json) -> string { load_var j - type_tag - jump_table [L4, L2, L5, L6, L3, _, L1, L0], default L8 + load_const null + cmp_op == + pop_jump_if_false L0 + jump L11 - L0: map - load_const "map" + L0: + load_var j + is_type bool + pop_jump_if_false L1 + jump L10 + + L1: + load_var j + is_type int + pop_jump_if_false L2 + jump L9 + + L2: + load_var j + is_type float + pop_jump_if_false L3 + jump L8 + + L3: + load_var j + is_type string + pop_jump_if_false L4 jump L7 - L1: list + L4: + load_var j + is_type baml.json.json[] + pop_jump_if_false L5 + jump L6 + + L5: + load_var j + is_type map + pop_jump_if_false L13 + load_const "map" + jump L12 + + L6: load_const "array" - jump L7 + jump L12 - L2: string + L7: load_const "string" - jump L7 + jump L12 - L3: float + L8: load_const "float" - jump L7 + jump L12 - L4: int + L9: load_const "int" - jump L7 + jump L12 - L5: bool + L10: load_const "bool" - jump L7 + jump L12 - L6: null + L11: load_const "null" - L7: + L12: return - L8: + L13: unreachable } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__04_5_mir.snap b/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__04_5_mir.snap index 7c281660c82..d3bc94f27af 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__04_5_mir.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__04_5_mir.snap @@ -251,7 +251,7 @@ fn user.GenerateMixedImageNarrative(prompt: string, client: baml.llm.Client) -> bb2: { _4 = { const "prompt": copy _1 }: map; - _5 = load_type(image | string[]); + _5 = load_type((image | string)[]); _0 = call const fn baml.llm.call_llm_function(copy _2, const "GenerateMixedImageNarrative", copy _4, const ) -> [bb3]; } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__06_codegen.snap b/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__06_codegen.snap index 718f30c8d9d..f553316c6e1 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__06_codegen.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/llm_image_outputs/baml_tests__compiles__llm_image_outputs__06_codegen.snap @@ -306,7 +306,7 @@ function user.GenerateMixedImageNarrative(prompt: string, client: baml.llm.Clien store_var client L0: - load_type image | string[] + load_type (image | string)[] load_var client load_const "GenerateMixedImageNarrative" load_var prompt @@ -370,8 +370,8 @@ function user.GenerateMixedImageNarrative$parse(json: string, client: baml.llm.C store_var client L0: - load_type image | string[] - load_type image | string[] + load_type (image | string)[] + load_type (image | string)[] load_var json call baml.llm.parse return @@ -386,8 +386,8 @@ function user.GenerateMixedImageNarrative$parse_stream(sse: baml.http.SseStream, store_var client L0: - load_type image | string[] - load_type image | string[] + load_type (image | string)[] + load_type (image | string)[] load_var2 2 1 call baml.llm.Client.__make_stream return @@ -423,8 +423,8 @@ function user.GenerateMixedImageNarrative$stream(prompt: string, client: baml.ll store_var client L0: - load_type image | string[] - load_type image | string[] + load_type (image | string)[] + load_type (image | string)[] load_var client load_const "GenerateMixedImageNarrative" load_var prompt diff --git a/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snap b/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snap index 0d83f7290bc..1f54de2dd7f 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__04_5_mir.snap @@ -1227,27 +1227,37 @@ fn user.unwrap_typed(cb: ((int) -> T throws never) | ((string) -> T throws never let _2: bool let _3: (int) -> T throws never // f let _4: (int) -> T throws never - let _5: (string) -> T throws never // f - let _6: (string) -> T throws never + let _5: bool + let _6: (string) -> T throws never // f + let _7: (string) -> T throws never bb0: { - _2 = is_type(copy _1, (int) -> T throws never); - branch copy _2 -> [bb2, bb1]; + _2 = is_type(copy _1, (int) -> #0 throws never); + branch copy _2 -> [bb4, bb1]; } bb1: { - _5 = copy _1; - _6 = copy _5; - _0 = call copy _6(const "") -> [bb3]; + _5 = is_type(copy _1, (string) -> #0 throws never); + branch copy _5 -> [bb3, bb2]; } bb2: { + unreachable; + } + + bb3: { + _6 = copy _1; + _7 = copy _6; + _0 = call copy _7(const "") -> [bb5]; + } + + bb4: { _3 = copy _1; _4 = copy _3; - _0 = call copy _4(const 0_i64) -> [bb3]; + _0 = call copy _4(const 0_i64) -> [bb5]; } - bb3: { + bb5: { return; } } diff --git a/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap b/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap index 2767ee34e4b..50da39ef45f 100644 --- a/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap +++ b/baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap @@ -881,11 +881,14 @@ function user.unwrap_curried(f: ((int) -> ((int) -> ((int) -> int throws never) function user.unwrap_typed(cb: ((int) -> T throws never) | ((string) -> T throws never)) -> T { load_var cb - is_type (int) -> T throws never + is_type (int) -> #0 throws never pop_jump_if_false L0 jump L1 L0: + load_var cb + is_type (string) -> #0 throws never + pop_jump_if_false L3 load_const "" load_var cb call_indirect @@ -898,4 +901,7 @@ function user.unwrap_typed(cb: ((int) -> T throws never) | ((string) -> T throws L2: return + + L3: + unreachable } diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__01_lexer__main.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__01_lexer__main.snap new file mode 100644 index 00000000000..c00692298f9 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__01_lexer__main.snap @@ -0,0 +1,444 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +Slash "/" +Slash "/" +Backtick "`" +Word "Self" +Backtick "`" +Is "is" +Word "a" +Word "signature-only" +Word "construct" +Dot "." +Word "In" +Word "a" +Word "method" +Star "*" +Word "body" +Star "*" +Word "type" +Word "position" +Word "it" +Word "has" +Slash "/" +Slash "/" +Word "no" +Word "substitution" +LParen "(" +Word "a" +Class "class" +Word "body" +Word "cannot" +Word "name" +Word "its" +Word "own" +Word "instantiation" +Word "as" +Word "a" +Word "value" +Slash "/" +Slash "/" +Word "type" +Comma "," +Word "and" +Word "an" +Interface "interface" +Word "default" +Word "method" +Word "cannot" +Word "name" +Word "the" +Word "implementor" +RParen ")" +Comma "," +Word "so" +Word "every" +Slash "/" +Slash "/" +Word "body-position" +Backtick "`" +Word "Self" +Backtick "`" +Is "is" +Word "a" +Word "compile" +Word "error" +Error "—" +Word "rather" +Word "than" +Word "silently" +Word "resolving" +Word "to" +Word "a" +Slash "/" +Slash "/" +Word "misbehaving" +Backtick "`" +Word "TypeVar" +LParen "(" +Quote "\"" +Word "Self" +Quote "\"" +RParen ")" +Backtick "`" +LParen "(" +Interface "interface" +Word "default" +Word "methods" +RParen ")" +Word "or" +Word "matching" +Word "any" +Slash "/" +Slash "/" +Word "instantiation" +LParen "(" +Word "generic" +Class "class" +Word "bodies" +RParen ")" +Dot "." +Slash "/" +Slash "/" +Slash "/" +Slash "/" +Word "The" +Word "sound" +Word "alternative" +In "in" +Word "a" +Class "class" +Word "method" +Is "is" +Word "to" +Word "name" +Word "the" +Word "enclosing" +Word "type" +Slash "/" +Slash "/" +Word "explicitly" +Comma "," +Word "e" +Dot "." +Word "g" +Dot "." +Backtick "`" +Word "Box" +Less "<" +Word "T" +Greater ">" +Backtick "`" +LParen "(" +Word "exercised" +Word "as" +Word "a" +Word "runtime" +Test "test" +In "in" +Slash "/" +Slash "/" +Backtick "`" +Word "ns_match_self_workaround" +Backtick "`" +RParen ")" +Dot "." +Slash "/" +Slash "/" +Error "─" +Error "─" +Word "Class-body" +Word "method" +Colon ":" +Match "match" +Word "arm" +Comma "," +Backtick "`" +Is "is" +Backtick "`" +Comma "," +Word "and" +Word "turbofish" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Class "class" +Word "Box" +Less "<" +Word "T" +Greater ">" +LBrace "{" +Word "v" +Colon ":" +Word "T" +Comma "," +Function "function" +Word "which" +LParen "(" +Word "self" +Comma "," +Word "other" +Colon ":" +Word "Box" +Less "<" +Word "int" +Greater ">" +Pipe "|" +Word "Box" +Less "<" +Word "string" +Greater ">" +RParen ")" +Arrow "->" +Word "string" +LBrace "{" +Match "match" +LParen "(" +Word "other" +RParen ")" +LBrace "{" +Let "let" +Word "s" +Colon ":" +Word "Self" +FatArrow "=>" +Quote "\"" +Word "self" +Quote "\"" +Comma "," +Word "_" +FatArrow "=>" +Quote "\"" +Word "other" +Quote "\"" +Comma "," +RBrace "}" +RBrace "}" +Function "function" +Word "is_self" +LParen "(" +Word "self" +Comma "," +Word "other" +Colon ":" +Word "Box" +Less "<" +Word "int" +Greater ">" +Pipe "|" +Word "Box" +Less "<" +Word "string" +Greater ">" +RParen ")" +Arrow "->" +Word "bool" +LBrace "{" +Word "other" +Is "is" +Word "Self" +RBrace "}" +Function "function" +Word "let_self" +LParen "(" +Word "self" +Comma "," +Word "other" +Colon ":" +Word "Box" +Less "<" +Word "int" +Greater ">" +RParen ")" +Arrow "->" +Word "int" +LBrace "{" +Let "let" +Word "a" +Colon ":" +Word "Self" +Equals "=" +Word "other" +Semicolon ";" +IntegerLiteral "1" +RBrace "}" +Function "function" +Word "nested_self" +LParen "(" +Word "self" +Comma "," +Word "w" +Colon ":" +Word "Box" +Less "<" +Word "Self" +Greater ">" +RParen ")" +Arrow "->" +Word "int" +LBrace "{" +Match "match" +LParen "(" +Word "w" +RParen ")" +LBrace "{" +Let "let" +Word "s" +Colon ":" +Word "Box" +Less "<" +Word "Self" +Greater ">" +FatArrow "=>" +IntegerLiteral "1" +Comma "," +Word "_" +FatArrow "=>" +IntegerLiteral "0" +Comma "," +RBrace "}" +RBrace "}" +Function "function" +Word "turbofish_self" +LParen "(" +Word "self" +RParen ")" +Arrow "->" +Word "string" +LBrace "{" +Word "reflect" +Dot "." +Word "type_of" +Less "<" +Word "Self" +Greater ">" +LParen "(" +RParen ")" +Dot "." +Word "to_string" +LParen "(" +RParen ")" +RBrace "}" +RBrace "}" +Slash "/" +Slash "/" +Error "─" +Error "─" +Word "Interface" +Word "default-method" +Word "body" +LParen "(" +Word "the" +Word "previously-silent" +Word "hole" +RParen ")" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Error "─" +Interface "interface" +Word "Tester" +LBrace "{" +Function "function" +Word "eq" +LParen "(" +Word "self" +Comma "," +Word "other" +Colon ":" +Word "Self" +RParen ")" +Arrow "->" +Word "bool" +Function "function" +Word "check" +LParen "(" +Word "self" +Comma "," +Word "other" +Colon ":" +Word "Self" +RParen ")" +Arrow "->" +Word "bool" +LBrace "{" +Word "other" +Is "is" +Word "Self" +RBrace "}" +Function "function" +Word "describe" +LParen "(" +Word "self" +RParen ")" +Arrow "->" +Word "string" +LBrace "{" +Word "reflect" +Dot "." +Word "type_of" +Less "<" +Word "Self" +Greater ">" +LParen "(" +RParen ")" +Dot "." +Word "to_string" +LParen "(" +RParen ")" +RBrace "}" +Function "function" +Word "bind_self" +LParen "(" +Word "self" +Comma "," +Word "other" +Colon ":" +Word "Self" +RParen ")" +Arrow "->" +Word "bool" +LBrace "{" +Let "let" +Word "a" +Colon ":" +Word "Self" +Equals "=" +Word "other" +Semicolon ";" +Word "true" +RBrace "}" +RBrace "}" diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__02_parser__main.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__02_parser__main.snap new file mode 100644 index 00000000000..184e1ae1f35 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__02_parser__main.snap @@ -0,0 +1,385 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +=== SYNTAX TREE === +SOURCE_FILE + CLASS_DEF + KW_CLASS "class" + WORD "Box" + GENERIC_PARAM_LIST + LESS "<" + GENERIC_PARAM "T" + WORD "T" + GREATER ">" + L_BRACE "{" + FIELD + WORD "v" + COLON ":" + TYPE_EXPR "T" + WORD "T" + COMMA "," + FUNCTION_DEF + KW_FUNCTION "function" + WORD "which" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "other" + COLON ":" + TYPE_EXPR + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "int" + WORD "int" + GREATER ">" + PIPE "|" + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "string" + WORD "string" + GREATER ">" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "string" + WORD "string" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + MATCH_EXPR + KW_MATCH "match" + L_PAREN "(" + WORD "other" + R_PAREN ")" + L_BRACE "{" + MATCH_ARM + PATTERN + BINDING_PATTERN + KW_LET "let" + WORD "s" + COLON ":" + PATTERN + TYPE_PATTERN + TYPE_EXPR "Self" + WORD "Self" + FAT_ARROW "=>" + STRING_LITERAL "self" + QUOTE """ + WORD "self" + QUOTE """ + COMMA "," + MATCH_ARM + PATTERN + WILDCARD_PATTERN "_" + WORD "_" + FAT_ARROW "=>" + STRING_LITERAL "other" + QUOTE """ + WORD "other" + QUOTE """ + COMMA "," + R_BRACE "}" + R_BRACE "}" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "is_self" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "other" + COLON ":" + TYPE_EXPR + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "int" + WORD "int" + GREATER ">" + PIPE "|" + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "string" + WORD "string" + GREATER ">" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "bool" + WORD "bool" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + IS_EXPR + WORD "other" + KW_IS "is" + PATTERN + TYPE_PATTERN + TYPE_EXPR "Self" + WORD "Self" + R_BRACE "}" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "let_self" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "other" + COLON ":" + TYPE_EXPR + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "int" + WORD "int" + GREATER ">" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "int" + WORD "int" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + LET_STMT + PATTERN + BINDING_PATTERN + KW_LET "let" + WORD "a" + COLON ":" + PATTERN + TYPE_PATTERN + TYPE_EXPR "Self" + WORD "Self" + EQUALS "=" + WORD "other" + SEMICOLON ";" + INTEGER_LITERAL "1" + R_BRACE "}" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "nested_self" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "w" + COLON ":" + TYPE_EXPR + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "Self" + WORD "Self" + GREATER ">" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "int" + WORD "int" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + MATCH_EXPR + KW_MATCH "match" + L_PAREN "(" + WORD "w" + R_PAREN ")" + L_BRACE "{" + MATCH_ARM + PATTERN + BINDING_PATTERN + KW_LET "let" + WORD "s" + COLON ":" + PATTERN + TYPE_PATTERN + TYPE_EXPR + WORD "Box" + TYPE_ARGS + LESS "<" + TYPE_EXPR "Self" + WORD "Self" + GREATER ">" + FAT_ARROW "=>" + INTEGER_LITERAL "1" + COMMA "," + MATCH_ARM + PATTERN + WILDCARD_PATTERN "_" + WORD "_" + FAT_ARROW "=>" + INTEGER_LITERAL "0" + COMMA "," + R_BRACE "}" + R_BRACE "}" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "turbofish_self" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "string" + WORD "string" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + CALL_EXPR + FIELD_ACCESS_EXPR + CALL_EXPR + PATH_EXPR + PATH_EXPR "reflect.type_of" + WORD "reflect" + DOT "." + WORD "type_of" + GENERIC_ARGS + LESS "<" + TYPE_EXPR "Self" + WORD "Self" + GREATER ">" + CALL_ARGS "()" + L_PAREN "(" + R_PAREN ")" + DOT "." + WORD "to_string" + CALL_ARGS "()" + L_PAREN "(" + R_PAREN ")" + R_BRACE "}" + R_BRACE "}" + INTERFACE_DEF + KW_INTERFACE "interface" + WORD "Tester" + L_BRACE "{" + METHOD_SIG + KW_FUNCTION "function" + WORD "eq" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "other" + COLON ":" + TYPE_EXPR "Self" + WORD "Self" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "bool" + WORD "bool" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "check" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "other" + COLON ":" + TYPE_EXPR "Self" + WORD "Self" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "bool" + WORD "bool" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + IS_EXPR + WORD "other" + KW_IS "is" + PATTERN + TYPE_PATTERN + TYPE_EXPR "Self" + WORD "Self" + R_BRACE "}" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "describe" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "string" + WORD "string" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + CALL_EXPR + FIELD_ACCESS_EXPR + CALL_EXPR + PATH_EXPR + PATH_EXPR "reflect.type_of" + WORD "reflect" + DOT "." + WORD "type_of" + GENERIC_ARGS + LESS "<" + TYPE_EXPR "Self" + WORD "Self" + GREATER ">" + CALL_ARGS "()" + L_PAREN "(" + R_PAREN ")" + DOT "." + WORD "to_string" + CALL_ARGS "()" + L_PAREN "(" + R_PAREN ")" + R_BRACE "}" + FUNCTION_DEF + KW_FUNCTION "function" + WORD "bind_self" + PARAMETER_LIST + L_PAREN "(" + PARAMETER "self" + WORD "self" + COMMA "," + PARAMETER + WORD "other" + COLON ":" + TYPE_EXPR "Self" + WORD "Self" + R_PAREN ")" + ARROW "->" + TYPE_EXPR "bool" + WORD "bool" + EXPR_FUNCTION_BODY + BLOCK_EXPR + L_BRACE "{" + LET_STMT + PATTERN + BINDING_PATTERN + KW_LET "let" + WORD "a" + COLON ":" + PATTERN + TYPE_PATTERN + TYPE_EXPR "Self" + WORD "Self" + EQUALS "=" + WORD "other" + SEMICOLON ";" + WORD "true" + R_BRACE "}" + R_BRACE "}" + +=== ERRORS === +None diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__03_hir.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__03_hir.snap new file mode 100644 index 00000000000..c03c736939e --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__03_hir.snap @@ -0,0 +1,31 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +=== HIR2 === +class user.Box { + v: T +} +function user.bind_self(self: ?, other: Self) -> bool [expr] { + { let a: Self = other } true +} +function user.check(self: ?, other: Self) -> bool [expr] { + { } other is Self +} +function user.describe(self: ?) -> string [expr] { + { } reflect.type_of().to_string() +} +function user.is_self(self: ?, other: user.Box | user.Box) -> bool [expr] { + { } other is Self +} +function user.let_self(self: ?, other: user.Box) -> int [expr] { + { let a: Self = other } 1 +} +function user.nested_self(self: ?, w: user.Box) -> int [expr] { + { } match (w) { s: user.Box => 1, _ => 0 } +} +function user.turbofish_self(self: ?) -> string [expr] { + { } reflect.type_of().to_string() +} +function user.which(self: ?, other: user.Box | user.Box) -> string [expr] { + { } match (other) { s: Self => "self", _ => "other" } +} diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__04_tir.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__04_tir.snap new file mode 100644 index 00000000000..4197c19be7a --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__04_tir.snap @@ -0,0 +1,70 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +=== TIR2 === +class user.Box { + v: T +} +function user.Box.which(self: user.Box, other: user.Box | user.Box) -> string throws never { + { : "self" | "other" + match (other : user.Box | user.Box) : "self" | "other" + s: Self => + "self" : "self" + _ => + "other" : "other" + } + !! 880..884: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + !! 880..884: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Box.is_self(self: user.Box, other: user.Box | user.Box) -> bool throws never { + { : bool + other is Self : bool + } + !! 1023..1027: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + !! 1023..1027: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Box.let_self(self: user.Box, other: user.Box) -> int throws never { + { : 1 + let a: Self = other : user.Box + 1 : 1 + } + !! 1103..1107: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Box.nested_self(self: user.Box, w: user.Box) -> int throws never { + { : 1 | 0 + match (w : user.Box) : 1 | 0 + s: Box => + 1 : 1 + _ => + 0 : 0 + } + !! 1230..1234: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Box.turbofish_self(self: user.Box) -> string throws never { + { : string + reflect.type_of().to_string() : string + } + !! 1332..1355: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Tester.check(self: user.Tester, other: unknown) -> bool throws never { + { : bool + other is Self : bool + } + !! 1620..1624: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Tester.describe(self: user.Tester) -> string throws never { + { : string + reflect.type_of().to_string() : string + } + !! 1679..1702: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +function user.Tester.bind_self(self: user.Tester, other: unknown) -> bool throws never { + { : true + let a: Self = other : Self + true : true + } + !! 1788..1792: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). +} +class user.Box$stream { + v: T | null +} diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__05_diagnostics.snap new file mode 100644 index 00000000000..9ce285d6706 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__05_diagnostics.snap @@ -0,0 +1,83 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +=== COMPILER2 DIAGNOSTICS === + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:18:20 ] + │ + 18 │ let s: Self => "self", + │ ──┬─ + │ ╰─── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:23:18 ] + │ + 23 │ other is Self + │ ──┬─ + │ ╰─── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:26:16 ] + │ + 26 │ let a: Self = other; + │ ──┬─ + │ ╰─── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:31:24 ] + │ + 31 │ let s: Box => 1, + │ ──┬─ + │ ╰─── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:36:9 ] + │ + 36 │ reflect.type_of().to_string() + │ ───────────┬─────────── + │ ╰───────────── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:45:18 ] + │ + 45 │ other is Self + │ ──┬─ + │ ╰─── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:48:9 ] + │ + 48 │ reflect.type_of().to_string() + │ ───────────┬─────────── + │ ╰───────────── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ + + [type] error: `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + ╭─[ main.baml:51:16 ] + │ + 51 │ let a: Self = other; + │ ──┬─ + │ ╰─── `Self` is only supported in a signature, not in a method body. In a class method, name the enclosing type explicitly (e.g. `Box`). + │ + │ Note: Error code: E0002 +────╯ diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__10_formatter__main.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__10_formatter__main.snap new file mode 100644 index 00000000000..04115d55fcb --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__10_formatter__main.snap @@ -0,0 +1,57 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +// `Self` is a signature-only construct. In a method *body* type position it has +// no substitution (a class body cannot name its own instantiation as a value +// type, and an interface default method cannot name the implementor), so every +// body-position `Self` is a compile error — rather than silently resolving to a +// misbehaving `TypeVar("Self")` (interface default methods) or matching any +// instantiation (generic class bodies). +// +// The sound alternative in a class method is to name the enclosing type +// explicitly, e.g. `Box` (exercised as a runtime test in +// `ns_match_self_workaround`). + +// ── Class-body method: match arm, `is`, and turbofish ──────────────────────── + +class Box { + v: T, + function which(self, other: Box | Box) -> string { + match (other) { + let s: Self => "self", + _ => "other", + } + } + function is_self(self, other: Box | Box) -> bool { + other is Self + } + function let_self(self, other: Box) -> int { + let a: Self = other; + 1 + } + function nested_self(self, w: Box) -> int { + match (w) { + let s: Box => 1, + _ => 0, + } + } + function turbofish_self(self) -> string { + reflect.type_of().to_string() + } +} + +// ── Interface default-method body (the previously-silent hole) ─────────────── + +interface Tester { + function eq(self, other: Self) -> bool + function check(self, other: Self) -> bool { + other is Self + } + function describe(self) -> string { + reflect.type_of().to_string() + } + function bind_self(self, other: Self) -> bool { + let a: Self = other; + true + } +} diff --git a/baml_language/crates/baml_type/src/family.rs b/baml_language/crates/baml_type/src/family.rs index 32df449a78c..e6ae4ab6e82 100644 --- a/baml_language/crates/baml_type/src/family.rs +++ b/baml_language/crates/baml_type/src/family.rs @@ -13,19 +13,33 @@ //! `validate_runtime`, the conversions, and the `lower_to_runtime` boundary) //! stay hand-written in `lib.rs`, `runtime_ty.rs`, and `realized_ty.rs`. +// The macro-generated accessors, conversions, and borsh impls reference every +// variant — including the deprecated `TyTemplate::TypeArgRefOrWildcard` — which +// is generated infrastructure, not misuse. Consumers importing from the crate +// root still receive the deprecation warning. +#![allow(deprecated)] + use baml_type_macros::ty_family; use borsh::{BorshDeserialize, BorshSerialize}; use crate::{Freshness, FunctionParamMode, Literal, MediaKind, Name, TyAttr, TypeName}; ty_family! { - axes { concrete, abstract, literal, never, typevar, tir, special } - - type Ty { includes: [concrete, abstract, literal, never, typevar, tir, special], child: Self } - type RuntimeTy { includes: [concrete, abstract, literal, never, typevar, special], child: Self } - type RealizedTy { includes: [concrete, abstract, literal, never, special], child: Self } - type ConcreteTy { includes: [concrete, never], child: RuntimeTy } - type ConcreteRealizedTy { includes: [concrete, never], child: RealizedTy } + axes { concrete, abstract, literal, never, typevar, projection, tir, special, template } + + type Ty { includes: [concrete, abstract, literal, never, typevar, projection, tir, special], child: Self } + type RuntimeTy { includes: [concrete, abstract, literal, never, typevar, projection, special], child: Self } + type RealizedTy { includes: [concrete, abstract, literal, never, special], child: Self } + type ConcreteTy { includes: [concrete, never], child: RuntimeTy } + type ConcreteRealizedTy { includes: [concrete, never], child: RealizedTy } + // A `Ty`-shaped template that may contain unresolved generic references. It + // swaps the `typevar` axis's name-based `TypeVar` for the positional + // `template`-axis leaves (`TypeArgRef`/`Wildcard`), while keeping the + // `projection` axis so a symbolic associated projection can still be carried + // structurally. `RealizedTy` is its fully-resolved subset (every + // `RealizedTy` is a valid `TyTemplate`; narrowing back proves no template + // leaf survives), giving the "is fully realized" check for free. + type TyTemplate { includes: [concrete, abstract, literal, never, projection, special, template], child: Self } satellite FunctionParamTy { pub name: Option, @@ -231,8 +245,10 @@ ty_family! { TypeVar(Name, TyAttr), /// Associated type projection, e.g. `P.Output` or `(T as Iterator).Item`. Bound /// during inference; can survive at runtime only inside reflective generic - /// metadata. - #[axis(typevar)] + /// metadata. Split into its own `projection` axis (distinct from `typevar`) + /// so a template can carry an unresolved projection without also admitting + /// a name-based `TypeVar`. + #[axis(projection)] AssociatedTypeProjection { base: Box, /// TODO: Should not be an `Option`; fix once the TIR is able to determine correctly. @@ -291,6 +307,29 @@ ty_family! { Infer { attr: TyAttr, }, + + // --- Template-only: positional references into an enclosing frame's + // type arguments, present only in `TyTemplate` (the `template` axis). + // These carry no `TyAttr` — they are pure structure — so the generated + // `attr()`/`with_attr()` accessors fall back to `TyAttr::EMPTY`. + /// A De Bruijn reference to the n-th type argument of the enclosing + /// call frame. Materialized to a concrete type by `TyTemplate::substitute` + /// against the frame's `type_args`; the template-space replacement for a + /// name-based `TypeVar`. + #[axis(template)] + TypeArgRef(u32), + /// De Bruijn reference like [`TyTemplate::TypeArgRef`], but a dispatch-guard hole: + /// an unconcretized runtime slot matches any actual type argument instead + /// of materializing `unknown` as a constraint. A type-erasure bandaid to be + /// removed once associated-type resolution is complete. + #[axis(template)] + #[deprecated = "Once type erasure is eliminated and associated type resolution are fixed this bandaid will be removed."] + TypeArgRefOrWildcard(u32), + /// Matches any type at this position — a guard hole (BEP-044) that pins + /// some type-args while leaving others unconstrained (e.g. `Pair`). + /// Never materialized to a concrete type. + #[axis(template)] + Wildcard, } } diff --git a/baml_language/crates/baml_type/src/lib.rs b/baml_language/crates/baml_type/src/lib.rs index 898054c9706..3b9e1667045 100644 --- a/baml_language/crates/baml_type/src/lib.rs +++ b/baml_language/crates/baml_type/src/lib.rs @@ -42,7 +42,6 @@ pub use family::*; pub use names::*; pub use primitive::*; pub use runtime_ty::*; -pub use template::TyTemplate; /// Upper bound on the bit-length of a `bigint` value we are willing to /// materialize at runtime. ~268 million bits ≈ 80 million decimal digits ≈ 32 diff --git a/baml_language/crates/baml_type/src/realized_ty.rs b/baml_language/crates/baml_type/src/realized_ty.rs index 0c9ac13ffab..f4c61f8bbb9 100644 --- a/baml_language/crates/baml_type/src/realized_ty.rs +++ b/baml_language/crates/baml_type/src/realized_ty.rs @@ -1,11 +1,53 @@ -//! Regression tests for the generated [`RealizedTy`] conversions. +//! Inherent impls and regression tests for [`RealizedTy`], the deep subset of +//! [`Ty`] with no type variables at any depth. //! -//! [`RealizedTy`] (the deep subset of [`Ty`] with no type variables at any -//! depth) and its conversions are generated in [`crate::family`] by the -//! `ty_family!` macro. These tests pin the narrowing contract — `RealizedTy` +//! [`RealizedTy`] and its conversions are generated in [`crate::family`] by the +//! `ty_family!` macro; this module holds its hand-written behaviour, mirroring +//! [`crate::runtime_ty`]. The tests pin the narrowing contract — `RealizedTy` //! deeply excludes the `typevar`-axis variants (`TypeVar`, //! `AssociatedTypeProjection`) and rejects them by name. +use crate::{RealizedTy, TyAttr}; + +impl RealizedTy { + // --- Primitive constructors (default TyAttr) --- + + /// `int` with default attributes. + pub fn int() -> Self { + RealizedTy::Int { + attr: TyAttr::default(), + } + } + + /// `string` with default attributes. + pub fn string() -> Self { + RealizedTy::String { + attr: TyAttr::default(), + } + } + + /// `null` with default attributes. + pub fn null() -> Self { + RealizedTy::Null { + attr: TyAttr::default(), + } + } + + /// `unknown` (the top type) with default attributes. + pub fn unknown() -> Self { + RealizedTy::BuiltinUnknown { + attr: TyAttr::default(), + } + } +} + +impl std::fmt::Display for RealizedTy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Zero-cost borrowed upcast; rendering lives on `Ty`. + std::fmt::Display::fmt(self.as_ty(), f) + } +} + #[cfg(test)] mod tests { use crate::{Interface, Name, NotRealizedTy, RealizedTy, Ty, TyAttr, TypeName}; diff --git a/baml_language/crates/baml_type/src/template.rs b/baml_language/crates/baml_type/src/template.rs index 46d3e27581e..1af2a360627 100644 --- a/baml_language/crates/baml_type/src/template.rs +++ b/baml_language/crates/baml_type/src/template.rs @@ -1,206 +1,371 @@ -#![expect(deprecated, reason = "TypeArgRefOrWildcard")] -//! `TyTemplate` — a parallel to `Ty` that may contain `TypeArgRef(N)` leaves. +//! Semantic impls for [`TyTemplate`], the `Ty`-shaped type family member that +//! may contain unresolved generic references. //! -//! Used by the `LoadType` VM instruction to represent type expressions that -//! contain references to generic type parameters. A `TyTemplate` with no -//! `TypeArgRef` leaves is "fully concrete" and can be materialised into a `Ty` -//! without a substitution environment. +//! The enum itself, its satellites ([`TyTemplateInterface`], +//! [`crate::TyTemplateFunctionParamTy`]), and the `RealizedTy ⇔ TyTemplate` +//! conversions are generated by the [`ty_family!`](baml_type_macros::ty_family) +//! macro. `TyTemplate` +//! swaps the name-based `TypeVar` for the positional [`TyTemplate::TypeArgRef`] +//! (and the deprecated dispatch-guard [`TyTemplate::TypeArgRefOrWildcard`]) plus +//! the [`TyTemplate::Wildcard`] hole, and keeps [`TyTemplate::AssociatedTypeProjection`] +//! for symbolic projections — so a template can express an unresolved generic +//! *structurally*, never as an opaque name. //! -//! # Design rationale -//! -//! `TyTemplate` is deliberately kept separate from `Ty` (rather than adding a -//! `TypeArgRef` variant to `Ty` itself). This ensures that post-concretisation -//! `Ty` values that reach the runtime via `Object::Type` never contain -//! unresolved references, making the invariant easy to audit. +//! A template that contains none of those template-only leaves is "fully +//! realized" and narrows to a [`RealizedTy`] (the generated `TryFrom`); that +//! narrowing is the [`TyTemplate::is_fully_concrete`] check. //! //! # De Bruijn indexing //! //! `TypeArgRef(n)` refers to the n-th entry in the enclosing call-frame's -//! `type_args` vector (0-based). The compiler assigns indices using -//! `function_generic_params` ordering so that the mapping is stable across -//! compilation. `TypeArgRefOrWildcard(n)` has the same index, but is only valid -//! in dispatch guards: if the runtime slot is unconcretized, it matches any -//! actual type argument instead of materializing `unknown` as a concrete -//! constraint. +//! `type_args` vector (0-based). The compiler assigns indices using +//! `function_generic_params` ordering so the mapping is stable across +//! compilation. `TypeArgRefOrWildcard(n)` has the same index but is only valid +//! in dispatch guards: an unconcretized runtime slot matches any actual type +//! argument instead of materializing `unknown` as a constraint. + +// `substitute`/`Display` walk every variant, including the deprecated +// dispatch-guard `TypeArgRefOrWildcard`. Handling it is not misuse. +#![expect(deprecated, reason = "TypeArgRefOrWildcard is a live template variant")] use std::fmt; -use borsh::{BorshDeserialize, BorshSerialize}; +use crate::{ + Name, RealizedTy, RuntimeFunctionParamTy, RuntimeInterface, RuntimeTy, TyAttr, TyTemplate, + TyTemplateInterface, TypeName, +}; -use crate::{RuntimeTy, TyAttr, TypeName}; +impl TyTemplate { + // --- Ergonomic constructors (default TyAttr) --- -/// A type expression that may contain unresolved generic-parameter references. -#[derive(Debug, Clone, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] -pub enum TyTemplate { - /// Fully concrete leaf — no substitution needed. - Concrete(RuntimeTy), - /// De Bruijn index into the enclosing frame's `type_args`. - TypeArgRef(u32), - /// De Bruijn index into the enclosing frame's `type_args`, with - /// unconcretized runtime slots treated as wildcards in dispatch guards. - /// - /// This is intentionally distinct from `TypeArgRef`: normal type - /// materialization must preserve `unknown`, while guard matching needs to - /// express "this position came from receiver-relative associated evidence - /// that was not concretized at runtime." - #[deprecated = "Once type erasure is eliminated and associated type resolution are fixed this bandaid will be removed."] - TypeArgRefOrWildcard(u32), - /// `T[]` - Array(Box), - /// `T1 | T2 | ...` (a member being `Concrete(null)` encodes optionality) - Union(Vec), - /// `map` - Map(Box, Box), - /// `Class` (generic class instantiation) - Class(TypeName, Vec), - /// `Interface` (generic interface instantiation) - Interface(TypeName, Vec, Vec<(crate::Name, TyTemplate)>), - /// Matches any type at this position. Only meaningful as a type-argument - /// of a `Class` template in an `IsType` guard (BEP-044): it lets a partial - /// guard pin some class type-args while leaving others unconstrained — e.g. - /// `Pair` to dispatch `implements Getter` on a requested - /// `Getter` without constraining `R`. Never materialized via - /// `LoadType`. - Wildcard, -} + /// `T[]` (list) with default attributes. + pub fn list(inner: TyTemplate) -> Self { + TyTemplate::List(Box::new(inner), TyAttr::default()) + } + + /// `map` with default attributes. + pub fn map(key: TyTemplate, value: TyTemplate) -> Self { + TyTemplate::Map { + key: Box::new(key), + value: Box::new(value), + attr: TyAttr::default(), + } + } + + /// `A | B | ...` (union) with default attributes. + pub fn union(members: impl IntoIterator) -> Self { + TyTemplate::Union(members.into_iter().collect(), TyAttr::default()) + } + + /// `Class` (generic class instantiation) with default attributes. + pub fn class(name: TypeName, args: Vec) -> Self { + TyTemplate::Class(name, args, TyAttr::default()) + } + + /// `Interface` with default attributes. + pub fn interface( + name: TypeName, + args: Vec, + associated_bindings: Vec<(Name, TyTemplate)>, + ) -> Self { + TyTemplate::Interface(name, args, associated_bindings, TyAttr::default()) + } -impl TyTemplate { /// Walk the template once, substituting each `TypeArgRef(n)` with /// `type_args[n]`. /// /// If `n` is out of range (e.g. when a generic function is called from a /// non-generic context that didn't supply type arguments), the substitution - /// falls back to `RuntimeTy::unknown()` rather than panicking. This handles - /// stdlib paths where T/S are declared in the function signature for + /// falls back to `RuntimeTy::unknown()` rather than panicking. This handles + /// stdlib paths where `T`/`S` are declared in the function signature for /// type-checking purposes but the corresponding Rust sys-op implementation /// doesn't use the type-arg slot. pub fn substitute(&self, type_args: &[RuntimeTy]) -> RuntimeTy { match self { - Self::Concrete(t) => t.clone(), - #[expect(deprecated)] + // ── Template-only leaves ────────────────────────────────────────── Self::TypeArgRef(n) | Self::TypeArgRefOrWildcard(n) => type_args .get(*n as usize) .cloned() .unwrap_or_else(RuntimeTy::unknown), - Self::Array(inner) => RuntimeTy::list(inner.substitute(type_args)), - Self::Union(parts) => RuntimeTy::union(parts.iter().map(|p| p.substitute(type_args))), - Self::Map(k, v) => RuntimeTy::Map { - key: Box::new(k.substitute(type_args)), - value: Box::new(v.substitute(type_args)), - attr: TyAttr::default(), - }, - Self::Class(name, args) => { - let resolved: Vec = - args.iter().map(|a| a.substitute(type_args)).collect(); - RuntimeTy::Class(name.clone(), resolved, TyAttr::default()) + // A wildcard never reaches `LoadType` materialization; if it ever + // does, fall back to `unknown` rather than panicking. + Self::Wildcard => RuntimeTy::unknown(), + + // ── Composites: recurse, resolving nested template refs ─────────── + Self::List(inner, attr) => { + RuntimeTy::List(Box::new(inner.substitute(type_args)), attr.clone()) } - Self::Interface(name, args, associated_bindings) => { - let resolved_args: Vec = - args.iter().map(|a| a.substitute(type_args)).collect(); - let resolved_bindings = associated_bindings + Self::Map { key, value, attr } => RuntimeTy::Map { + key: Box::new(key.substitute(type_args)), + value: Box::new(value.substitute(type_args)), + attr: attr.clone(), + }, + Self::Union(parts, attr) => RuntimeTy::Union( + parts.iter().map(|p| p.substitute(type_args)).collect(), + attr.clone(), + ), + Self::Class(name, args, attr) => RuntimeTy::Class( + name.clone(), + args.iter().map(|a| a.substitute(type_args)).collect(), + attr.clone(), + ), + Self::Interface(name, args, associated_bindings, attr) => RuntimeTy::Interface( + name.clone(), + args.iter().map(|a| a.substitute(type_args)).collect(), + associated_bindings .iter() .map(|(name, ty)| (name.clone(), ty.substitute(type_args))) - .collect(); - RuntimeTy::Interface( - name.clone(), - resolved_args, - resolved_bindings, - TyAttr::default(), - ) + .collect(), + attr.clone(), + ), + Self::Function { + params, + ret, + throws, + attr, + } => RuntimeTy::Function { + params: params + .iter() + .map(|p| RuntimeFunctionParamTy { + name: p.name.clone(), + ty: p.ty.substitute(type_args), + mode: p.mode, + }) + .collect(), + ret: Box::new(ret.substitute(type_args)), + throws: Box::new(throws.substitute(type_args)), + attr: attr.clone(), + }, + Self::Future(value, error, attr) => RuntimeTy::Future( + Box::new(value.substitute(type_args)), + Box::new(error.substitute(type_args)), + attr.clone(), + ), + Self::WatchAccessor(inner, attr) => { + RuntimeTy::WatchAccessor(Box::new(inner.substitute(type_args)), attr.clone()) } - // A wildcard never reaches `LoadType` materialization; if it ever - // does, fall back to `unknown` rather than panicking. - Self::Wildcard => RuntimeTy::unknown(), + // The projection stays symbolic — only its base/interface positions + // realize. Resolving it to the witness type needs impl knowledge the + // substitution environment doesn't carry. + Self::AssociatedTypeProjection { + base, + interface, + member, + attr, + } => RuntimeTy::AssociatedTypeProjection { + base: Box::new(base.substitute(type_args)), + interface: interface + .as_ref() + .map(|iface| Box::new(iface.substitute(type_args))), + member: member.clone(), + attr: attr.clone(), + }, + + // ── Realized leaf ───────────────────────────────────────────────── + // No template refs and no nested type positions: it narrows to a + // `RealizedTy` (proving realizedness) which widens to `RuntimeTy` by + // transmute. A composite variant is handled above, so a narrowing + // failure here would be a missing arm — surfaced loudly. + other => RealizedTy::try_from(other.clone()) + .unwrap_or_else(|e| unreachable!("realized-leaf template narrowing failed: {e}")) + .into(), } } - /// Returns `true` when no `TypeArgRef` appears anywhere in the template. + /// Returns `true` when the template contains no template-only leaf + /// (`TypeArgRef`, `TypeArgRefOrWildcard`, `Wildcard`) at any depth — i.e. it + /// is a fully realized type that narrows to a [`RealizedTy`]. pub fn is_fully_concrete(&self) -> bool { + <&RealizedTy>::try_from(self).is_ok() + } + + /// Whether a `Wildcard` hole appears anywhere in the template. + /// + /// Distinct from (not) [`Self::is_fully_concrete`]: a template can be + /// non-concrete without holes (frame references materialize to exactly one + /// type under `substitute`), while a hole never materializes — it matches + /// any type at its own position, so a holey template only constrains the + /// positions around its holes. + pub fn contains_wildcard(&self) -> bool { match self { - Self::Concrete(_) => true, - #[expect(deprecated)] + Self::Wildcard => true, Self::TypeArgRef(_) | Self::TypeArgRefOrWildcard(_) => false, - Self::Array(inner) => inner.is_fully_concrete(), - Self::Union(parts) => parts.iter().all(TyTemplate::is_fully_concrete), - Self::Map(k, v) => k.is_fully_concrete() && v.is_fully_concrete(), - Self::Class(_, args) => args.iter().all(TyTemplate::is_fully_concrete), - Self::Interface(_, args, associated_bindings) => { - args.iter().all(TyTemplate::is_fully_concrete) - && associated_bindings + Self::List(inner, _) | Self::WatchAccessor(inner, _) => inner.contains_wildcard(), + Self::Map { key, value, .. } => key.contains_wildcard() || value.contains_wildcard(), + Self::Future(value, error, _) => value.contains_wildcard() || error.contains_wildcard(), + Self::Union(parts, _) => parts.iter().any(Self::contains_wildcard), + Self::Class(_, args, _) => args.iter().any(Self::contains_wildcard), + Self::Interface(_, args, associated_bindings, _) => { + args.iter().any(Self::contains_wildcard) + || associated_bindings .iter() - .all(|(_, ty)| ty.is_fully_concrete()) + .any(|(_, ty)| ty.contains_wildcard()) } - // No `TypeArgRef` to substitute, so it needs no environment. - Self::Wildcard => true, + Self::Function { + params, + ret, + throws, + .. + } => { + params.iter().any(|p| p.ty.contains_wildcard()) + || ret.contains_wildcard() + || throws.contains_wildcard() + } + Self::AssociatedTypeProjection { + base, interface, .. + } => { + base.contains_wildcard() + || interface.as_ref().is_some_and(|iface| { + iface.generics.iter().any(Self::contains_wildcard) + || iface + .associated_types + .iter() + .any(|(_, ty)| ty.contains_wildcard()) + }) + } + // Realized leaves carry no nested type positions. + Self::Int { .. } + | Self::Bigint { .. } + | Self::Float { .. } + | Self::String { .. } + | Self::Bool { .. } + | Self::Null { .. } + | Self::Uint8Array { .. } + | Self::Media(..) + | Self::Literal(..) + | Self::Enum(..) + | Self::EnumVariant(..) + | Self::RustType { .. } + | Self::Type { .. } + | Self::Resource { .. } + | Self::PromptAst { .. } + | Self::Void { .. } + | Self::TypeAlias(..) + | Self::BuiltinUnknown { .. } + | Self::Never { .. } => false, } } } +impl TyTemplateInterface { + /// Substitute frame type args through the interface's generic and + /// associated-binding positions (see [`TyTemplate::substitute`]). + fn substitute(&self, type_args: &[RuntimeTy]) -> RuntimeInterface { + RuntimeInterface::new( + self.name.clone(), + self.generics + .iter() + .map(|g| g.substitute(type_args)) + .collect(), + self.associated_types + .iter() + .map(|(name, ty)| (name.clone(), ty.substitute(type_args))) + .collect(), + ) + } +} + impl fmt::Display for TyTemplate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Render through `Ty`'s `Display` so every shared construct (list-postfix + // parenthesization, `future<…>` casing, function `throws` handling, …) + // stays byte-identical to the canonical type renderer — no per-variant + // drift. Only the template-only leaves need a placeholder: a frame ref + // shows as `#n` (a `TypeVar` named `#n`) and a hole as `_` (`Infer`). + fmt::Display::fmt(&self.to_display_ty(), f) + } +} + +impl TyTemplate { + /// A lossy [`Ty`] view for rendering only: frame refs become + /// `TypeVar("#n")`, the dispatch-guard ref `TypeVar("#n?")`, and a wildcard + /// hole `Infer` (`_`). Every other node maps structurally, so + /// [`fmt::Display`] can delegate to `Ty`'s renderer. + fn to_display_ty(&self) -> crate::Ty { + use crate::Ty; match self { - Self::Concrete(ty) => write!(f, "{ty}"), - Self::TypeArgRef(n) => write!(f, "#{n}"), - #[expect(deprecated)] - Self::TypeArgRefOrWildcard(n) => write!(f, "#{n}?"), - Self::Array(inner) => write!(f, "{inner}[]"), - Self::Union(parts) => { - // `?` is sugar that exists only in source/lowering; after that a - // nullable type is a plain union. Concrete function members are - // parenthesized so a nullable callback stays unambiguous. - let strs: Vec = parts + Self::TypeArgRef(n) => Ty::TypeVar(Name::new(format!("#{n}")), TyAttr::default()), + Self::TypeArgRefOrWildcard(n) => { + Ty::TypeVar(Name::new(format!("#{n}?")), TyAttr::default()) + } + Self::Wildcard => Ty::Infer { + attr: TyAttr::default(), + }, + Self::List(inner, attr) => Ty::List(Box::new(inner.to_display_ty()), attr.clone()), + Self::Map { key, value, attr } => Ty::Map { + key: Box::new(key.to_display_ty()), + value: Box::new(value.to_display_ty()), + attr: attr.clone(), + }, + Self::Union(parts, attr) => Ty::Union( + parts.iter().map(Self::to_display_ty).collect(), + attr.clone(), + ), + Self::Class(name, args, attr) => Ty::Class( + name.clone(), + args.iter().map(Self::to_display_ty).collect(), + attr.clone(), + ), + Self::Interface(name, args, associated_bindings, attr) => Ty::Interface( + name.clone(), + args.iter().map(Self::to_display_ty).collect(), + associated_bindings + .iter() + .map(|(n, t)| (n.clone(), t.to_display_ty())) + .collect(), + attr.clone(), + ), + Self::Function { + params, + ret, + throws, + attr, + } => Ty::Function { + params: params .iter() - .map(|p| { - let rendered = p.to_string(); - if matches!(p, TyTemplate::Concrete(ty) if matches!(ty, RuntimeTy::Function { .. })) - { - format!("({rendered})") - } else { - rendered - } + .map(|p| crate::FunctionParamTy { + name: p.name.clone(), + ty: p.ty.to_display_ty(), + mode: p.mode, }) - .collect(); - write!(f, "{}", strs.join(" | ")) - } - Self::Map(k, v) => write!(f, "map<{k}, {v}>"), - Self::Class(name, args) => { - write!(f, "{}", name.display_name())?; - if !args.is_empty() { - write!(f, "<")?; - for (i, arg) in args.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{arg}")?; - } - write!(f, ">")?; - } - Ok(()) - } - Self::Interface(name, args, associated_bindings) => { - write!(f, "{}", name.display_name())?; - if !args.is_empty() || !associated_bindings.is_empty() { - write!(f, "<")?; - let mut first = true; - for arg in args { - if !first { - write!(f, ", ")?; - } - first = false; - write!(f, "{arg}")?; - } - for (name, ty) in associated_bindings { - if !first { - write!(f, ", ")?; - } - first = false; - write!(f, "{name} = {ty}")?; - } - write!(f, ">")?; - } - Ok(()) + .collect(), + ret: Box::new(ret.to_display_ty()), + throws: Box::new(throws.to_display_ty()), + attr: attr.clone(), + }, + Self::Future(value, error, attr) => Ty::Future( + Box::new(value.to_display_ty()), + Box::new(error.to_display_ty()), + attr.clone(), + ), + Self::WatchAccessor(inner, attr) => { + Ty::WatchAccessor(Box::new(inner.to_display_ty()), attr.clone()) } - Self::Wildcard => write!(f, "_"), + Self::AssociatedTypeProjection { + base, + interface, + member, + attr, + } => Ty::AssociatedTypeProjection { + base: Box::new(base.to_display_ty()), + interface: interface.as_ref().map(|iface| { + Box::new(crate::Interface { + name: iface.name.clone(), + generics: iface.generics.iter().map(Self::to_display_ty).collect(), + associated_types: iface + .associated_types + .iter() + .map(|(n, t)| (n.clone(), t.to_display_ty())) + .collect(), + }) + }), + member: member.clone(), + attr: attr.clone(), + }, + // A realized leaf widens into `Ty` by the generated conversion. + other => Ty::from(RealizedTy::try_from(other.clone()).unwrap_or_else(|e| { + unreachable!("realized-leaf template widening for display failed: {e}") + })), } } } @@ -211,7 +376,7 @@ mod tests { #[test] fn concrete_template_substitutes_to_itself() { - let tmpl = TyTemplate::Concrete(RuntimeTy::int()); + let tmpl = TyTemplate::from(RealizedTy::int()); assert_eq!(tmpl.substitute(&[]), RuntimeTy::int()); assert!(tmpl.is_fully_concrete()); } @@ -226,8 +391,8 @@ mod tests { #[test] fn array_of_type_arg_ref() { - // Array(TypeArgRef(0)) with type_args=[int] → int[] - let tmpl = TyTemplate::Array(Box::new(TyTemplate::TypeArgRef(0))); + // list<#0> with type_args=[int] → int[] + let tmpl = TyTemplate::list(TyTemplate::TypeArgRef(0)); let ty = tmpl.substitute(&[RuntimeTy::int()]); assert_eq!(ty, RuntimeTy::list(RuntimeTy::int())); assert!(!tmpl.is_fully_concrete()); @@ -235,10 +400,10 @@ mod tests { #[test] fn optional_of_type_arg_ref() { - // (TypeArgRef(0) | null) with type_args=[string] → string? - let tmpl = TyTemplate::Union(vec![ + // (#0 | null) with type_args=[string] → string? + let tmpl = TyTemplate::union([ TyTemplate::TypeArgRef(0), - TyTemplate::Concrete(RuntimeTy::null()), + TyTemplate::from(RealizedTy::null()), ]); let ty = tmpl.substitute(&[RuntimeTy::string()]); assert_eq!(ty, RuntimeTy::optional(RuntimeTy::string())); @@ -247,15 +412,15 @@ mod tests { #[test] fn concrete_array_is_fully_concrete() { - let tmpl = TyTemplate::Array(Box::new(TyTemplate::Concrete(RuntimeTy::int()))); + let tmpl = TyTemplate::list(TyTemplate::from(RealizedTy::int())); assert!(tmpl.is_fully_concrete()); } #[test] fn union_of_concrete_is_fully_concrete() { - let tmpl = TyTemplate::Union(vec![ - TyTemplate::Concrete(RuntimeTy::int()), - TyTemplate::Concrete(RuntimeTy::string()), + let tmpl = TyTemplate::union([ + TyTemplate::from(RealizedTy::int()), + TyTemplate::from(RealizedTy::string()), ]); assert!(tmpl.is_fully_concrete()); let ty = tmpl.substitute(&[]); @@ -267,8 +432,8 @@ mod tests { #[test] fn union_containing_type_arg_ref_not_concrete() { - let tmpl = TyTemplate::Union(vec![ - TyTemplate::Concrete(RuntimeTy::int()), + let tmpl = TyTemplate::union([ + TyTemplate::from(RealizedTy::int()), TyTemplate::TypeArgRef(0), ]); assert!(!tmpl.is_fully_concrete()); @@ -276,9 +441,7 @@ mod tests { #[test] fn class_with_type_arg_ref_substitution() { - // Class("Container", [TypeArgRef(0)]) with type_args=[user_class("User")] - // → RuntimeTy::Class("Container", [user_class("User")], _) - let tmpl = TyTemplate::Class( + let tmpl = TyTemplate::class( TypeName::local(crate::Name::new("Container")), vec![TyTemplate::TypeArgRef(0)], ); @@ -293,7 +456,7 @@ mod tests { #[test] fn class_no_args_is_fully_concrete() { - let tmpl = TyTemplate::Class(TypeName::local(crate::Name::new("User")), vec![]); + let tmpl = TyTemplate::class(TypeName::local(crate::Name::new("User")), vec![]); assert!(tmpl.is_fully_concrete()); let result = tmpl.substitute(&[]); assert_eq!( diff --git a/baml_language/crates/baml_type_macros/src/emit.rs b/baml_language/crates/baml_type_macros/src/emit.rs index 42b1691ce59..90fed547b2f 100644 --- a/baml_language/crates/baml_type_macros/src/emit.rs +++ b/baml_language/crates/baml_type_macros/src/emit.rs @@ -90,10 +90,11 @@ pub(crate) fn member_variants<'a>( .filter(move |v| member.includes.contains(&v.axis)) } -/// Generate the mechanical `attr` / `with_attr` accessors for a member. Every -/// variant carries a `TyAttr`: named variants in an `attr` field, tuple -/// variants as the last positional. A unit variant (none today) would have -/// nowhere to hold one, so it is rejected with a `compile_error!`. +/// Generate the mechanical `attr` / `with_attr` accessors for a member. An +/// attr-carrying variant exposes its `TyAttr` (a named `attr` field or the last +/// tuple positional); an attr-less template leaf (`TypeArgRef`, `Wildcard`) +/// borrows the shared [`TyAttr::EMPTY`] and ignores `with_attr` (it has nowhere +/// to store one). fn gen_accessors(family: &Family, member: &Member) -> TokenStream { let name = &member.name; let attr_arms = member_variants(family, member).map(|v| attr_arm(name, v)); @@ -119,15 +120,32 @@ fn gen_accessors(family: &Family, member: &Member) -> TokenStream { fn attr_arm(name: &Ident, v: &MVariant) -> TokenStream { let vident = &v.ident; + // Attr-less leaves borrow the shared empty attribute set. + if !v.has_attr { + return match &v.fields { + Fields::Unit => quote! { #name::#vident => &TyAttr::EMPTY }, + _ => quote! { #name::#vident { .. } => &TyAttr::EMPTY }, + }; + } match &v.fields { Fields::Named(_) => quote! { #name::#vident { attr, .. } => attr }, Fields::Unnamed(_) => quote! { #name::#vident(.., attr) => attr }, - Fields::Unit => attr_less_error(name, vident), + // `has_attr` is false for a unit variant, handled above. + Fields::Unit => unreachable!("attr-carrying unit variant is impossible"), } } fn with_attr_arm(name: &Ident, v: &MVariant) -> TokenStream { let vident = &v.ident; + // Attr-less leaves have nowhere to store an attribute, so `with_attr` is + // the identity — the incoming `attr` is dropped. `_ = &attr` silences the + // unused-binding lint without moving it (every other arm consumes `attr`). + if !v.has_attr { + return match &v.fields { + Fields::Unit => quote! { this @ #name::#vident => { let _ = &attr; this } }, + _ => quote! { this @ #name::#vident { .. } => { let _ = &attr; this } }, + }; + } match &v.fields { Fields::Named(named) => { let rest: Vec<&Ident> = named @@ -147,17 +165,11 @@ fn with_attr_arm(name: &Ident, v: &MVariant) -> TokenStream { #name::#vident(#(#binds,)* _) => #name::#vident(#(#binds,)* attr) } } - Fields::Unit => attr_less_error(name, vident), + // `has_attr` is false for a unit variant, handled above. + Fields::Unit => unreachable!("attr-carrying unit variant is impossible"), } } -fn attr_less_error(name: &Ident, vident: &Ident) -> TokenStream { - let msg = format!( - "ty_family: variant `{name}::{vident}` has no `TyAttr` field, so `attr`/`with_attr` cannot be generated" - ); - quote! { #name::#vident => ::core::compile_error!(#msg) } -} - fn gen_satellite(family: &Family, member: &Member, sat: &Satellite) -> TokenStream { let sat_name = satellite_name_for(&member.name, &sat.name); let map = replacements(family, &member.name); diff --git a/baml_language/crates/baml_type_macros/src/parse.rs b/baml_language/crates/baml_type_macros/src/parse.rs index 9ea81c2d653..712aa188c2f 100644 --- a/baml_language/crates/baml_type_macros/src/parse.rs +++ b/baml_language/crates/baml_type_macros/src/parse.rs @@ -179,6 +179,10 @@ pub(crate) struct MVariant { pub(crate) ident: Ident, pub(crate) fields: Fields, pub(crate) axis: usize, + /// Whether the variant carries a `TyAttr` (a named `attr` field or a + /// trailing tuple `TyAttr`). Attr-less template leaves get accessor + /// fallbacks instead of a compile error. + pub(crate) has_attr: bool, } impl Family { @@ -284,21 +288,18 @@ fn resolve_variant( ) })?; let axis = axis_index(&axis_ident)?; - if !carries_ty_attr(&variant.fields) { - return Err(syn::Error::new( - span, - format!( - "variant `{}` must carry a `TyAttr` (a named `attr` field, or as the last \ - tuple field) so `attr`/`with_attr` can be generated", - variant.ident - ), - )); - } + // A variant need not carry a `TyAttr`: template-only leaves (`TypeArgRef`, + // `Wildcard`) are pure structure with no streaming metadata. The generated + // `attr()`/`with_attr()` accessors fall back to `TyAttr::EMPTY` / identity + // for them (see `emit::attr_arm`). `has_attr` records which case applies so + // the accessor arms don't need to re-derive it. + let has_attr = carries_ty_attr(&variant.fields); Ok(MVariant { attrs, ident: variant.ident, fields: variant.fields, axis, + has_attr, }) } diff --git a/baml_language/crates/bex_engine/src/conversion.rs b/baml_language/crates/bex_engine/src/conversion.rs index 2e5ae8a390e..4b5b98c9237 100644 --- a/baml_language/crates/bex_engine/src/conversion.rs +++ b/baml_language/crates/bex_engine/src/conversion.rs @@ -1368,20 +1368,44 @@ pub(crate) struct ParamVarPositions { fn template_max_type_arg_ref(t: &baml_type::TyTemplate) -> Option { use baml_type::TyTemplate as T; match t { - T::Concrete(_) | T::Wildcard => None, T::TypeArgRef(n) | T::TypeArgRefOrWildcard(n) => Some(*n), - T::Array(inner) => template_max_type_arg_ref(inner), - T::Map(k, v) => template_max_type_arg_ref(k) + T::List(inner, _) | T::WatchAccessor(inner, _) => template_max_type_arg_ref(inner), + T::Map { key, value, .. } | T::Future(key, value, _) => template_max_type_arg_ref(key) .into_iter() - .chain(template_max_type_arg_ref(v)) + .chain(template_max_type_arg_ref(value)) .max(), - T::Union(members) => members.iter().filter_map(template_max_type_arg_ref).max(), - T::Class(_, args) => args.iter().filter_map(template_max_type_arg_ref).max(), - T::Interface(_, args, assoc) => args + T::Union(members, _) => members.iter().filter_map(template_max_type_arg_ref).max(), + T::Class(_, args, _) => args.iter().filter_map(template_max_type_arg_ref).max(), + T::Interface(_, args, assoc, _) => args .iter() .chain(assoc.iter().map(|(_, t)| t)) .filter_map(template_max_type_arg_ref) .max(), + T::Function { + params, + ret, + throws, + .. + } => params + .iter() + .map(|p| &p.ty) + .chain([ret.as_ref(), throws.as_ref()]) + .filter_map(template_max_type_arg_ref) + .max(), + T::AssociatedTypeProjection { + base, interface, .. + } => template_max_type_arg_ref(base) + .into_iter() + .chain(interface.iter().flat_map(|iface| { + iface + .generics + .iter() + .chain(iface.associated_types.iter().map(|(_, t)| t)) + .filter_map(template_max_type_arg_ref) + })) + .max(), + // Realized leaves and `Wildcard` carry no frame ref. + _ => None, } } diff --git a/baml_language/crates/bex_heap/src/gc.rs b/baml_language/crates/bex_heap/src/gc.rs index 7523a2ed503..00eaffa9a1e 100644 --- a/baml_language/crates/bex_heap/src/gc.rs +++ b/baml_language/crates/bex_heap/src/gc.rs @@ -1298,7 +1298,7 @@ mod tests { field_type: baml_type::RuntimeTy::Int { attr: baml_type::TyAttr::default(), }, - field_template: baml_type::TyTemplate::Concrete(baml_type::RuntimeTy::Int { + field_template: baml_type::TyTemplate::from(baml_type::RealizedTy::Int { attr: baml_type::TyAttr::default(), }), description: None, diff --git a/baml_language/crates/bex_heap/src/tlab.rs b/baml_language/crates/bex_heap/src/tlab.rs index 619e2472de0..f416a6802a9 100644 --- a/baml_language/crates/bex_heap/src/tlab.rs +++ b/baml_language/crates/bex_heap/src/tlab.rs @@ -595,7 +595,7 @@ mod tests { field_type: baml_type::RuntimeTy::Int { attr: baml_type::TyAttr::default(), }, - field_template: baml_type::TyTemplate::Concrete(baml_type::RuntimeTy::Int { + field_template: baml_type::TyTemplate::from(baml_type::RealizedTy::Int { attr: baml_type::TyAttr::default(), }), description: None, @@ -607,7 +607,7 @@ mod tests { field_type: baml_type::RuntimeTy::Int { attr: baml_type::TyAttr::default(), }, - field_template: baml_type::TyTemplate::Concrete(baml_type::RuntimeTy::Int { + field_template: baml_type::TyTemplate::from(baml_type::RealizedTy::Int { attr: baml_type::TyAttr::default(), }), description: None, diff --git a/baml_language/crates/bex_vm/src/lib.rs b/baml_language/crates/bex_vm/src/lib.rs index 26461263d34..fbbb6649787 100644 --- a/baml_language/crates/bex_vm/src/lib.rs +++ b/baml_language/crates/bex_vm/src/lib.rs @@ -19,6 +19,7 @@ pub mod package_baml; pub mod package_boundary; pub mod package_load; mod type_context; +mod type_match; pub mod types; pub mod vm; pub mod watch; diff --git a/baml_language/crates/bex_vm/src/package_baml/mod.rs b/baml_language/crates/bex_vm/src/package_baml/mod.rs index a7dae0f1952..0a383cd3136 100644 --- a/baml_language/crates/bex_vm/src/package_baml/mod.rs +++ b/baml_language/crates/bex_vm/src/package_baml/mod.rs @@ -266,9 +266,13 @@ pub(super) fn make_compare_callee(vm: &mut BexVm, v: Value) -> Result Option let fn_name = to_string_override_fn_name(vm, v)?; let fn_ptr = vm.find_function_by_name(&fn_name)?; + let type_args = vm.bound_method_curried_type_args(v); Some(vm.alloc_bound_method(bex_vm_types::BoundMethod { function: fn_ptr, receiver: v, + // A stdlib-dispatched `to_string` override on `v`'s concrete class; curry + // its class type args (→ `Self`) from the receiver. + type_args, })) } @@ -330,9 +338,13 @@ pub(super) fn to_string_override_fn_name(vm: &BexVm, v: Value) -> Option pub(super) fn make_to_json_override_callee(vm: &mut BexVm, v: Value) -> Option { let fn_name = to_json_override_fn_name(vm, v)?; let fn_ptr = vm.find_function_by_name(&fn_name)?; + let type_args = vm.bound_method_curried_type_args(v); Some(vm.alloc_bound_method(bex_vm_types::BoundMethod { function: fn_ptr, receiver: v, + // A stdlib-dispatched `to_json` override on `v`'s concrete class; curry + // its class type args (→ `Self`) from the receiver. + type_args, })) } diff --git a/baml_language/crates/bex_vm/src/package_baml/resolve.rs b/baml_language/crates/bex_vm/src/package_baml/resolve.rs index 87acb02be72..b36e6849a5f 100644 --- a/baml_language/crates/bex_vm/src/package_baml/resolve.rs +++ b/baml_language/crates/bex_vm/src/package_baml/resolve.rs @@ -14,7 +14,7 @@ use std::borrow::Cow; use baml_type::{ - Literal, MediaKind, Name, RuntimeInterface, RuntimeTy, TyAttr, TyTemplate, TypeName, + Literal, MediaKind, Name, RealizedTy, RuntimeInterface, RuntimeTy, TyAttr, TyTemplate, TypeName, }; use bex_vm_types::{ HeapPtr, @@ -357,6 +357,14 @@ fn match_template( concrete: &RuntimeTy, bindings: &mut [Option], ) -> bool { + // A fully-realized pattern carries no frame refs or holes: compare it to + // the concrete type semantically (union-order-insensitive, matching the + // type checker). The flattened successor to the old `Concrete(t)` arm — + // `ty_equivalent` falls back to structural `==` for non-union leaves. + if let Ok(realized) = <&RealizedTy>::try_from(pattern) { + return ty_equivalent(realized.as_runtime_ty(), concrete); + } + match pattern { TyTemplate::Wildcard => true, // `TypeArgRefOrWildcard` is a de Bruijn ref like `TypeArgRef` (substitution @@ -374,28 +382,23 @@ fn match_template( None => false, } } - // Compare semantically rather than via derived `==`: union members are - // order-insensitive (`int | string` ≡ `string | int`), matching the type - // checker. `ty_equivalent` falls back to structural `==` (incl. `TyAttr`) - // for non-union leaves; for-type patterns and the runtime types we match - // carry default attrs (stream artifacts are keyed under separate `$stream` - // names). - TyTemplate::Concrete(t) => ty_equivalent(t, concrete), - TyTemplate::Array(inner) => match concrete { + TyTemplate::List(inner, _) => match concrete { RuntimeTy::List(elem, _) => match_template(inner, elem, bindings), _ => false, }, - TyTemplate::Map(k, v) => match concrete { - RuntimeTy::Map { key, value, .. } => { - match_template(k, key, bindings) && match_template(v, value, bindings) - } + TyTemplate::Map { key, value, .. } => match concrete { + RuntimeTy::Map { + key: ckey, + value: cvalue, + .. + } => match_template(key, ckey, bindings) && match_template(value, cvalue, bindings), _ => false, }, - TyTemplate::Class(name, args) => match concrete { + TyTemplate::Class(name, args, _) => match concrete { RuntimeTy::Class(cname, cargs, _) => name == cname && all_match(args, cargs, bindings), _ => false, }, - TyTemplate::Interface(name, args, assoc) => match concrete { + TyTemplate::Interface(name, args, assoc, _) => match concrete { RuntimeTy::Interface(cname, cargs, cassoc, _) => { // Each *concrete* binding must match a same-named pattern binding, found // order-insensitively; extra pattern bindings don't constrain. This @@ -418,13 +421,52 @@ fn match_template( } _ => false, }, + // A symbolic projection whose witness type was not resolved at compile + // time — never a match: a runtime value's concrete type carries no + // unresolved projection to unify with. + TyTemplate::AssociatedTypeProjection { .. } => false, + TyTemplate::Function { + params, + ret, + throws, + .. + } => match concrete { + RuntimeTy::Function { + params: cparams, + ret: cret, + throws: cthrows, + .. + } => { + params.len() == cparams.len() + && params.iter().zip(cparams).all(|(p, cp)| { + p.name == cp.name + && p.mode == cp.mode + && match_template(&p.ty, &cp.ty, bindings) + }) + && match_template(ret, cret, bindings) + && match_template(throws, cthrows, bindings) + } + _ => false, + }, + TyTemplate::Future(value, error, _) => match concrete { + RuntimeTy::Future(cvalue, cerror, _) => { + match_template(value, cvalue, bindings) && match_template(error, cerror, bindings) + } + _ => false, + }, + TyTemplate::WatchAccessor(inner, _) => match concrete { + RuntimeTy::WatchAccessor(cinner, _) => match_template(inner, cinner, bindings), + _ => false, + }, // Order-insensitive union match: each pattern member must pair with a // distinct concrete member, with type-var bindings consistent across the // chosen pairing (so `Box` matches a value `Box`). - TyTemplate::Union(parts) => match concrete { + TyTemplate::Union(parts, _) => match concrete { RuntimeTy::Union(cparts, _) => match_union(parts, cparts, bindings), _ => false, }, + // Realized leaves are handled by the fast path above. + _ => false, } } @@ -699,11 +741,16 @@ pub(super) fn implementor_entries(vm: &BexVm, iface: &TypeName) -> Vec { + // A concrete for-type (`int`, a monomorphic class, `int[]`, …) + // narrows to a realized type — the implementor is that type. + other if <&RealizedTy>::try_from(other).is_ok() => { + let realized = <&RealizedTy>::try_from(other) + .unwrap_or_else(|_| unreachable!("guarded by the `is_ok` above")); let (args, assoc) = pinned_interface_instantiation(rule); - push_unique(&mut out, (ty.clone(), args, assoc)); + push_unique(&mut out, (realized.as_runtime_ty().clone(), args, assoc)); } - TyTemplate::Class(name, _) => { + // A generic class for-type (`Foo`) is reported by its base. + TyTemplate::Class(name, _, _) => { let (args, assoc) = pinned_interface_instantiation(rule); let base = RuntimeTy::Class(name.clone(), Vec::new(), TyAttr::default()); push_unique(&mut out, (base, args, assoc)); @@ -767,16 +814,43 @@ fn max_type_arg_ref(t: &TyTemplate) -> Option { match t { #[expect(deprecated)] TyTemplate::TypeArgRef(n) | TyTemplate::TypeArgRefOrWildcard(n) => Some(*n), - TyTemplate::Concrete(_) | TyTemplate::Wildcard => None, - TyTemplate::Array(inner) => max_type_arg_ref(inner), - TyTemplate::Map(k, v) => max_type_arg_ref(k).max(max_type_arg_ref(v)), - TyTemplate::Union(parts) => parts.iter().filter_map(max_type_arg_ref).max(), - TyTemplate::Class(_, args) => args.iter().filter_map(max_type_arg_ref).max(), - TyTemplate::Interface(_, args, assoc) => args + TyTemplate::List(inner, _) | TyTemplate::WatchAccessor(inner, _) => max_type_arg_ref(inner), + TyTemplate::Map { key, value, .. } | TyTemplate::Future(key, value, _) => { + max_type_arg_ref(key).max(max_type_arg_ref(value)) + } + TyTemplate::Union(parts, _) => parts.iter().filter_map(max_type_arg_ref).max(), + TyTemplate::Class(_, args, _) => args.iter().filter_map(max_type_arg_ref).max(), + TyTemplate::Interface(_, args, assoc, _) => args .iter() .filter_map(max_type_arg_ref) .chain(assoc.iter().filter_map(|(_, t)| max_type_arg_ref(t))) .max(), + TyTemplate::Function { + params, + ret, + throws, + .. + } => params + .iter() + .filter_map(|p| max_type_arg_ref(&p.ty)) + .chain(max_type_arg_ref(ret)) + .chain(max_type_arg_ref(throws)) + .max(), + TyTemplate::AssociatedTypeProjection { + base, interface, .. + } => max_type_arg_ref(base) + .into_iter() + .chain(interface.iter().flat_map(|iface| { + iface.generics.iter().filter_map(max_type_arg_ref).chain( + iface + .associated_types + .iter() + .filter_map(|(_, t)| max_type_arg_ref(t)), + ) + })) + .max(), + // Realized leaves and `Wildcard` carry no frame ref. + _ => None, } } @@ -785,15 +859,42 @@ fn template_has_type_arg_ref(t: &TyTemplate) -> bool { match t { #[expect(deprecated)] TyTemplate::TypeArgRef(_) | TyTemplate::TypeArgRefOrWildcard(_) => true, - TyTemplate::Concrete(_) | TyTemplate::Wildcard => false, - TyTemplate::Array(inner) => template_has_type_arg_ref(inner), - TyTemplate::Map(k, v) => template_has_type_arg_ref(k) || template_has_type_arg_ref(v), - TyTemplate::Union(parts) => parts.iter().any(template_has_type_arg_ref), - TyTemplate::Class(_, args) => args.iter().any(template_has_type_arg_ref), - TyTemplate::Interface(_, args, assoc) => { + TyTemplate::List(inner, _) | TyTemplate::WatchAccessor(inner, _) => { + template_has_type_arg_ref(inner) + } + TyTemplate::Map { key, value, .. } | TyTemplate::Future(key, value, _) => { + template_has_type_arg_ref(key) || template_has_type_arg_ref(value) + } + TyTemplate::Union(parts, _) => parts.iter().any(template_has_type_arg_ref), + TyTemplate::Class(_, args, _) => args.iter().any(template_has_type_arg_ref), + TyTemplate::Interface(_, args, assoc, _) => { args.iter().any(template_has_type_arg_ref) || assoc.iter().any(|(_, t)| template_has_type_arg_ref(t)) } + TyTemplate::Function { + params, + ret, + throws, + .. + } => { + params.iter().any(|p| template_has_type_arg_ref(&p.ty)) + || template_has_type_arg_ref(ret) + || template_has_type_arg_ref(throws) + } + TyTemplate::AssociatedTypeProjection { + base, interface, .. + } => { + template_has_type_arg_ref(base) + || interface.as_ref().is_some_and(|iface| { + iface.generics.iter().any(template_has_type_arg_ref) + || iface + .associated_types + .iter() + .any(|(_, t)| template_has_type_arg_ref(t)) + }) + } + // Realized leaves and `Wildcard` carry no frame ref. + _ => false, } } @@ -905,7 +1006,11 @@ mod tests { #[test] fn concrete_literal_pattern_matches_only_the_literal() { let one = RuntimeTy::Literal(Literal::Int(1), Freshness::Regular, TyAttr::default()); - let pattern = TyTemplate::Concrete(one.clone()); + let pattern = TyTemplate::from(RealizedTy::Literal( + Literal::Int(1), + Freshness::Regular, + TyAttr::default(), + )); let mut binds: Vec> = Vec::new(); assert!(match_template(&pattern, &one, &mut binds)); assert!(!match_template(&pattern, &RuntimeTy::int(), &mut binds)); diff --git a/baml_language/crates/bex_vm/src/type_context.rs b/baml_language/crates/bex_vm/src/type_context.rs index 8b4c8a92502..0cede34b92b 100644 --- a/baml_language/crates/bex_vm/src/type_context.rs +++ b/baml_language/crates/bex_vm/src/type_context.rs @@ -15,10 +15,12 @@ //! there is no `requires`-closure entry at runtime yet (the resolver proves //! `concrete: I`, not `I_a requires I_b`). //! -//! NOTE: not yet wired into any caller. The `RuntimeTy::is_subtype_of` call sites -//! migrate onto this context only once the compiler adopts the canonical algebra -//! (the runtime must not become stricter than the compiler — see the -//! List/Map-invariance sequencing constraint). +//! First wired into [`crate::type_match`] — the `IsType` value matcher — as of +//! the canonical-algebra unit. Other `RuntimeTy::is_subtype_of` call sites still +//! migrate onto this context only as the surrounding relation is made canonical +//! (the runtime must not become stricter than the compiler where it would break +//! a proven-exhaustive match — see the List/Map-invariance sequencing constraint; +//! the matcher's callers gate structural tests accordingly). use baml_type::{Interface, Name, QualifiedTypeName, RuntimeTy, Ty, normalize::TypeContext}; use bex_vm_types::types::Object; @@ -32,10 +34,6 @@ pub(crate) struct RuntimeTypeContext<'a> { vm: &'a BexVm, } -#[expect( - dead_code, - reason = "constructed at the runtime subtyping call sites once the flip lands" -)] impl<'a> RuntimeTypeContext<'a> { pub(crate) fn new(vm: &'a BexVm) -> Self { Self { vm } diff --git a/baml_language/crates/bex_vm/src/type_match.rs b/baml_language/crates/bex_vm/src/type_match.rs new file mode 100644 index 00000000000..17dfc729823 --- /dev/null +++ b/baml_language/crates/bex_vm/src/type_match.rs @@ -0,0 +1,432 @@ +//! Runtime `IsType` matching against a [`TyTemplate`], using the canonical type +//! algebra (`baml_type::normalize`) over the running program via +//! [`RuntimeTypeContext`]. +//! +//! This is the value-directed successor to the tag/pointer `IsType` fast paths +//! and the type-directed `guard_template_matches`: given a VM value, a template +//! (which may carry frame references and `Wildcard` holes), and the enclosing +//! frame's realized `type_args`, it answers "is this value a member of the type +//! the template denotes". +//! +//! # Relation +//! +//! The leaf relation is the canonical `baml_type::normalize` algebra — invariant +//! generic arguments, interface-membership- and alias-aware — never the +//! deprecated context-free [`RuntimeTy::is_subtype_of`] fork. It is applied with +//! a [`Variance`]: +//! +//! - **top level** (a value against the arm type) is *covariant* membership +//! (`is_subtype`): a value belongs to `int | string` iff its concrete type is +//! a subtype; +//! - **generic-argument positions** (a list element, a class type-arg) are +//! *invariant* (`equivalent`): `int[]` is not `string[]`, and `Foo` is +//! not `Foo`. +//! +//! `Wildcard` holes match anything at their position, which is why a hole-free +//! subtree short-circuits to a single canonical relation while a subtree +//! containing a hole is walked structurally down to it. The walk descends only +//! into positions whose variance is covariant (top level) or invariant (generic +//! arguments); function types — whose parameters are contravariant and whose +//! subtyping is arity-sensitive — are never hand-walked, so a holey function arm +//! fails closed while a hole-free one is left to the canonical algebra. +//! +//! Dormant until MIR/emit routes structural type tests through +//! `ConstValue::Type` — the sole caller today is a unit-tested `IsType` arm. + +use baml_type::{RuntimeTy, Ty, TyTemplate, normalize}; +use bex_vm_types::Value; + +use crate::{BexVm, type_context::RuntimeTypeContext}; + +/// The variance at a template position the structural walk visits. Only two +/// arise here: +/// +/// - **`Covariant`** — top-level value membership (`is_subtype`): a value +/// belongs to an arm iff its concrete type is a subtype. +/// - **`Invariant`** — a generic-argument position (a list element, a class +/// type-arg): BAML generics are invariant, so `Foo` relates to `Foo` +/// only when `A` and `B` are equivalent. +/// +/// There is deliberately no `Contravariant`. Contravariance arises for function +/// *parameters*, but this walk never descends into a function type (see the +/// `Function` arm of [`template_relates`]) — so no contravariant, nor any +/// variance-*composing*, relation is ever needed. Invariance moreover *absorbs* +/// any incoming variance (invariant ∘ anything = invariant), which is why the +/// generic-argument positions can pass a fixed `Invariant` without composing it +/// with the variance they were reached under. A hole-free function subtree is +/// handed whole to the canonical algebra, which applies parameter contravariance +/// (and the rest of function subtyping) itself. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Variance { + Covariant, + Invariant, +} + +/// Whether `value` is a member of the type denoted by `template`, with the +/// enclosing frame's realized `type_args` resolving the template's frame +/// references. The `IsType` value matcher for `match` and `is` expressions. +pub(crate) fn value_matches_template( + vm: &BexVm, + value: Value, + template: &TyTemplate, + frame_type_args: &[RuntimeTy], +) -> bool { + // A value with no concrete BAML type (function/closure/future/…) is a member + // of no structural type test. + let Some(value_ty) = vm.value_concrete_ty(value) else { + return false; + }; + // The canonical algebra operates over `Ty`; a value's concrete type widens + // into it (a shallow structural conversion — `ConcreteRealizedTy` is not a + // deep, transmute-compatible family member with a borrowed upcast). + let value_ty: Ty = value_ty.into(); + let ctx = RuntimeTypeContext::new(vm); + template_relates( + &ctx, + template, + frame_type_args, + &value_ty, + Variance::Covariant, + ) +} + +/// Whether `actual` relates to the type denoted by `template` (resolved against +/// `frame_type_args`) under `variance`. +/// +/// A hole-free subtree is compared as a whole by the canonical relation +/// (`is_subtype` for covariant, `equivalent` for invariant). A subtree that +/// contains a `Wildcard` is walked structurally so the hole can match anything +/// at its own position while the rest is still related canonically. +fn template_relates( + ctx: &C, + template: &TyTemplate, + frame_type_args: &[RuntimeTy], + actual: &Ty, + variance: Variance, +) -> bool { + if !template.contains_wildcard() { + // Resolve frame references and let the canonical algebra do the work. + let expected = template.substitute(frame_type_args); + return relate(ctx, actual, expected.as_ty(), variance); + } + + // Holey subtree: match the actual's shape structurally, recursing into + // generic-argument positions invariantly, until each `Wildcard` is reached. + match template { + TyTemplate::Wildcard => true, + TyTemplate::List(inner, _) => match actual { + Ty::List(actual_inner, _) => template_relates( + ctx, + inner, + frame_type_args, + actual_inner, + Variance::Invariant, + ), + _ => false, + }, + TyTemplate::Map { key, value, .. } => match actual { + Ty::Map { + key: actual_key, + value: actual_value, + .. + } => { + template_relates(ctx, key, frame_type_args, actual_key, Variance::Invariant) + && template_relates( + ctx, + value, + frame_type_args, + actual_value, + Variance::Invariant, + ) + } + _ => false, + }, + TyTemplate::Class(name, args, _) => match actual { + Ty::Class(actual_name, actual_args, _) => { + name == actual_name + && args.len() == actual_args.len() + && args.iter().zip(actual_args).all(|(arg, actual_arg)| { + template_relates(ctx, arg, frame_type_args, actual_arg, Variance::Invariant) + }) + } + _ => false, + }, + TyTemplate::Future(value, error, _) => match actual { + Ty::Future(actual_value, actual_error, _) => { + template_relates( + ctx, + value, + frame_type_args, + actual_value, + Variance::Invariant, + ) && template_relates( + ctx, + error, + frame_type_args, + actual_error, + Variance::Invariant, + ) + } + _ => false, + }, + TyTemplate::WatchAccessor(inner, _) => match actual { + Ty::WatchAccessor(actual_inner, _) => template_relates( + ctx, + inner, + frame_type_args, + actual_inner, + Variance::Invariant, + ), + _ => false, + }, + // A union at the top level matches if any member matches (covariant); as + // an invariant argument it must match member-for-member, but a + // value's concrete type is never itself a union at an argument position + // that a hole could distinguish, so the any-member rule is sufficient + // here and stays conservative. + TyTemplate::Union(parts, _) => parts + .iter() + .any(|part| template_relates(ctx, part, frame_type_args, actual, variance)), + // A function type's parameters are *contravariant*, and its subtyping is + // subtle: required params match positionally, optional params match by + // name, and return/throws are covariant (see `normalize`'s + // `list_subtype`). Rather than hand-roll that — plus the variance + // composition contravariant positions would force — for the rare holey + // function arm, fail closed: a hole inside a function type matches + // nothing. Hole-free function arms never reach here; they take the fast + // path into the canonical algebra, which applies all of the above. + TyTemplate::Function { .. } => false, + // Interface and projection templates with holes have no structural + // runtime membership check yet (Unit-5 work); a hole there matches + // nothing until then. + TyTemplate::Interface(..) | TyTemplate::AssociatedTypeProjection { .. } => false, + // Frame references and realized leaves contain no `Wildcard`, so the + // hole-free fast path above already handled them; they cannot be the top + // of a holey subtree. + _ => unreachable!("hole-free template handled by the fast path"), + } +} + +/// Apply the canonical relation at `variance`: covariant positions use +/// membership (`is_subtype`), invariant positions use equality (`equivalent`). +fn relate( + ctx: &C, + actual: &Ty, + expected: &Ty, + variance: Variance, +) -> bool { + match variance { + Variance::Covariant => normalize::is_subtype(actual, expected, ctx), + Variance::Invariant => normalize::equivalent(actual, expected, ctx), + } +} + +#[cfg(test)] +mod tests { + use baml_type::{ + Interface, Name, QualifiedTypeName, RealizedTy, RuntimeTy, TyTemplate, TypeName, + normalize::TypeContext, + }; + + use super::{Variance, template_relates}; + + /// A fail-safe, context-free [`TypeContext`]: no aliases, no interface + /// memberships, no bounds. It exercises the matcher's *structural* algebra + /// (invariant generic args, literal widening, union membership, `Wildcard` + /// holes) — the parts that don't need program facts. Nominal facts (a class + /// implementing an interface) are validated by the VM-backed e2e tests. + struct EmptyCtx; + impl TypeContext for EmptyCtx { + fn alias_def(&self, _: &QualifiedTypeName) -> Option { + None + } + fn implements_interface(&self, _: &baml_type::Ty, _: &Interface) -> bool { + false + } + fn type_var_bound(&self, _: &Name) -> Vec { + Vec::new() + } + fn interface_requires(&self, _: &Interface, _: &Interface) -> bool { + false + } + fn enum_variants(&self, _: &QualifiedTypeName) -> Option> { + None + } + } + + /// `template_relates` over the empty context, covariant at the top level. + fn matches(template: &TyTemplate, frame: &[RuntimeTy], actual: &RuntimeTy) -> bool { + template_relates( + &EmptyCtx, + template, + frame, + actual.as_ty(), + Variance::Covariant, + ) + } + + /// A realized-leaf template from a `RealizedTy`. + fn leaf(ty: RealizedTy) -> TyTemplate { + TyTemplate::from(ty) + } + + fn user_class(name: &str) -> TypeName { + TypeName::local(Name::new(name)) + } + + #[test] + fn list_discriminates_element_type() { + let int_list_pat = TyTemplate::list(leaf(RealizedTy::int())); + assert!(matches( + &int_list_pat, + &[], + &RuntimeTy::list(RuntimeTy::int()) + )); + // `int[]` must NOT match a `string[]` value — element position is invariant. + assert!(!matches( + &int_list_pat, + &[], + &RuntimeTy::list(RuntimeTy::string()) + )); + // Nor a bare `int`. + assert!(!matches(&int_list_pat, &[], &RuntimeTy::int())); + } + + #[test] + fn map_discriminates_value_type() { + let m_int = TyTemplate::map(leaf(RealizedTy::string()), leaf(RealizedTy::int())); + assert!(matches( + &m_int, + &[], + &RuntimeTy::map(RuntimeTy::string(), RuntimeTy::int()) + )); + assert!(!matches( + &m_int, + &[], + &RuntimeTy::map(RuntimeTy::string(), RuntimeTy::string()) + )); + } + + #[test] + fn class_type_args_are_invariant() { + let tn = user_class("Foo"); + let foo_int = TyTemplate::class(tn.clone(), vec![leaf(RealizedTy::int())]); + assert!(matches( + &foo_int, + &[], + &RuntimeTy::class_with_args(tn.clone(), vec![RuntimeTy::int()]) + )); + assert!(!matches( + &foo_int, + &[], + &RuntimeTy::class_with_args(tn, vec![RuntimeTy::string()]) + )); + } + + #[test] + fn type_arg_ref_realizes_frame_slot() { + // `T` arm with the frame binding `T = int`: an `int` value matches, a + // `string` value does not. + let t = TyTemplate::TypeArgRef(0); + assert!(matches(&t, &[RuntimeTy::int()], &RuntimeTy::int())); + assert!(!matches(&t, &[RuntimeTy::int()], &RuntimeTy::string())); + } + + #[test] + fn type_arg_ref_membership_is_covariant_at_top_level() { + // `T = int | string`: an `int` value is a *member* of the union arm. + let t = TyTemplate::TypeArgRef(0); + let frame = [RuntimeTy::union([RuntimeTy::int(), RuntimeTy::string()])]; + assert!(matches(&t, &frame, &RuntimeTy::int())); + assert!(matches(&t, &frame, &RuntimeTy::string())); + assert!(!matches(&t, &frame, &RuntimeTy::bool())); + } + + #[test] + fn list_of_type_arg_ref() { + // `T[]` with `T = int`. + let t_list = TyTemplate::list(TyTemplate::TypeArgRef(0)); + assert!(matches( + &t_list, + &[RuntimeTy::int()], + &RuntimeTy::list(RuntimeTy::int()) + )); + assert!(!matches( + &t_list, + &[RuntimeTy::int()], + &RuntimeTy::list(RuntimeTy::string()) + )); + } + + #[test] + fn wildcard_matches_anything() { + let w = TyTemplate::Wildcard; + assert!(matches(&w, &[], &RuntimeTy::int())); + assert!(matches(&w, &[], &RuntimeTy::list(RuntimeTy::string()))); + } + + #[test] + fn class_with_wildcard_arg() { + // `Foo<_>` matches every `Foo` instantiation, but not another class. + let foo = user_class("Foo"); + let bar = user_class("Bar"); + let foo_any = TyTemplate::class(foo.clone(), vec![TyTemplate::Wildcard]); + assert!(matches( + &foo_any, + &[], + &RuntimeTy::class_with_args(foo.clone(), vec![RuntimeTy::int()]) + )); + assert!(matches( + &foo_any, + &[], + &RuntimeTy::class_with_args(foo, vec![RuntimeTy::string()]) + )); + assert!(!matches( + &foo_any, + &[], + &RuntimeTy::class_with_args(bar, vec![RuntimeTy::int()]) + )); + } + + #[test] + fn map_with_wildcard_value() { + // `map` matches any value type but requires a string key. + let m = TyTemplate::map(leaf(RealizedTy::string()), TyTemplate::Wildcard); + assert!(matches( + &m, + &[], + &RuntimeTy::map(RuntimeTy::string(), RuntimeTy::int()) + )); + assert!(matches( + &m, + &[], + &RuntimeTy::map(RuntimeTy::string(), RuntimeTy::list(RuntimeTy::bool())) + )); + assert!(!matches( + &m, + &[], + &RuntimeTy::map(RuntimeTy::int(), RuntimeTy::int()) + )); + } + + #[test] + fn union_top_level_membership() { + // A bare `int | string` arm: an `int` value is a member; a `bool` is not. + let u = TyTemplate::union([leaf(RealizedTy::int()), leaf(RealizedTy::string())]); + assert!(matches(&u, &[], &RuntimeTy::int())); + assert!(matches(&u, &[], &RuntimeTy::string())); + assert!(!matches(&u, &[], &RuntimeTy::bool())); + } + + #[test] + fn literal_widens_into_base() { + // A value of literal type `1` is a member of the `int` arm. + let int_arm = leaf(RealizedTy::int()); + let one = RuntimeTy::Literal( + baml_type::Literal::Int(1), + baml_type::Freshness::Regular, + baml_type::TyAttr::default(), + ); + assert!(matches(&int_arm, &[], &one)); + } +} diff --git a/baml_language/crates/bex_vm/src/vm.rs b/baml_language/crates/bex_vm/src/vm.rs index 8fb5d7173ef..5782f9ea1f3 100644 --- a/baml_language/crates/bex_vm/src/vm.rs +++ b/baml_language/crates/bex_vm/src/vm.rs @@ -43,32 +43,41 @@ fn guard_template_matches( frame_type_args: &[baml_type::RuntimeTy], actual: &baml_type::RuntimeTy, ) -> bool { + use baml_type::{RealizedTy, RuntimeTy, TyTemplate}; + + // A fully-realized template carries no frame refs or holes: compare it to + // the actual arg by exact structural equality (the invariant-position rule + // for reified type args). This is the flattened successor to the old + // `Concrete(expected) => expected == actual` arm. + if let Ok(realized) = <&RealizedTy>::try_from(template) { + return realized.as_runtime_ty() == actual; + } + match template { - baml_type::TyTemplate::Wildcard => true, + TyTemplate::Wildcard => true, #[expect(deprecated)] - baml_type::TyTemplate::TypeArgRefOrWildcard(n) => match frame_type_args.get(*n as usize) { - Some(baml_type::RuntimeTy::BuiltinUnknown { .. }) | None => true, + TyTemplate::TypeArgRefOrWildcard(n) => match frame_type_args.get(*n as usize) { + Some(RuntimeTy::BuiltinUnknown { .. }) | None => true, Some(expected) => actual.is_subtype_of(expected), }, - baml_type::TyTemplate::TypeArgRef(n) => { + TyTemplate::TypeArgRef(n) => { frame_type_args .get(*n as usize) .cloned() - .unwrap_or_else(baml_type::RuntimeTy::unknown) + .unwrap_or_else(RuntimeTy::unknown) == *actual } - baml_type::TyTemplate::Concrete(expected) => expected == actual, - baml_type::TyTemplate::Array(inner) => { - matches!(actual, baml_type::RuntimeTy::List(actual_inner, _) if guard_template_matches(inner, frame_type_args, actual_inner)) + TyTemplate::List(inner, _) => { + matches!(actual, RuntimeTy::List(actual_inner, _) if guard_template_matches(inner, frame_type_args, actual_inner)) } - baml_type::TyTemplate::Map(k, v) => { - matches!(actual, baml_type::RuntimeTy::Map { key, value, .. } if guard_template_matches(k, frame_type_args, key) && guard_template_matches(v, frame_type_args, value)) + TyTemplate::Map { key, value, .. } => { + matches!(actual, RuntimeTy::Map { key: actual_key, value: actual_value, .. } if guard_template_matches(key, frame_type_args, actual_key) && guard_template_matches(value, frame_type_args, actual_value)) } - baml_type::TyTemplate::Class(name, args) => { - matches!(actual, baml_type::RuntimeTy::Class(actual_name, actual_args, _) if name == actual_name && args.len() == actual_args.len() && args.iter().zip(actual_args).all(|(template, actual)| guard_template_matches(template, frame_type_args, actual))) + TyTemplate::Class(name, args, _) => { + matches!(actual, RuntimeTy::Class(actual_name, actual_args, _) if name == actual_name && args.len() == actual_args.len() && args.iter().zip(actual_args).all(|(template, actual)| guard_template_matches(template, frame_type_args, actual))) } - baml_type::TyTemplate::Interface(name, args, assoc) => { - matches!(actual, baml_type::RuntimeTy::Interface(actual_name, actual_args, actual_assoc, _) if name == actual_name + TyTemplate::Interface(name, args, assoc, _) => { + matches!(actual, RuntimeTy::Interface(actual_name, actual_args, actual_assoc, _) if name == actual_name && args.len() == actual_args.len() && args.iter().zip(actual_args).all(|(template, actual)| guard_template_matches(template, frame_type_args, actual)) && assoc.iter().all(|(name, template)| { @@ -78,18 +87,46 @@ fn guard_template_matches( .is_some_and(|(_, actual)| guard_template_matches(template, frame_type_args, actual)) })) } - baml_type::TyTemplate::Union(parts) => match actual { - baml_type::RuntimeTy::Union(actual_parts, _) => { - actual_parts.iter().all(|actual_part| { - parts - .iter() - .any(|part| guard_template_matches(part, frame_type_args, actual_part)) - }) - } + TyTemplate::Union(parts, _) => match actual { + RuntimeTy::Union(actual_parts, _) => actual_parts.iter().all(|actual_part| { + parts + .iter() + .any(|part| guard_template_matches(part, frame_type_args, actual_part)) + }), _ => parts .iter() .any(|part| guard_template_matches(part, frame_type_args, actual)), }, + TyTemplate::Function { + params, + ret, + throws, + .. + } => { + matches!(actual, RuntimeTy::Function { params: actual_params, ret: actual_ret, throws: actual_throws, .. } + if params.len() == actual_params.len() + && params.iter().zip(actual_params).all(|(param, actual_param)| { + param.mode == actual_param.mode + && guard_template_matches(¶m.ty, frame_type_args, &actual_param.ty) + }) + && guard_template_matches(ret, frame_type_args, actual_ret) + && guard_template_matches(throws, frame_type_args, actual_throws)) + } + TyTemplate::Future(value, error, _) => { + matches!(actual, RuntimeTy::Future(actual_value, actual_error, _) + if guard_template_matches(value, frame_type_args, actual_value) + && guard_template_matches(error, frame_type_args, actual_error)) + } + TyTemplate::WatchAccessor(inner, _) => { + matches!(actual, RuntimeTy::WatchAccessor(actual_inner, _) + if guard_template_matches(inner, frame_type_args, actual_inner)) + } + // A symbolic projection whose witness type was not resolved at compile + // time — never a match: a runtime value's concrete type carries no + // unresolved projection for it to equal. + TyTemplate::AssociatedTypeProjection { .. } => false, + // Realized leaves are handled by the fast path above. + _ => false, } } @@ -366,7 +403,7 @@ mod tests { ClassField { name: name.to_string(), field_type: int_ty(), - field_template: TyTemplate::Concrete(int_ty()), + field_template: TyTemplate::from(baml_type::RealizedTy::int()), description: None, alias: None, skip: false, @@ -704,8 +741,10 @@ mod tests { // concrete path (`contains_typevar == false`) and must keep exact // discrimination — unaffected by the B-634 subtype fix. let foo = TypeName::local(Name::new("Foo")); - let foo_int_template = - TyTemplate::Class(foo.clone(), vec![TyTemplate::Concrete(RuntimeTy::int())]); + let foo_int_template = TyTemplate::class( + foo.clone(), + vec![TyTemplate::from(baml_type::RealizedTy::int())], + ); let foo_int_value = RuntimeTy::class_with_args(foo.clone(), vec![RuntimeTy::int()]); let foo_string_value = RuntimeTy::class_with_args(foo, vec![RuntimeTy::string()]); @@ -1245,6 +1284,65 @@ fn value_as_float(value: Value) -> Option { } } +/// Narrow a value's stored argument type (a `RuntimeTy`) to the `RealizedTy` a +/// [`ConcreteRealizedTy`] holds. A value's arguments are realized by +/// construction, so this returns `None` only if a residual type variable leaked +/// in — a bug — which callers propagate up as "no concrete type". +/// +/// [`ConcreteRealizedTy`]: baml_type::ConcreteRealizedTy +fn realized_arg(ty: &baml_type::RuntimeTy) -> Option { + baml_type::RealizedTy::try_from(ty).ok() +} + +/// The [`ConcreteRealizedTy::Function`] denoted by a `Function` object's stored +/// signature, or `None` if a param/return/throws type carries an unresolved +/// generic that does not narrow to a `RealizedTy` (a bug — see [`realized_arg`]). +/// +/// Used to reconstruct the concrete type of a callable value (closure / generic +/// function). The signature erases unresolved generics to `unknown` +/// (`Function::param_types` / `return_type`), so a generic callable reconstructs +/// coarsely — but faithfully to what the runtime object carries. +/// +/// [`ConcreteRealizedTy::Function`]: baml_type::ConcreteRealizedTy::Function +fn function_object_ty(f: &bex_vm_types::types::Function) -> Option { + use baml_type::{ + ConcreteRealizedTy, FunctionParamMode, RealizedFunctionParamTy, RealizedTy, TyAttr, + }; + let params = f + .param_types + .iter() + .enumerate() + .map(|(i, ty)| { + Some(RealizedFunctionParamTy { + name: f + .param_names + .get(i) + .filter(|n| !n.is_empty()) + .map(|n| Name::new(n.as_str())), + ty: realized_arg(ty)?, + mode: if f.param_has_default.get(i).copied().unwrap_or(false) { + FunctionParamMode::Optional + } else { + FunctionParamMode::Required + }, + }) + }) + .collect::>>()?; + // `None` throws == "never throws" == the `void` error type. + let throws = match &f.throws_type { + Some(ty) => realized_arg(ty)?, + None => RealizedTy::Void { + attr: TyAttr::default(), + }, + }; + Some(ConcreteRealizedTy::Function { + params, + ret: Box::new(realized_arg(&f.return_type)?), + throws: Box::new(throws), + attr: TyAttr::default(), + }) +} + /// Get the type tag for any runtime value. /// /// This is a free function to avoid borrow checker issues when called @@ -1906,70 +2004,81 @@ impl BexVm { Type::of(value, |ptr| ObjectType::of(self.get_object(ptr))) } - /// The full concrete `RuntimeTy` of a value — its runtime `Self` for a virtual - /// interface call. Total: every value realizes to exactly one concrete type - /// (the coherence in [`crate::package_baml`] relies on this), so this never - /// returns an "unknown". Primitives map to their kind; an `Instance` carries - /// its `class_type_args` (so `Box` resolves the `Box` impl at `T = int`); - /// an enum `Variant` maps to its enum. - fn value_concrete_runtime_ty(&self, value: Value) -> baml_type::RuntimeTy { - use baml_type::{RuntimeTy, TyAttr}; + /// The value's concrete type as a [`ConcreteRealizedTy`] — the invariant every + /// runtime value's type satisfies (a concrete top with realized arguments, no + /// type variables) made explicit in the type. `None` for a value kind that + /// has no such type (a raw function/future value or an opaque native handle). + /// + /// Primitives construct their leaf directly; an `Instance` narrows its stored + /// `class_type_args` into the argument list (so `Box` resolves the `Box` + /// impl at `T = int`); an enum `Variant` maps to its enum; a container narrows + /// its element/key/value types; a `Cell` is transparent. A value's arguments + /// are realized by construction, so a per-argument narrow (see [`realized_arg`]) + /// fails (→ `None`) only if a residual type variable leaked in — a bug. + /// + /// The interface resolver wants the loose `RuntimeTy`, so the sole such caller + /// widens the result back; the `IsType` value matcher wants the invariant made + /// explicit and uses it directly. + pub(crate) fn value_concrete_ty(&self, value: Value) -> Option { + use baml_type::{ConcreteRealizedTy, TyAttr}; if value.as_int().is_some() { - return RuntimeTy::Int { + return Some(ConcreteRealizedTy::Int { attr: TyAttr::default(), - }; + }); } if value.as_bool().is_some() { - return RuntimeTy::Bool { + return Some(ConcreteRealizedTy::Bool { attr: TyAttr::default(), - }; + }); } if value.is_null() { - return RuntimeTy::Null { + return Some(ConcreteRealizedTy::Null { attr: TyAttr::default(), - }; + }); } - let ptr = value - .as_object_ptr() - .unwrap_or_else(|| unreachable!("value is neither a primitive nor a heap object")); - match self.get_object(ptr) { - Object::Float(_) => RuntimeTy::Float { + Some(match self.get_object(value.as_object_ptr()?) { + Object::Float(_) => ConcreteRealizedTy::Float { attr: TyAttr::default(), }, - Object::Bigint(_) => RuntimeTy::Bigint { + Object::Bigint(_) => ConcreteRealizedTy::Bigint { attr: TyAttr::default(), }, - Object::String(_) => RuntimeTy::String { + Object::String(_) => ConcreteRealizedTy::String { attr: TyAttr::default(), }, - Object::Uint8Array(_) => RuntimeTy::Uint8Array { + Object::Uint8Array(_) => ConcreteRealizedTy::Uint8Array { attr: TyAttr::default(), }, - Object::Instance(inst) => { - let type_args = inst.class_type_args.to_vec(); - match self.get_object(inst.class) { - Object::Class(class) => { - // Media values are `Object::Instance`s of the std media - // classes (`baml.media.{Image,Audio,Video,Pdf}`), but their - // concrete type is the `image`/`audio`/… primitive - // (`RuntimeTy::Media`) — which is how the impl registry keys - // `implement I for image`. Return that, not the class. - if let Some(kind) = crate::package_baml::json::media_kind_from_fqn( - class.name.display_name().as_str(), - ) { - RuntimeTy::Media(kind, TyAttr::default()) - } else { - RuntimeTy::Class(class.name.clone(), type_args, TyAttr::default()) - } + Object::Instance(inst) => match self.get_object(inst.class) { + Object::Class(class) => { + // Media values are `Object::Instance`s of the std media classes + // (`baml.media.{Image,Audio,Video,Pdf}`), but their concrete + // type is the `image`/`audio`/… primitive + // (`ConcreteRealizedTy::Media`) — which is how the impl registry + // keys `implement I for image`. Return that, not the class. + if let Some(kind) = crate::package_baml::json::media_kind_from_fqn( + class.name.display_name().as_str(), + ) { + ConcreteRealizedTy::Media(kind, TyAttr::default()) + } else { + // A generic instance's stored `class_type_args` are realized + // at construction (`Box` ⇒ `T = int`); narrow each into + // the `ConcreteRealizedTy::Class` argument list. + let type_args = inst + .class_type_args + .iter() + .map(realized_arg) + .collect::>>()?; + ConcreteRealizedTy::Class(class.name.clone(), type_args, TyAttr::default()) } - other => unreachable!( - "Instance.class must point to a Class, found {:?}", - ObjectType::of(other) - ), } - } + other => unreachable!( + "Instance.class must point to a Class, found {:?}", + ObjectType::of(other) + ), + }, Object::Variant(v) => match self.get_object(v.enm) { - Object::Enum(e) => RuntimeTy::Enum(e.name.clone(), TyAttr::default()), + Object::Enum(e) => ConcreteRealizedTy::Enum(e.name.clone(), TyAttr::default()), other => unreachable!( "Variant.enm must point to an Enum, found {:?}", ObjectType::of(other) @@ -1977,27 +2086,96 @@ impl BexVm { }, // A `type` value (e.g. `reflect.type_of()`) — its concrete type is // the `type` primitive, the subject of `implement I for type`. - Object::Type(_) => RuntimeTy::Type { + Object::Type(_) => ConcreteRealizedTy::Type { attr: TyAttr::default(), }, // Arrays/maps carry their element/key/value types, so the faithful - // `list` / `map` is reconstructed from the value itself. In - // practice MIR routes container-backed interface dispatch to the - // closed-world type-tag switch (the `iface_may_be_container_backed` - // gate), so a container receiver does not actually reach a virtual - // call (the sole caller) — but should one ever arrive, return its real - // type rather than aborting the VM. - Object::Array(arr) => RuntimeTy::list((*arr.element_ty).clone()), - Object::Map(map) => RuntimeTy::map((*map.key_ty).clone(), (*map.value_ty).clone()), - // Functions/closures/futures are not valid impl subjects, and - // `resource`/`prompt_ast` (also impl subjects) have no runtime value - // representation — none can be an interface-dispatch receiver, so the - // type checker never emits a virtual call on them. - other => unreachable!( - "value of object kind {:?} cannot be a virtual-call receiver", - ObjectType::of(other) + // `list` / `map` is reconstructed from the value itself. + Object::Array(arr) => ConcreteRealizedTy::List( + Box::new(realized_arg(&arr.element_ty)?), + TyAttr::default(), ), - } + Object::Map(map) => ConcreteRealizedTy::Map { + key: Box::new(realized_arg(&map.key_ty)?), + value: Box::new(realized_arg(&map.value_ty)?), + attr: TyAttr::default(), + }, + // A cell is a transparent capture/mutable-binding slot, not a value + // of its own: its concrete type is that of the value it holds. + Object::Cell(cell) => return self.value_concrete_ty(cell.load()), + + // ── Function-pointer values ────────────────────────────────────── + // These are user-facing callables; their concrete type is the + // reconstructed function signature. (The signature stored on the + // underlying `Function` erases unresolved generics to `unknown`, so a + // generic closure reconstructs coarsely — but it is a real function + // type, not "no type".) + Object::Closure(closure) => { + // SAFETY: `closure.function` points to a live `Function`, the + // same invariant `resolve_callable_target` relies on. + match unsafe { closure.function.get() } { + Object::Function(f) => function_object_ty(f)?, + _ => return None, + } + } + Object::GenericFunction(gf) => { + // Resolve the underlying function through the global table, as at + // call time; its `type_args` seed the frame but the stored + // signature is already generic-erased, so reuse it directly. + let inner = self.globals.get(self.proof(), gf.function); + match inner.as_object_ptr().map(|p| self.get_object(p)) { + Some(Object::Function(f)) => function_object_ty(f)?, + _ => return None, + } + } + Object::HostClosure(hc) => ConcreteRealizedTy::Function { + params: hc + .params + .iter() + .map(|p| { + Some(baml_type::RealizedFunctionParamTy { + name: p.name.clone(), + ty: realized_arg(&p.ty)?, + mode: p.mode, + }) + }) + .collect::>>()?, + ret: Box::new(realized_arg(&hc.ret_ty)?), + throws: Box::new(realized_arg(&hc.throws_ty)?), + attr: TyAttr::default(), + }, + // BUG: a `BoundMethod` is a callable value and *should* reconstruct + // to its underlying function's type with the bound `self` parameter + // dropped, but the receiver-relative generic realization needed to do + // that faithfully isn't threaded onto the object — so it reports no + // type for now rather than a wrong one. + Object::BoundMethod(_) => return None, + + // `Object::Function` is NOT a function-pointer value — it is the + // internal function representation that acts as the type constructor + // for the callables above, so like the other compile-time definition + // objects (a package, class, enum, interface, or impl rule) it is + // never a *data value* reaching a type test. + Object::Function(_) + | Object::Package(_) + | Object::Class(_) + | Object::Enum(_) + | Object::Interface(_) + | Object::ImplRule(_) => return None, + + // A `Future` *is* a concrete type, but the value holds only its + // resolved value and state, never its `` type arguments, so a + // precise `Future` can't be reconstructed; `Future` patterns are + // matched by the coarse `FUTURE` type tag instead. + Object::Future(_) | Object::UnscheduledFuture(_) => return None, + + // Opaque native handles are not BAML data types at all. + Object::RustData(_) | Object::Collector(_) => return None, + + // A GC-debug sentinel is never a live value. + #[cfg(feature = "heap_debug")] + Object::Sentinel(_) => return None, + }) } /// Get mutable string from a Value. @@ -3909,6 +4087,26 @@ impl BexVm { callee } + /// The class-level type arguments to curry into a bound method whose + /// receiver is `receiver`: the receiver instance's `class_type_args` (De + /// Bruijn class-param order — the method's `Self`), or empty for a + /// non-instance receiver (a primitive, `type`, `uint8array`, …, which has no + /// class generics). Captured at `MakeBoundMethod` time so the value is fully + /// realized; installed as the callee's `frame.type_args` at `CallIndirect` + /// (see the `Object::BoundMethod` arm of `execute_call_from_locals_offset`). + pub(crate) fn bound_method_curried_type_args( + &self, + receiver: Value, + ) -> Box<[baml_type::RuntimeTy]> { + match receiver.as_object_ptr() { + Some(ptr) => match self.get_object(ptr) { + Object::Instance(inst) => inst.class_type_args.clone(), + _ => Box::new([]), + }, + None => Box::new([]), + } + } + // ── BEX profiling event stream (bex_events::prof) ────────────────── /// The innermost live call's profiling id (`0` = at thread root). The @@ -4362,10 +4560,14 @@ impl BexVm { // case, including all recursion — needs neither closure captures nor // bound-method class args, so it takes the empty fast path. The // `closure_type_args` are a Closure's captured type args; the - // `bound_method_class_type_args` are the receiver's class type args (De - // Bruijn ordering: class args ++ explicit call-site args, matching - // enclosing_generic_params() which puts class params first). Both are - // injected into the new BytecodeFrame after it is created. + // `bound_method_class_type_args` are the bound method's curried type args + // (De Bruijn ordering: class args → `Self` ++ explicit call-site args, + // matching enclosing_generic_params() which puts class params first). + // Curried at `MakeBoundMethod` time (see `bound_method_curried_type_args`) + // so every callable value seeds its frame from its own type-args field — + // the same mechanism as `Closure::captured_type_args` / + // `GenericFunction::type_args`. Both are injected into the new + // BytecodeFrame after it is created. let (is_host, closure_type_args, bound_method_class_type_args): ( bool, Box<[baml_type::RuntimeTy]>, @@ -4373,16 +4575,7 @@ impl BexVm { ) = match self.get_object(callee_ptr) { Object::HostClosure(_) => (true, Box::new([]), Box::new([])), Object::Closure(c) => (false, c.captured_type_args.clone(), Box::new([])), - Object::BoundMethod(bm) => { - let bm_args: Box<[baml_type::RuntimeTy]> = match bm.receiver.as_object_ptr() { - Some(recv_ptr) => match self.get_object(recv_ptr) { - Object::Instance(inst) => inst.class_type_args.clone(), - _ => Box::new([]), - }, - None => Box::new([]), - }; - (false, Box::new([]), bm_args) - } + Object::BoundMethod(bm) => (false, Box::new([]), bm.type_args.clone()), // Plain Function (fast path) and everything else: no extra args. _ => (false, Box::new([]), Box::new([])), }; @@ -6622,7 +6815,16 @@ impl BexVm { // agrees. The rule borrows `self`; scope it so the borrow ends // before the `&mut self` call below. let receiver = self.stack[StackIndex::from_raw(args_offset)]; - let self_ty = self.value_concrete_runtime_ty(receiver); + // The resolver operates on the loose `RuntimeTy`; widen back + // from the value's concrete-realized type. + let self_ty = baml_type::RuntimeTy::from( + self.value_concrete_ty(receiver).unwrap_or_else(|| { + unreachable!( + "value of kind {:?} cannot be a virtual-call receiver", + self.type_of(&receiver) + ) + }), + ); let (callee_ptr, type_args) = { let (rule, bound_args) = crate::package_baml::resolve_implements_rule( self, @@ -7095,6 +7297,25 @@ impl BexVm { // to the pre-resolved Object/Int path. let raw_const = &function.bytecode.constants[const_idx]; let result = match raw_const { + // Structural type test against a full `TyTemplate` (a + // container element type, a bare frame ref, a `Wildcard` + // hole, …), matched with the canonical type algebra. Not + // emitted yet — the MIR/emit routing that produces these + // constants lands with the structural-routing unit. + ConstValue::Type(template) => { + let frame_type_args = + if let Frame::Bytecode(bf) = &self.frames[*frame_idx] { + bf.type_args.clone() + } else { + vec![] + }; + crate::type_match::value_matches_template( + self, + value, + template, + &frame_type_args, + ) + } ConstValue::ClassWithTypeArgs { class_obj, type_args_templates, @@ -7133,10 +7354,18 @@ impl BexVm { } _ => { let expected = &function.bytecode.resolved_constants[const_idx]; - if let Some(class_ptr) = expected.as_object_ptr() { + if let Some(expected_ptr) = expected.as_object_ptr() { + // Class- or enum-pointer identity: `is Foo` checks + // the instance's class object; `is Color` checks the + // variant's enum object. Enum-type tests dispatch on + // enum identity because the shared `ENUM` type tag + // cannot tell `Color` from `Status`. match value.as_object_ptr() { Some(val_ptr) => match self.get_object(val_ptr) { - Object::Instance(instance) => instance.class == class_ptr, + Object::Instance(instance) => { + instance.class == expected_ptr + } + Object::Variant(variant) => variant.enm == expected_ptr, _ => false, }, None => false, @@ -7280,19 +7509,19 @@ impl BexVm { }; let ty = { - let frame_type_args = if let Frame::Bytecode(bf) = &self.frames[*frame_idx] - { - bf.type_args.clone() - } else { - vec![] - }; - if template.is_fully_concrete() { - if let baml_type::TyTemplate::Concrete(t) = &template { - t.clone() - } else { - template.substitute(&frame_type_args) - } + // A fully-realized template narrows to `RealizedTy` (a + // single validation walk) and widens to `RuntimeTy` by + // transmute — no substitution environment needed. + // Otherwise resolve its frame refs. + if let Ok(realized) = <&baml_type::RealizedTy>::try_from(&template) { + baml_type::RuntimeTy::from(realized) } else { + let frame_type_args = + if let Frame::Bytecode(bf) = &self.frames[*frame_idx] { + bf.type_args.clone() + } else { + vec![] + }; template.substitute(&frame_type_args) } }; @@ -7309,9 +7538,17 @@ impl BexVm { let callee_value = self.globals.get(self.proof(), global_idx); let function_ptr = self.as_object_ptr(callee_value, FunctionType::Callable.into())?; + // Curry the receiver's class type args (→ `Self`) into the + // value now, so the bound method is fully realized and the + // `CallIndirect` that invokes it needs no type-arg operands. + // (Method-level fn generics — `b.m` — are not yet + // curried here; that needs turbofish-on-member-access + // support and would append after these.) + let type_args = self.bound_method_curried_type_args(receiver); let bound = Object::BoundMethod(BoundMethod { function: function_ptr, receiver, + type_args, }); let ptr = self.tlab.alloc(bound); self.stack.push(Value::object(ptr)); diff --git a/baml_language/crates/bex_vm/tests/interface_registry.rs b/baml_language/crates/bex_vm/tests/interface_registry.rs index 153561d7100..7dfb4ca97a7 100644 --- a/baml_language/crates/bex_vm/tests/interface_registry.rs +++ b/baml_language/crates/bex_vm/tests/interface_registry.rs @@ -7,7 +7,7 @@ //! winning over the default. use baml_project::testing::compile_source; -use baml_type::{RuntimeTy, TyTemplate}; +use baml_type::TyTemplate; use bex_vm_types::{Object, types::Program}; /// The head type name of a for-type pattern (`Dog` for `Dog`, `Wrap` for @@ -16,8 +16,7 @@ use bex_vm_types::{Object, types::Program}; /// companions, which have a distinct head name) from colliding. fn for_ty_head_name(pat: &TyTemplate) -> Option<&str> { match pat { - TyTemplate::Concrete(RuntimeTy::Class(qtn, ..) | RuntimeTy::Enum(qtn, ..)) - | TyTemplate::Class(qtn, _) => Some(qtn.name().as_str()), + TyTemplate::Class(qtn, ..) | TyTemplate::Enum(qtn, ..) => Some(qtn.name().as_str()), _ => None, } } diff --git a/baml_language/crates/bex_vm/tests/load_type.rs b/baml_language/crates/bex_vm/tests/load_type.rs index 97c1522133b..92d33700514 100644 --- a/baml_language/crates/bex_vm/tests/load_type.rs +++ b/baml_language/crates/bex_vm/tests/load_type.rs @@ -1,7 +1,7 @@ //! Unit tests for the `LoadType` instruction and the `type_args` calling convention. //! //! These tests exercise Phase 3 of the type-reflection implementation: -//! - `TyTemplate::Concrete(...)` → `LoadType` materialises the concrete `RuntimeTy` +//! - `TyTemplate::from(...)` → `LoadType` materialises the concrete `RuntimeTy` //! - `TyTemplate::TypeArgRef(0)` → `LoadType` substitutes from `frame.type_args[0]` //! - Composite templates (e.g. `Array(TypeArgRef(0))`) substitute correctly //! - `Call { ntypeargs }` pops type args from the stack and stores them in the frame @@ -114,11 +114,11 @@ fn run_with_bytecode_keep_vm( // ─── 3.1 & 3.5 ── LoadType with a fully-concrete template ─────────────────── -/// `LoadType(k)` where `k` is a `ConstValue::Type(TyTemplate::Concrete(int))` +/// `LoadType(k)` where `k` is a `ConstValue::Type(TyTemplate::from(int))` /// should push an `Object::Type` whose inner `RuntimeTy` is `RuntimeTy::int()`. #[test] fn load_type_concrete_int() { - let template = TyTemplate::Concrete(RuntimeTy::int()); + let template = TyTemplate::from(baml_type::RealizedTy::int()); let (result, vm) = run_with_bytecode_keep_vm( "user.test_load_int", vec![Instruction::LoadType(0), Instruction::Return], @@ -138,20 +138,24 @@ fn load_type_concrete_int() { } } -/// A `TyTemplate::Concrete(string)` produces a `RuntimeTy::string()` payload distinct -/// from a `TyTemplate::Concrete(int)`, and the resulting heap objects compare +/// A `TyTemplate::from(string)` produces a `RuntimeTy::string()` payload distinct +/// from a `TyTemplate::from(int)`, and the resulting heap objects compare /// unequal under `deep_equals`. #[test] fn load_type_concrete_string_different_from_int() { let (r_int, vm_int) = run_with_bytecode_keep_vm( "user.test_load_int2", vec![Instruction::LoadType(0), Instruction::Return], - vec![ConstValue::Type(TyTemplate::Concrete(RuntimeTy::int()))], + vec![ConstValue::Type(TyTemplate::from( + baml_type::RealizedTy::int(), + ))], ); let (r_str, vm_str) = run_with_bytecode_keep_vm( "user.test_load_str", vec![Instruction::LoadType(0), Instruction::Return], - vec![ConstValue::Type(TyTemplate::Concrete(RuntimeTy::string()))], + vec![ConstValue::Type(TyTemplate::from( + baml_type::RealizedTy::string(), + ))], ); let Some(p_int) = r_int.as_object_ptr() else { @@ -248,7 +252,7 @@ fn load_type_type_arg_ref_substitutes_from_frame() { /// should produce `Object::Type(RuntimeTy::list(int))`. #[test] fn load_type_array_of_type_arg_ref() { - let template = TyTemplate::Array(Box::new(TyTemplate::TypeArgRef(0))); + let template = TyTemplate::list(TyTemplate::TypeArgRef(0)); let fn_name = "user.test_array_typearg"; let mut program = compile_source(STUB_SOURCE); @@ -350,7 +354,9 @@ fn call_ntypeargs_threads_type_arg_into_callee() { }, Instruction::Return, ], - vec![ConstValue::Type(TyTemplate::Concrete(RuntimeTy::string()))], + vec![ConstValue::Type(TyTemplate::from( + baml_type::RealizedTy::string(), + ))], ); let _ = inner_obj_idx; // suppress unused warning diff --git a/baml_language/crates/bex_vm/tests/method_class_type_args.rs b/baml_language/crates/bex_vm/tests/method_class_type_args.rs index 0afd42c5a73..6c4f16dfc7b 100644 --- a/baml_language/crates/bex_vm/tests/method_class_type_args.rs +++ b/baml_language/crates/bex_vm/tests/method_class_type_args.rs @@ -125,7 +125,9 @@ fn alloc_instance_ntypeargs_stores_class_type_args() { }, Instruction::Return, ], - vec![ConstValue::Type(TyTemplate::Concrete(RuntimeTy::int()))], + vec![ConstValue::Type(TyTemplate::from( + baml_type::RealizedTy::int(), + ))], ); let (result, vm) = run_fn(program, fn_name); diff --git a/baml_language/crates/bex_vm_types/src/types/class.rs b/baml_language/crates/bex_vm_types/src/types/class.rs index b28e18e0e2f..b539206abce 100644 --- a/baml_language/crates/bex_vm_types/src/types/class.rs +++ b/baml_language/crates/bex_vm_types/src/types/class.rs @@ -18,7 +18,7 @@ pub struct ClassField { /// generic params (`N` indexes into `Instance::class_type_args`). /// /// Populated by emit using the enclosing class's `generic_params`. For - /// non-generic classes this is `TyTemplate::Concrete(field_type.clone())`. + /// non-generic classes this is a fully-realized template (no `TypeArgRef`). pub field_template: baml_type::TyTemplate, pub description: Option, pub alias: Option, diff --git a/baml_language/crates/bex_vm_types/src/types/function.rs b/baml_language/crates/bex_vm_types/src/types/function.rs index 51749f6f1e7..07157196a0d 100644 --- a/baml_language/crates/bex_vm_types/src/types/function.rs +++ b/baml_language/crates/bex_vm_types/src/types/function.rs @@ -327,12 +327,29 @@ pub struct Closure { /// /// Created by `MakeBoundMethod`. The receiver is inserted as `self` /// at call time by `CallIndirect`. +/// +/// Like every callable value, a bound method is fully realized: its complete +/// type environment is curried in at creation via [`Self::type_args`], so the +/// `CallIndirect` that invokes it carries no type arguments of its own. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct BoundMethod { /// Pointer to the underlying `Object::Function`. pub function: HeapPtr, /// The receiver value (inserted as `self` at call time). pub receiver: Value, + /// The method's curried type arguments, in the callee frame's De Bruijn + /// order (`[class generics (→ Self), method fn generics]`) — the exact + /// vector a direct `receiver.method<…>(…)` call would seed into + /// `frame.type_args`. Materialized at `MakeBoundMethod` time and installed + /// as `frame.type_args` when the value is invoked by `CallIndirect`, so + /// `LoadType(TypeArgRef(N))` / `IsType` inside the body resolve correctly. + /// + /// `RuntimeTy` (not `RealizedTy`) mirrors [`Closure::captured_type_args`] + /// and [`GenericFunction::type_args`]: these positions should never carry a + /// type variable, but the upstream fix that stops typevars leaking into + /// value positions is still in flight, so all three stay `RuntimeTy` and + /// narrow to `RealizedTy` together once it lands. + pub type_args: Box<[baml_type::RuntimeTy]>, } /// A generic function instantiation carrying concrete type arguments.