From b51a5502395e5d52477d1cbde37fb1c5826f2ef1 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Thu, 2 Jul 2026 16:53:03 -0700 Subject: [PATCH 1/7] Make `TyTemplate` part of the type family This prevents drift and allows zero-cost conversions with `RealizedTy`. TODO: `Self` is currently not properly mapped into TemplateTy --- baml_language/crates/baml_base/src/attr.rs | 10 + .../baml_compiler2_emit/src/analysis.rs | 2 +- .../crates/baml_compiler2_emit/src/emit.rs | 197 ++++---- .../crates/baml_compiler2_emit/src/lib.rs | 15 +- .../baml_compiler2_emit/src/pull_semantics.rs | 2 +- .../baml_compiler2_emit/src/stack_carry.rs | 16 +- .../crates/baml_compiler2_mir/src/ir.rs | 12 +- .../crates/baml_compiler2_mir/src/lower.rs | 391 ++++++++++----- .../baml_tests/snapshots/baml_src/csv.snap | 4 +- .../baml_src/inferred_generic_type_args.snap | 6 +- .../baml_tests/snapshots/baml_src/iter.snap | 8 +- .../iter_impl_generics_only.core.snap | 74 +-- ...sts__compiles____baml_std____04_5_mir.snap | 110 ++--- ...s__compiles____baml_std____06_codegen.snap | 104 ++-- ...compiles__llm_image_outputs__04_5_mir.snap | 2 +- ...mpiles__llm_image_outputs__06_codegen.snap | 14 +- ...sts__compiles__patterns_new__04_5_mir.snap | 2 +- ...s__compiles__patterns_new__06_codegen.snap | 2 +- baml_language/crates/baml_type/src/family.rs | 57 ++- baml_language/crates/baml_type/src/lib.rs | 1 - .../crates/baml_type/src/realized_ty.rs | 50 +- .../crates/baml_type/src/template.rs | 449 +++++++++++------- .../crates/baml_type_macros/src/emit.rs | 38 +- .../crates/baml_type_macros/src/parse.rs | 21 +- .../crates/bex_engine/src/conversion.rs | 38 +- baml_language/crates/bex_heap/src/gc.rs | 2 +- baml_language/crates/bex_heap/src/tlab.rs | 4 +- .../crates/bex_vm/src/package_baml/resolve.rs | 168 +++++-- baml_language/crates/bex_vm/src/vm.rs | 113 +++-- .../crates/bex_vm/tests/interface_registry.rs | 5 +- .../crates/bex_vm/tests/load_type.rs | 24 +- .../bex_vm/tests/method_class_type_args.rs | 4 +- .../crates/bex_vm_types/src/types/class.rs | 2 +- 33 files changed, 1235 insertions(+), 712 deletions(-) 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..40cfb93bbd0 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, @@ -1476,12 +1476,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 +3187,87 @@ 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())); + }; + 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()), - ); - } + other => { + // A fully-realized leaf (primitive, enum, alias, literal, …): + // class-pointer identity for a `TypeAlias`, otherwise its type + // tag. This is the flattened successor to the old `Concrete` arm. + 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 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())); + // A structural shape with template holes. Its runtime check + // is still the coarse erased-shape tag today (element/arg- + // aware checks are upcoming work), so a typevar-carrying + // `T[]` / `map<_, T>` / `(int) -> T` pattern keeps exactly the + // semantics its erased form had: any list / map / function. + let tag = match other { + TyTemplate::List(..) => Some(baml_type::typetag::LIST), + TyTemplate::Map { .. } => Some(baml_type::typetag::MAP), + TyTemplate::Function { .. } => Some(baml_type::typetag::FUNCTION), + // Bare frame refs, wildcards, unions, interfaces, + // projections, futures, watch accessors: no representable + // check today — `false`. + _ => None, + }; + if let Some(tag) = tag { + let c = self.add_constant(ConstValue::Int(tag)); + let inst = self.emit(Instruction::IsType(c)); + self.set_operand(inst, OperandMeta::Const(ty_template.to_string())); + } else { + 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 +3403,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..a5346b032a6 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), + ), + // 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(), ), - // 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)), + 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,72 @@ 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)) } - // 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()), + 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), } } @@ -3930,7 +4089,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 +4105,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 +5278,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 +5311,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 +9230,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 +10547,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 +12949,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 { @@ -14364,10 +14521,10 @@ 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); self.emit_is_type_template_branch(scrutinee, ty_template, success, failure); } 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/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..0be919e7309 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 @@ -580,7 +580,7 @@ function iter_impl_generics_only.core.FlatMap$stream.Iterator.ne L35: 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 @@ -593,7 +593,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 +607,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 @@ -847,7 +847,7 @@ function iter_impl_generics_only.core.FlatMap.Iterator.next(self L35: 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 @@ -860,7 +860,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 +874,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 @@ -1114,7 +1114,7 @@ function iter_impl_generics_only.core.Flatten$stream.Iterator.next(se L35: 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 @@ -1125,7 +1125,7 @@ function iter_impl_generics_only.core.Flatten$stream.Iterator.next(se L36: load_var _40 - load_type iter_impl_generics_only.core.Iterable + load_type iter_impl_generics_only.core.Iterable<#0, #2> load_const "iter" virtual_call nargs=1 ntypeargs=0 store_var it @@ -1139,7 +1139,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 @@ -1379,7 +1379,7 @@ function iter_impl_generics_only.core.Flatten.Iterator.next(self: ite L35: 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 @@ -1390,7 +1390,7 @@ function iter_impl_generics_only.core.Flatten.Iterator.next(self: ite L36: load_var _40 - load_type iter_impl_generics_only.core.Iterable + load_type iter_impl_generics_only.core.Iterable<#0, #2> load_const "iter" virtual_call nargs=1 ntypeargs=0 store_var it @@ -1404,7 +1404,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 @@ -1444,7 +1444,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 +1472,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 +1498,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 +1546,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 +1611,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 +1637,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 +1677,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 +1707,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 +1756,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 +1794,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 +1827,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 +2063,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 +2072,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 +2099,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 +2136,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 +2145,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 +2172,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/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..d79e6bbc3ff 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: { @@ -5911,7 +5911,7 @@ fn baml.iter.Flatten.Iterator.next(self: null) -> R | baml.iter.Done { bb42: { _47 = copy _1.0; - _46 = virtual_call next as baml.iter.Iterator(copy _47) -> [bb43]; + _46 = virtual_call next as baml.iter.Iterator(copy _47) -> [bb43]; } bb43: { @@ -5986,7 +5986,7 @@ fn baml.iter.Flatten.Iterator.next(self: null) -> R | baml.iter.Done { } bb57: { - _77 = is_type(copy _49, T[]); + _77 = is_type(copy _49, _[]); branch copy _77 -> [bb61, bb58]; } @@ -6085,7 +6085,7 @@ fn baml.iter.Flatten.Iterator.next(self: null) -> R | baml.iter.Done { bb77: { _42 = copy _2; - _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb78]; + _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb78]; } bb78: { @@ -6129,7 +6129,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 +6150,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 +6221,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: { @@ -6543,7 +6543,7 @@ fn baml.iter.FlatMap.Iterator.next(self: null) -> R | baml.iter.Done { bb42: { _47 = copy _1.0; - _46 = virtual_call next as baml.iter.Iterator(copy _47) -> [bb43]; + _46 = virtual_call next as baml.iter.Iterator(copy _47) -> [bb43]; } bb43: { @@ -6624,7 +6624,7 @@ fn baml.iter.FlatMap.Iterator.next(self: null) -> R | baml.iter.Done { } bb58: { - _80 = is_type(copy _51, T[]); + _80 = is_type(copy _51, _[]); branch copy _80 -> [bb62, bb59]; } @@ -6723,7 +6723,7 @@ fn baml.iter.FlatMap.Iterator.next(self: null) -> R | baml.iter.Done { bb78: { _42 = copy _2; - _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb79]; + _43 = virtual_call next as baml.iter.Iterator(copy _42) -> [bb79]; } bb79: { @@ -6783,12 +6783,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 +6809,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 +6888,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 +6939,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 +6970,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 +7030,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 +7075,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 +10596,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]; } 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..69f9c0b8472 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 @@ -4471,7 +4471,7 @@ 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 @@ -4564,7 +4564,7 @@ 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 @@ -4964,7 +4964,7 @@ 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 @@ -5057,7 +5057,7 @@ 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 @@ -5457,7 +5457,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 _46 @@ -5547,7 +5547,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 @@ -5947,7 +5947,7 @@ function baml.iter.Flatten.Iterator.next(self: baml.iter.Flatten) - L41: 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 @@ -6037,7 +6037,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 +6158,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 +6280,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 +6412,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 +6440,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 +6466,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 +6514,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 +6556,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 +6604,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 +6630,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 +6670,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 +6700,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 +6736,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 +6774,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 +6807,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 +7042,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 +7051,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 +7078,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 +7115,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 +7124,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 +7151,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 +9769,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 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..c606f31ba52 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 @@ -1231,7 +1231,7 @@ fn user.unwrap_typed(cb: ((int) -> T throws never) | ((string) -> T throws never let _6: (string) -> T throws never bb0: { - _2 = is_type(copy _1, (int) -> T throws never); + _2 = is_type(copy _1, (int) -> _ throws never); branch copy _2 -> [bb2, bb1]; } 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..47e5464dc50 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,7 +881,7 @@ 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) -> _ throws never pop_jump_if_false L0 jump L1 diff --git a/baml_language/crates/baml_type/src/family.rs b/baml_language/crates/baml_type/src/family.rs index 32df449a78c..81affb8a78d 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 [`Ty::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..2c1d17229e7 100644 --- a/baml_language/crates/baml_type/src/template.rs +++ b/baml_language/crates/baml_type/src/template.rs @@ -1,206 +1,303 @@ -#![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`], +//! [`TyTemplateFunctionParamTy`]), and the `RealizedTy ⇔ TyTemplate` conversions +//! are generated in [`crate::family`] by the `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 { - match self { - Self::Concrete(_) => true, - #[expect(deprecated)] - 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 - .iter() - .all(|(_, ty)| ty.is_fully_concrete()) - } - // No `TypeArgRef` to substitute, so it needs no environment. - Self::Wildcard => true, - } + <&RealizedTy>::try_from(self).is_ok() + } +} + +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(|p| { - let rendered = p.to_string(); - if matches!(p, TyTemplate::Concrete(ty) if matches!(ty, RuntimeTy::Function { .. })) - { - format!("({rendered})") - } else { - rendered - } + .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| 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 +308,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 +323,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 +332,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 +344,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 +364,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 +373,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 +388,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/package_baml/resolve.rs b/baml_language/crates/bex_vm/src/package_baml/resolve.rs index 87acb02be72..0b174ec2708 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,51 @@ 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.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 +740,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 +813,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 +858,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 +1005,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/vm.rs b/baml_language/crates/bex_vm/src/vm.rs index 8fb5d7173ef..4d7791033e8 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()]); @@ -7280,19 +7319,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) } }; 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, From e619c7d5b7897ec7dab8b148bb72e791b1439195 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Fri, 3 Jul 2026 20:12:49 -0700 Subject: [PATCH 2/7] Type of value and template matching --- baml_language/crates/bex_vm/src/lib.rs | 1 + .../crates/bex_vm/src/type_context.rs | 14 +- baml_language/crates/bex_vm/src/type_match.rs | 476 ++++++++++++++++++ baml_language/crates/bex_vm/src/vm.rs | 291 ++++++++--- 4 files changed, 712 insertions(+), 70 deletions(-) create mode 100644 baml_language/crates/bex_vm/src/type_match.rs 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/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..370f9a408e8 --- /dev/null +++ b/baml_language/crates/bex_vm/src/type_match.rs @@ -0,0 +1,476 @@ +//! 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. +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 !contains_wildcard(template) { + // 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), + } +} + +/// Whether a `Wildcard` hole appears anywhere in the template. A hole-free +/// template resolves to a concrete type by `substitute` and can be compared as +/// a whole; a holey one must be walked structurally. +fn contains_wildcard(template: &TyTemplate) -> bool { + match template { + TyTemplate::Wildcard => true, + TyTemplate::List(inner, _) | TyTemplate::WatchAccessor(inner, _) => { + contains_wildcard(inner) + } + TyTemplate::Map { key, value, .. } | TyTemplate::Future(key, value, _) => { + contains_wildcard(key) || contains_wildcard(value) + } + TyTemplate::Union(parts, _) => parts.iter().any(contains_wildcard), + TyTemplate::Class(_, args, _) => args.iter().any(contains_wildcard), + TyTemplate::Interface(_, args, assoc, _) => { + args.iter().any(contains_wildcard) || assoc.iter().any(|(_, t)| contains_wildcard(t)) + } + TyTemplate::Function { + params, + ret, + throws, + .. + } => { + params.iter().any(|p| contains_wildcard(&p.ty)) + || contains_wildcard(ret) + || contains_wildcard(throws) + } + TyTemplate::AssociatedTypeProjection { + base, interface, .. + } => { + contains_wildcard(base) + || interface.as_ref().is_some_and(|iface| { + iface.generics.iter().any(contains_wildcard) + || iface + .associated_types + .iter() + .any(|(_, t)| contains_wildcard(t)) + }) + } + // Frame references and realized leaves carry no `Wildcard`. + _ => false, + } +} + +#[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 4d7791033e8..453c467de1f 100644 --- a/baml_language/crates/bex_vm/src/vm.rs +++ b/baml_language/crates/bex_vm/src/vm.rs @@ -1284,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 @@ -1945,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) @@ -2016,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. @@ -6661,7 +6800,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, @@ -7134,6 +7282,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, From de54c4ac001ce29760ce4e78a3716e202666d250 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Fri, 3 Jul 2026 21:33:23 -0700 Subject: [PATCH 3/7] Update codegen for list/map match We now emit type-aware code for matching on lists and maps: - If there is only one map type/list type possible, then we match on type tag (any value must have the expected element type) - Otherwise if there are multiple then we have to emit code to check the element types --- .../crates/baml_compiler2_emit/src/emit.rs | 83 ++++++--- .../crates/baml_compiler2_mir/src/lower.rs | 117 ++++++++++++- .../match_container_types.baml | 86 ++++++++++ .../baml_tests/snapshots/baml_src/_root.snap | 3 + .../snapshots/baml_src/json_alias.snap | 4 +- .../baml_src/json_parse_stringify.snap | 4 +- .../baml_src/json_to_from_string.snap | 2 +- .../baml_src/match_container_types.snap | 157 ++++++++++++++++++ .../baml_src/patterns_new_runtime.snap | 68 ++++---- ...s_binding_uses_branch_local_rest_type.snap | 2 +- ...sts__compiles____baml_std____04_5_mir.snap | 2 +- ...s__compiles____baml_std____06_codegen.snap | 2 +- ...s__compiles__patterns_new__06_codegen.snap | 3 +- 13 files changed, 462 insertions(+), 71 deletions(-) create mode 100644 baml_language/crates/baml_tests/baml_src/ns_match_container_types/match_container_types.baml create mode 100644 baml_language/crates/baml_tests/snapshots/baml_src/match_container_types.snap diff --git a/baml_language/crates/baml_compiler2_emit/src/emit.rs b/baml_language/crates/baml_compiler2_emit/src/emit.rs index 40cfb93bbd0..9ba1cdd0aba 100644 --- a/baml_language/crates/baml_compiler2_emit/src/emit.rs +++ b/baml_language/crates/baml_compiler2_emit/src/emit.rs @@ -3193,6 +3193,20 @@ impl PullSink for StackifyCodegen<'_, '_> { 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 ────────────────────────────────────────────────── @@ -3219,10 +3233,55 @@ impl PullSink for StackifyCodegen<'_, '_> { self.set_operand(inst, OperandMeta::Const(format!("{class_name_str}<...>"))); } } + + // ── 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), + + // 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. This is the flattened successor to the old `Concrete` arm. + // tag (this is where `Function` → the coarse `FUNCTION` 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) { @@ -3245,27 +3304,7 @@ impl PullSink for StackifyCodegen<'_, '_> { emit_false(self); } } else { - // A structural shape with template holes. Its runtime check - // is still the coarse erased-shape tag today (element/arg- - // aware checks are upcoming work), so a typevar-carrying - // `T[]` / `map<_, T>` / `(int) -> T` pattern keeps exactly the - // semantics its erased form had: any list / map / function. - let tag = match other { - TyTemplate::List(..) => Some(baml_type::typetag::LIST), - TyTemplate::Map { .. } => Some(baml_type::typetag::MAP), - TyTemplate::Function { .. } => Some(baml_type::typetag::FUNCTION), - // Bare frame refs, wildcards, unions, interfaces, - // projections, futures, watch accessors: no representable - // check today — `false`. - _ => None, - }; - if let Some(tag) = tag { - let c = self.add_constant(ConstValue::Int(tag)); - let inst = self.emit(Instruction::IsType(c)); - self.set_operand(inst, OperandMeta::Const(ty_template.to_string())); - } else { - emit_false(self); - } + emit_false(self); } } } diff --git a/baml_language/crates/baml_compiler2_mir/src/lower.rs b/baml_language/crates/baml_compiler2_mir/src/lower.rs index a5346b032a6..61a16d17609 100644 --- a/baml_language/crates/baml_compiler2_mir/src/lower.rs +++ b/baml_language/crates/baml_compiler2_mir/src/lower.rs @@ -1199,6 +1199,76 @@ pub(crate) fn ty_to_template_from_resolved_ty(ty: &RuntimeTy) -> TyTemplate { } } +/// 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, + } +} + +/// Whether a coarse `LIST`/`MAP` type-tag test for the container arm `arm` is +/// equivalent to the element-discriminating structural test, for values of the +/// scrutinee's resolved static type `scrutinee`. +/// +/// True when every same-shape (list or map) member of the scrutinee's flattened +/// static type shares `arm`'s constructor identity — so no differently-shaped +/// container value can reach this arm for the coarse tag to conflate — or when +/// `arm`'s element is the top type (`unknown`), which every container already +/// satisfies. A recursive alias member (kept unexpanded by `convert`, e.g. +/// `json`) is not list/map-shaped, so it contributes no same-shape member: a +/// pure-`json` scrutinee is (correctly) tag-sufficient, since on it a coarse +/// "any list" tag *is* the `json[]` test. +/// +/// 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 { + // A top (`unknown`) element already means "any list" / "any map", so the + // coarse tag is exact regardless of the scrutinee. + RuntimeTy::List(elem, _) if matches!(**elem, RuntimeTy::BuiltinUnknown { .. }) => { + return true; + } + RuntimeTy::Map { value, .. } if matches!(**value, RuntimeTy::BuiltinUnknown { .. }) => { + return true; + } + 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 { + 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, + // A different-shape container, or an alias / opaque leaf, cannot be + // confused with this arm's tag, so it does not block tag-sufficiency. + _ => true, + }) +} + // ─── def_to_item_ref helper ────────────────────────────────────────────────── use baml_compiler2_hir::{ @@ -1878,6 +1948,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, @@ -2947,6 +3026,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, @@ -3040,6 +3120,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, @@ -13907,20 +13988,27 @@ 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 { + self.lower_match_chain(scrutinee_local, &arms, dest, bb_join, is_exhaustive); } - self.lower_match_chain(scrutinee_local, &arms, dest, bb_join, is_exhaustive); - + self.match_scrutinee = saved_scrutinee; self.builder.set_current_block(bb_join); } @@ -14526,6 +14614,23 @@ impl LoweringContext<'_> { // 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); } } 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..afb596fcc9c --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_match_container_types/match_container_types.baml @@ -0,0 +1,86 @@ +// 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(baml.deep_equals(string_list_is_int_array_fn(), false)) +} + +// ─── 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") +} 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..cd694e861fd 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,9 @@ function $init_test(registry: unknown) -> null { 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/json_alias.snap b/baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap index 88a31a48481..5873a3d8c8a 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 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..11b4df112f8 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/baml_src/match_container_types.snap @@ -0,0 +1,157 @@ +--- +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_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_const "strings" + jump L2 + + L1: + load_const "ints" + + L2: + return +} + +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_const "string values" + jump L2 + + L1: + load_const "int values" + + L2: + return +} + +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.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.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 +} 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/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 d79e6bbc3ff..c254a28cb70 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 @@ -14450,7 +14450,7 @@ fn baml.toml.table_from_json(j: baml.json.json) -> baml.toml.Table { let _48: baml.json.JsonDecodeError bb0: { - _2 = is_type(copy _1, map); + _2 = is_type(copy _1, map<_, _>); branch copy _2 -> [bb2, bb1]; } 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 69f9c0b8472..8d9d251d0df 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 @@ -13212,7 +13212,7 @@ function baml.toml.item_to_json(item: baml.toml.Item) -> baml.json.json { function baml.toml.table_from_json(j: baml.json.json) -> baml.toml.Table { 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/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 47e5464dc50..c96bf24d940 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,7 +881,8 @@ 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) -> _ throws never + pop 1 + load_const false pop_jump_if_false L0 jump L1 From 7a4da1c2dee595c3d98ef0b41b85c6b5f2f2f328 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Mon, 6 Jul 2026 10:42:35 -0700 Subject: [PATCH 4/7] Invariance in matching on map/list elements --- .../crates/baml_compiler2_emit/src/emit.rs | 26 +- .../crates/baml_compiler2_mir/src/lower.rs | 282 +++++++- .../generic_match_subtype.baml | 43 ++ .../generic_match_typevar.baml | 144 ++++ .../match_container_types.baml | 145 ++++- .../baml_tests/snapshots/baml_src/_root.snap | 3 + .../baml_src/generic_match_subtype.snap | 87 +++ .../baml_src/generic_match_typevar.snap | 250 +++++++ .../iter_impl_generics_only.core.snap | 44 +- .../baml_src/match_container_types.snap | 282 ++++++++ ...sts__compiles____baml_std____04_5_mir.snap | 616 +++++++++--------- ...s__compiles____baml_std____06_codegen.snap | 156 +++-- ...sts__compiles__patterns_new__04_5_mir.snap | 28 +- ...s__compiles__patterns_new__06_codegen.snap | 9 +- .../crates/baml_type/src/template.rs | 67 ++ baml_language/crates/bex_vm/src/type_match.rs | 46 +- 16 files changed, 1753 insertions(+), 475 deletions(-) create mode 100644 baml_language/crates/baml_tests/baml_src/ns_generic_match_typevar/generic_match_typevar.baml create mode 100644 baml_language/crates/baml_tests/snapshots/baml_src/generic_match_typevar.snap diff --git a/baml_language/crates/baml_compiler2_emit/src/emit.rs b/baml_language/crates/baml_compiler2_emit/src/emit.rs index 9ba1cdd0aba..dbb86e88ad6 100644 --- a/baml_language/crates/baml_compiler2_emit/src/emit.rs +++ b/baml_language/crates/baml_compiler2_emit/src/emit.rs @@ -3269,6 +3269,26 @@ impl PullSink for StackifyCodegen<'_, '_> { | 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 @@ -3279,9 +3299,9 @@ impl PullSink for StackifyCodegen<'_, '_> { other => { // A fully-realized leaf (primitive, enum, alias, literal, …): // class-pointer identity for a `TypeAlias`, otherwise its type - // tag (this is where `Function` → the coarse `FUNCTION` tag). The - // only non-realized template reaching here is an associated - // projection (Unit-5 work), which has no representable check yet. + // 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) { diff --git a/baml_language/crates/baml_compiler2_mir/src/lower.rs b/baml_language/crates/baml_compiler2_mir/src/lower.rs index 61a16d17609..930fe877696 100644 --- a/baml_language/crates/baml_compiler2_mir/src/lower.rs +++ b/baml_language/crates/baml_compiler2_mir/src/lower.rs @@ -1233,26 +1233,24 @@ fn wildcard_container_elements(template: TyTemplate) -> TyTemplate { /// scrutinee's resolved static type `scrutinee`. /// /// True when every same-shape (list or map) member of the scrutinee's flattened -/// static type shares `arm`'s constructor identity — so no differently-shaped -/// container value can reach this arm for the coarse tag to conflate — or when -/// `arm`'s element is the top type (`unknown`), which every container already -/// satisfies. A recursive alias member (kept unexpanded by `convert`, e.g. -/// `json`) is not list/map-shaped, so it contributes no same-shape member: a -/// pure-`json` scrutinee is (correctly) tag-sufficient, since on it a coarse -/// "any list" tag *is* the `json[]` test. +/// static type shares `arm`'s constructor identity — so no differently-typed +/// container value can reach this arm for the coarse tag to conflate. +/// +/// Invariance applies all the way to the top type: `unknown[]` is NOT "any +/// list" — it denotes lists whose element type is `unknown` — so a top-typed +/// element earns no shortcut and must be proven by scrutinee identity like any +/// other. (The match-anything container position is the template `Wildcard` +/// hole, produced by compiler erasure — never the `unknown` *type*.) +/// +/// A recursive alias member (kept unexpanded by `convert`, e.g. `json`) is not +/// list/map-shaped, so it contributes no same-shape member: a pure-`json` +/// scrutinee is (correctly) tag-sufficient, since on it a coarse "any list" +/// tag *is* the `json[]` test. /// /// 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 { - // A top (`unknown`) element already means "any list" / "any map", so the - // coarse tag is exact regardless of the scrutinee. - RuntimeTy::List(elem, _) if matches!(**elem, RuntimeTy::BuiltinUnknown { .. }) => { - return true; - } - RuntimeTy::Map { value, .. } if matches!(**value, RuntimeTy::BuiltinUnknown { .. }) => { - return true; - } RuntimeTy::List(..) => true, RuntimeTy::Map { .. } => false, _ => return false, @@ -1269,6 +1267,64 @@ fn container_arm_tag_sufficient(arm: &RuntimeTy, scrutinee: &RuntimeTy) -> bool }) } +/// 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)) + } + 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. + RuntimeTy::TypeAlias(..) + | RuntimeTy::BuiltinUnknown { .. } + | RuntimeTy::TypeVar(..) + | RuntimeTy::Interface(..) + | RuntimeTy::AssociatedTypeProjection { .. } => false, + _ => true, + }) + } + _ => true, + } +} + // ─── def_to_item_ref helper ────────────────────────────────────────────────── use baml_compiler2_hir::{ @@ -14005,7 +14061,24 @@ impl LoweringContext<'_> { None, ); if !switched { - self.lower_match_chain(scrutinee_local, &arms, dest, bb_join, is_exhaustive); + // 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.match_scrutinee = saved_scrutinee; @@ -14042,6 +14115,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; @@ -14063,7 +14146,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) { @@ -14152,7 +14235,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) { @@ -14454,6 +14538,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 @@ -14475,6 +14560,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); @@ -14527,7 +14634,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; } @@ -14555,7 +14662,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 @@ -14930,6 +15083,15 @@ impl LoweringContext<'_> { RuntimeTy::Null { .. } => Some(baml_type::typetag::NULL), RuntimeTy::Float { .. } => Some(baml_type::typetag::FLOAT), RuntimeTy::Uint8Array { .. } => Some(baml_type::typetag::UINT8ARRAY), + // BUG: the single shared ENUM tag conflates *all* enum types (and + // all variants of one enum), so a TypeTag switch over e.g. + // `Color | Status | int | string` with `let c: Color` / `let s: + // Status` bind arms dedups the second enum arm away and every enum + // value jumps to the first — the same silent wrong-arm mechanism + // the container/class tag-sufficiency gate now catches. Enum arms + // need their own sufficiency criterion (same enum name for a bare + // enum arm; exact variant identity for a variant-typed bind arm) + // before they can be trusted on the switch path. RuntimeTy::Enum(..) | RuntimeTy::EnumVariant(..) => Some(baml_type::typetag::ENUM), RuntimeTy::List(..) => Some(baml_type::typetag::LIST), RuntimeTy::Map { .. } => Some(baml_type::typetag::MAP), @@ -15343,12 +15505,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 @@ -15365,8 +15532,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( @@ -15868,12 +16045,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; @@ -15910,30 +16096,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 } @@ -15941,6 +16130,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_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 index afb596fcc9c..2a6fc43738e 100644 --- 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 @@ -55,7 +55,7 @@ function string_list_is_int_array_fn() -> bool { } test "is_string_list_is_not_int_array" { - assert.is_true(baml.deep_equals(string_list_is_int_array_fn(), false)) + assert.is_true(!string_list_is_int_array_fn()) } // ─── 3. map value-type discrimination via `match` ───────────────────────────── @@ -84,3 +84,146 @@ function classify_string_map_fn() -> string { 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/snapshots/baml_src/_root.snap b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap index cd694e861fd..9421895da8b 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap @@ -222,6 +222,9 @@ function $init_test(registry: unknown) -> null { 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/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/iter_impl_generics_only.core.snap b/baml_language/crates/baml_tests/snapshots/baml_src/iter_impl_generics_only.core.snap index 0be919e7309..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 @@ -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<#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 @@ -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<#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 @@ -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,19 +1124,22 @@ 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, #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_var _41 load_type iter_impl_generics_only.core.Iterable<#0, #2> load_const "iter" virtual_call nargs=1 ntypeargs=0 @@ -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,19 +1395,22 @@ 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, #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_var _41 load_type iter_impl_generics_only.core.Iterable<#0, #2> load_const "iter" virtual_call nargs=1 ntypeargs=0 @@ -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 { 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 index 11b4df112f8..160503290bc 100644 --- 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 @@ -38,6 +38,60 @@ function match_container_types.$init_test_ns_match_container_types_match_contain 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 } @@ -69,6 +123,9 @@ function match_container_types.classify_list(x: int[] | string[]) -> string { jump L1 L0: + load_var x + is_type string[] + pop_jump_if_false L3 load_const "strings" jump L2 @@ -77,6 +134,9 @@ function match_container_types.classify_list(x: int[] | string[]) -> string { L2: return + + L3: + unreachable } function match_container_types.classify_map(x: map | map) -> string { @@ -86,6 +146,9 @@ function match_container_types.classify_map(x: map | map + pop_jump_if_false L3 load_const "string values" jump L2 @@ -94,6 +157,9 @@ function match_container_types.classify_map(x: map | map string { @@ -115,6 +181,146 @@ function match_container_types.classify_string_map_fn() -> string { 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 @@ -136,6 +342,35 @@ function match_container_types.int_list_is_int_array_fn() -> bool { 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" @@ -155,3 +390,50 @@ function match_container_types.string_list_is_int_array_fn() -> bool { 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/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 c254a28cb70..a30c2fc5ebb 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 @@ -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, _[]); - 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; } @@ -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, _[]); - 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; } 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 8d9d251d0df..206e61301f9 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 @@ -4469,13 +4469,16 @@ function baml.iter.FlatMap$stream.Iterator.next(self: baml.iter.FlatMap$stream 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 + 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_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 + 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_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 @@ -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_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 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 c606f31ba52..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) -> _ 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 c96bf24d940..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,12 +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 - pop 1 - load_const false + 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 @@ -899,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_type/src/template.rs b/baml_language/crates/baml_type/src/template.rs index 2c1d17229e7..52640f508c3 100644 --- a/baml_language/crates/baml_type/src/template.rs +++ b/baml_language/crates/baml_type/src/template.rs @@ -177,6 +177,73 @@ impl TyTemplate { 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::Wildcard => true, + Self::TypeArgRef(_) | Self::TypeArgRefOrWildcard(_) => false, + 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() + .any(|(_, ty)| ty.contains_wildcard()) + } + 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 { diff --git a/baml_language/crates/bex_vm/src/type_match.rs b/baml_language/crates/bex_vm/src/type_match.rs index 370f9a408e8..0139061054e 100644 --- a/baml_language/crates/bex_vm/src/type_match.rs +++ b/baml_language/crates/bex_vm/src/type_match.rs @@ -104,7 +104,7 @@ fn template_relates( actual: &Ty, variance: Variance, ) -> bool { - if !contains_wildcard(template) { + 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); @@ -221,50 +221,6 @@ fn relate( } } -/// Whether a `Wildcard` hole appears anywhere in the template. A hole-free -/// template resolves to a concrete type by `substitute` and can be compared as -/// a whole; a holey one must be walked structurally. -fn contains_wildcard(template: &TyTemplate) -> bool { - match template { - TyTemplate::Wildcard => true, - TyTemplate::List(inner, _) | TyTemplate::WatchAccessor(inner, _) => { - contains_wildcard(inner) - } - TyTemplate::Map { key, value, .. } | TyTemplate::Future(key, value, _) => { - contains_wildcard(key) || contains_wildcard(value) - } - TyTemplate::Union(parts, _) => parts.iter().any(contains_wildcard), - TyTemplate::Class(_, args, _) => args.iter().any(contains_wildcard), - TyTemplate::Interface(_, args, assoc, _) => { - args.iter().any(contains_wildcard) || assoc.iter().any(|(_, t)| contains_wildcard(t)) - } - TyTemplate::Function { - params, - ret, - throws, - .. - } => { - params.iter().any(|p| contains_wildcard(&p.ty)) - || contains_wildcard(ret) - || contains_wildcard(throws) - } - TyTemplate::AssociatedTypeProjection { - base, interface, .. - } => { - contains_wildcard(base) - || interface.as_ref().is_some_and(|iface| { - iface.generics.iter().any(contains_wildcard) - || iface - .associated_types - .iter() - .any(|(_, t)| contains_wildcard(t)) - }) - } - // Frame references and realized leaves carry no `Wildcard`. - _ => false, - } -} - #[cfg(test)] mod tests { use baml_type::{ From c1e22a24efd60568b96e944b852425aa44a35ea0 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Mon, 6 Jul 2026 12:08:13 -0700 Subject: [PATCH 5/7] Compiler error for `Self` in invalid positions This hardens the preexisting behavior so it either works or gives a compiler error instead of unexpected runtime failures. At a later date ([B-687](https://linear.app/boundaryml2/issue/B-687/support-self-everywhere-like-rust)) we should expand support beyond the current narrow usage. --- .../crates/baml_compiler2_tir/src/builder.rs | 46 ++ .../baml_compiler2_tir/src/infer_context.rs | 19 + .../baml_compiler2_tir/src/lower_type_expr.rs | 62 +++ .../crates/baml_lsp2_actions/src/check.rs | 1 + .../match_self_workaround.baml | 70 +++ .../diagnostic_errors/self_in_body/main.baml | 54 +++ .../baml_tests/snapshots/baml_src/_root.snap | 3 + .../baml_src/match_self_workaround.snap | 99 ++++ ..._errors__self_in_body__01_lexer__main.snap | 444 ++++++++++++++++++ ...errors__self_in_body__02_parser__main.snap | 385 +++++++++++++++ ...agnostic_errors__self_in_body__03_hir.snap | 31 ++ ...agnostic_errors__self_in_body__04_tir.snap | 70 +++ ..._errors__self_in_body__05_diagnostics.snap | 83 ++++ ...ors__self_in_body__10_formatter__main.snap | 57 +++ 14 files changed, 1424 insertions(+) create mode 100644 baml_language/crates/baml_tests/baml_src/ns_match_self_workaround/match_self_workaround.baml create mode 100644 baml_language/crates/baml_tests/projects/diagnostic_errors/self_in_body/main.baml create mode 100644 baml_language/crates/baml_tests/snapshots/baml_src/match_self_workaround.snap create mode 100644 baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__01_lexer__main.snap create mode 100644 baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__02_parser__main.snap create mode 100644 baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__03_hir.snap create mode 100644 baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__04_tir.snap create mode 100644 baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__05_diagnostics.snap create mode 100644 baml_language/crates/baml_tests/snapshots/diagnostic_errors/self_in_body/baml_tests__diagnostic_errors__self_in_body__10_formatter__main.snap 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..2e401d54181 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_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/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 9421895da8b..33351af6904 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap @@ -225,6 +225,9 @@ function $init_test(registry: unknown) -> null { 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/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/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 + } +} From 2daf047d73757f403eeb99dfb24adb056a156e02 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Mon, 6 Jul 2026 14:54:07 -0700 Subject: [PATCH 6/7] Type args on `Object::BoundMethod` `BoundMethod` should carry its type args. Previously we extracted class generic args off the receiver, but this is insufficient as it silently dropped the method's generic args. We now produce the full type arg frame when the bound method is created, similar to `GenericFunction` (the function-as-value object) TODO: actually use the type args --- .../crates/bex_vm/src/package_baml/mod.rs | 12 +++++ baml_language/crates/bex_vm/src/vm.rs | 51 ++++++++++++++----- .../crates/bex_vm_types/src/types/function.rs | 17 +++++++ 3 files changed, 66 insertions(+), 14 deletions(-) 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/vm.rs b/baml_language/crates/bex_vm/src/vm.rs index 453c467de1f..417a182b412 100644 --- a/baml_language/crates/bex_vm/src/vm.rs +++ b/baml_language/crates/bex_vm/src/vm.rs @@ -4087,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 @@ -4540,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]>, @@ -4551,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([])), }; @@ -7515,9 +7530,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_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. From af33397f339946f33e55a133833b65c7c7a57255 Mon Sep 17 00:00:00 2001 From: 2kai2kai2 Date: Tue, 7 Jul 2026 11:05:06 -0700 Subject: [PATCH 7/7] Fix issue where type tags were still being used when ambiguous --- .../crates/baml_compiler2_emit/src/emit.rs | 34 + .../crates/baml_compiler2_mir/src/lower.rs | 145 +++- .../baml_compiler2_tir/src/lower_type_expr.rs | 2 +- .../ns_match_enum_types/match_enum_types.baml | 79 ++ .../baml_tests/baml_src/ns_toml/toml.baml | 7 +- .../baml_tests/snapshots/baml_src/_root.snap | 3 + .../snapshots/baml_src/json_alias.snap | 69 +- .../snapshots/baml_src/match_enum_types.snap | 186 +++++ .../baml_tests/snapshots/baml_src/toml.snap | 2 +- ...sts__compiles____baml_std____04_5_mir.snap | 558 ++++++++------ ...s__compiles____baml_std____06_codegen.snap | 694 ++++++++++-------- ..._compiles__json_alias_basic__04_5_mir.snap | 72 +- ...ompiles__json_alias_basic__06_codegen.snap | 69 +- baml_language/crates/baml_type/src/family.rs | 2 +- .../crates/baml_type/src/template.rs | 5 +- .../crates/bex_vm/src/package_baml/resolve.rs | 9 +- baml_language/crates/bex_vm/src/type_match.rs | 2 +- baml_language/crates/bex_vm/src/vm.rs | 12 +- 18 files changed, 1341 insertions(+), 609 deletions(-) create mode 100644 baml_language/crates/baml_tests/baml_src/ns_match_enum_types/match_enum_types.baml create mode 100644 baml_language/crates/baml_tests/snapshots/baml_src/match_enum_types.snap diff --git a/baml_language/crates/baml_compiler2_emit/src/emit.rs b/baml_language/crates/baml_compiler2_emit/src/emit.rs index dbb86e88ad6..3efc0ce23d4 100644 --- a/baml_language/crates/baml_compiler2_emit/src/emit.rs +++ b/baml_language/crates/baml_compiler2_emit/src/emit.rs @@ -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 { @@ -3316,6 +3333,23 @@ impl PullSink for StackifyCodegen<'_, '_> { } 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)); diff --git a/baml_language/crates/baml_compiler2_mir/src/lower.rs b/baml_language/crates/baml_compiler2_mir/src/lower.rs index 930fe877696..caef39b1860 100644 --- a/baml_language/crates/baml_compiler2_mir/src/lower.rs +++ b/baml_language/crates/baml_compiler2_mir/src/lower.rs @@ -1228,24 +1228,72 @@ fn wildcard_container_elements(template: TyTemplate) -> TyTemplate { } } -/// Whether a coarse `LIST`/`MAP` type-tag test for the container arm `arm` is -/// equivalent to the element-discriminating structural test, for values of the -/// scrutinee's resolved static type `scrutinee`. +/// 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. /// -/// True when every same-shape (list or map) member of the scrutinee's flattened -/// static type shares `arm`'s constructor identity — so no differently-typed -/// container value can reach this arm for the coarse tag to conflate. +/// 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`. /// -/// Invariance applies all the way to the top type: `unknown[]` is NOT "any -/// list" — it denotes lists whose element type is `unknown` — so a top-typed -/// element earns no shortcut and must be proven by scrutinee identity like any -/// other. (The match-anything container position is the template `Wildcard` -/// hole, produced by compiler erasure — never the `unknown` *type*.) +/// 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. /// -/// A recursive alias member (kept unexpanded by `convert`, e.g. `json`) is not -/// list/map-shaped, so it contributes no same-shape member: a pure-`json` -/// scrutinee is (correctly) tag-sufficient, since on it a coarse "any list" -/// tag *is* the `json[]` test. +/// 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 { @@ -1259,11 +1307,13 @@ fn container_arm_tag_sufficient(arm: &RuntimeTy, scrutinee: &RuntimeTy) -> bool 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, - // A different-shape container, or an alias / opaque leaf, cannot be - // confused with this arm's tag, so it does not block tag-sufficiency. - _ => true, + // 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), }) } @@ -1295,6 +1345,31 @@ fn switch_member_tag_sufficient(member: &RuntimeTy, scrutinee: Option<&RuntimeTy 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; @@ -1313,18 +1388,24 @@ fn switch_member_tag_sufficient(member: &RuntimeTy, scrutinee: Option<&RuntimeTy RuntimeTy::Class(..) => true, // An opaque member may admit another instantiation of this // class at runtime; the proof fails closed. - RuntimeTy::TypeAlias(..) - | RuntimeTy::BuiltinUnknown { .. } - | RuntimeTy::TypeVar(..) - | RuntimeTy::Interface(..) - | RuntimeTy::AssociatedTypeProjection { .. } => false, - _ => true, + _ => !member_is_opaque_for_tag_proof(m), }) } _ => 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, + } +} + // ─── def_to_item_ref helper ────────────────────────────────────────────────── use baml_compiler2_hir::{ @@ -15083,15 +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), - // BUG: the single shared ENUM tag conflates *all* enum types (and - // all variants of one enum), so a TypeTag switch over e.g. - // `Color | Status | int | string` with `let c: Color` / `let s: - // Status` bind arms dedups the second enum arm away and every enum - // value jumps to the first — the same silent wrong-arm mechanism - // the container/class tag-sufficiency gate now catches. Enum arms - // need their own sufficiency criterion (same enum name for a bare - // enum arm; exact variant identity for a variant-typed bind arm) - // before they can be trusted on the switch path. + // 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), 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 2e401d54181..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 @@ -249,7 +249,7 @@ fn substitute_paths_walk( /// 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 +/// 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 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_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/snapshots/baml_src/_root.snap b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap index 33351af6904..df6353465c7 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/_root.snap @@ -228,6 +228,9 @@ function $init_test(registry: unknown) -> null { 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/json_alias.snap b/baml_language/crates/baml_tests/snapshots/baml_src/json_alias.snap index 5873a3d8c8a..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 @@ -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: 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/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/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/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 a30c2fc5ebb..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 @@ -13719,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; } } @@ -13764,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 @@ -13777,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 @@ -13828,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; } } @@ -14470,7 +14590,7 @@ fn baml.toml.table_from_json(j: baml.json.json) -> baml.toml.Table { let _48: baml.json.JsonDecodeError bb0: { - _2 = is_type(copy _1, map<_, _>); + _2 = is_type(copy _1, map); branch copy _2 -> [bb2, bb1]; } 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 206e61301f9..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 @@ -12519,712 +12519,822 @@ 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 + + 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 - L0: list + 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 + 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 } function baml.toml.table_from_json(j: baml.json.json) -> baml.toml.Table { 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/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_type/src/family.rs b/baml_language/crates/baml_type/src/family.rs index 81affb8a78d..e6ae4ab6e82 100644 --- a/baml_language/crates/baml_type/src/family.rs +++ b/baml_language/crates/baml_type/src/family.rs @@ -318,7 +318,7 @@ ty_family! { /// name-based `TypeVar`. #[axis(template)] TypeArgRef(u32), - /// De Bruijn reference like [`Ty::TypeArgRef`], but a dispatch-guard hole: + /// 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. diff --git a/baml_language/crates/baml_type/src/template.rs b/baml_language/crates/baml_type/src/template.rs index 52640f508c3..1af2a360627 100644 --- a/baml_language/crates/baml_type/src/template.rs +++ b/baml_language/crates/baml_type/src/template.rs @@ -2,8 +2,9 @@ //! may contain unresolved generic references. //! //! The enum itself, its satellites ([`TyTemplateInterface`], -//! [`TyTemplateFunctionParamTy`]), and the `RealizedTy ⇔ TyTemplate` conversions -//! are generated in [`crate::family`] by the `ty_family!` macro. `TyTemplate` +//! [`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`] 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 0b174ec2708..b36e6849a5f 100644 --- a/baml_language/crates/bex_vm/src/package_baml/resolve.rs +++ b/baml_language/crates/bex_vm/src/package_baml/resolve.rs @@ -438,10 +438,11 @@ fn match_template( .. } => { params.len() == cparams.len() - && params - .iter() - .zip(cparams) - .all(|(p, cp)| p.mode == cp.mode && match_template(&p.ty, &cp.ty, bindings)) + && 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) } diff --git a/baml_language/crates/bex_vm/src/type_match.rs b/baml_language/crates/bex_vm/src/type_match.rs index 0139061054e..17dfc729823 100644 --- a/baml_language/crates/bex_vm/src/type_match.rs +++ b/baml_language/crates/bex_vm/src/type_match.rs @@ -64,7 +64,7 @@ enum Variance { /// 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. +/// references. The `IsType` value matcher for `match` and `is` expressions. pub(crate) fn value_matches_template( vm: &BexVm, value: Value, diff --git a/baml_language/crates/bex_vm/src/vm.rs b/baml_language/crates/bex_vm/src/vm.rs index 417a182b412..5782f9ea1f3 100644 --- a/baml_language/crates/bex_vm/src/vm.rs +++ b/baml_language/crates/bex_vm/src/vm.rs @@ -7354,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,