Skip to content

Commit 577a3d4

Browse files
committed
Coderabbit fixes
1 parent bff1a1e commit 577a3d4

33 files changed

Lines changed: 881 additions & 906 deletions

File tree

baml_language/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

baml_language/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ web-time = "1.1.0"
265265
getrandom = { version = "0.2" }
266266
getrandom_03 = { package = "getrandom", version = "0.3", features = [ "wasm_js" ] }
267267
num-bigint = { version = "0.4", features = [ "rand", "serde" ] }
268+
num-traits = { version = "0.2" }
268269
rsa = { version = "0.9", default-features = false, features = [ "std", "pem" ] }
269270
sha2 = { version = "0.10", features = [ "oid" ] }
270271

baml_language/crates/baml_builtins2/baml_std/baml/ns_errors/errors.baml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ class HostCallable {
5555
class_name string
5656
language string
5757
traceback string?
58-
// Opaque slot reserved for the bridge SDK's same-host rehydration
59-
// handle. When set by the originating host, the BAML→host decoder on
60-
// the same runtime can resolve it back to the original native
61-
// exception object; foreign runtimes / released keys see `null` and
62-
// fall back to the metadata fields above. Not yet populated end-to-end
63-
// (HostValueKind::Error wiring is pending).
58+
// Opaque slot carrying the bridge SDK's same-host rehydration handle.
59+
// Always populated on the wire by the originating bridge — a `HostCallable`
60+
// with no `_handle` is not a valid BAML value. The originating bridge
61+
// can resolve the handle back to the original native exception object
62+
// (same-process round-trip → `raised === caught` identity); foreign
63+
// runtimes receive the same handle on the wire but their local lookup
64+
// misses, at which point the host SDK degrades to constructing an
65+
// exception from the metadata fields above. The fallback is a
66+
// *lookup-result* concern on the host side, not a wire shape — the
67+
// field itself is required.
6468
_handle $rust_type
6569
}
6670

baml_language/crates/baml_builtins2/baml_std/baml/ns_host/host.baml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
///
44
/// `T` is the expected return type and `E` the declared error contract,
55
/// both delivered to the native handler at runtime via the type-arg channel
6-
/// (`type_arg_0` / `type_arg_1`). The handler validates the host's returned
7-
/// value against `T` today; `E` is plumbed end-to-end but not yet
8-
/// consumed at the completion site — a future phase will reject host
9-
/// throws that fall outside `E` as `baml.panics.HostContractViolation`. A
10-
/// host value left untyped at the boundary erases `E` to `unknown`.
6+
/// (`type_arg_0` / `type_arg_1`). The completion site validates the host's
7+
/// returned value against `T` and the thrown value against `E`: a return
8+
/// that doesn't inhabit `T` and a throw that isn't a subtype of `E` both
9+
/// become `baml.panics.HostContractViolation` (uncatchable). An on-contract
10+
/// throw rides the VM's normal exception unwinder. A host value left
11+
/// untyped at the boundary erases `E` to `unknown`, which accepts any
12+
/// thrown value — including an opaque `baml.errors.HostCallable`.
1113
function call_host_value<T, E>(handle: HostValue, args: unknown[])
1214
-> T throws E {
1315
$rust_io_function

baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ class errors.NotImplemented <builtin>/baml/ns_errors/error
2727
class errors.LlmClient <builtin>/baml/ns_errors/errors.baml:44
2828
class errors.DevOther <builtin>/baml/ns_errors/errors.baml:49
2929
class errors.HostCallable <builtin>/baml/ns_errors/errors.baml:53
30-
class errors.GenericSdkError <builtin>/baml/ns_errors/errors.baml:70
31-
class errors.CompilationError <builtin>/baml/ns_errors/errors.baml:76
30+
class errors.GenericSdkError <builtin>/baml/ns_errors/errors.baml:74
31+
class errors.CompilationError <builtin>/baml/ns_errors/errors.baml:80
3232
class errors.StackFrame <builtin>/baml/ns_errors/stack_trace.baml:2
3333
class errors.StackTrace <builtin>/baml/ns_errors/stack_trace.baml:9
3434
function events.send <builtin>/baml/ns_events/events.baml:3
@@ -49,8 +49,8 @@ class future.Future <builtin>/baml/ns_future/futur
4949
class glob.ScanOptions <builtin>/baml/ns_glob/glob.baml:2
5050
class glob.Glob <builtin>/baml/ns_glob/glob.baml:18
5151
function glob.new <builtin>/baml/ns_glob/glob.baml:34
52-
function host.call_host_value <builtin>/baml/ns_host/host.baml:11
53-
class host.HostValue <builtin>/baml/ns_host/host.baml:18
52+
function host.call_host_value <builtin>/baml/ns_host/host.baml:13
53+
class host.HostValue <builtin>/baml/ns_host/host.baml:20
5454
class http.Request <builtin>/baml/ns_http/http.baml:2
5555
class http.Response <builtin>/baml/ns_http/http.baml:10
5656
class http.SseStream <builtin>/baml/ns_http/http.baml:33

baml_language/crates/bex_engine/src/conversion.rs

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,3 +1535,268 @@ pub fn test_arg_to_external(v: &bex_vm_types::TestArgValue) -> BexExternalValue
15351535
},
15361536
}
15371537
}
1538+
1539+
#[cfg(test)]
1540+
mod peel_to_rust_type_tests {
1541+
use baml_type::{TyAttr, TypeName};
1542+
1543+
use super::*;
1544+
1545+
/// `Ty::Opaque("baml.rust.RustType", _)` — the canonical shape.
1546+
fn rust_type() -> Ty {
1547+
Ty::Opaque(
1548+
TypeName::from_dotted_path("baml.rust.RustType"),
1549+
TyAttr::default(),
1550+
)
1551+
}
1552+
1553+
#[test]
1554+
fn direct_rust_type_matches() {
1555+
assert_eq!(peel_to_rust_type(&rust_type()), Some(()));
1556+
}
1557+
1558+
#[test]
1559+
fn optional_rust_type_peels_through() {
1560+
// `Ty::optional(RustType)` lowers to `Ty::Union([RustType, Null])`
1561+
// post-`Ty::Optional`-removal; the union arm in `peel_to_rust_type`
1562+
// picks the single RustType member.
1563+
let ty = Ty::optional(rust_type());
1564+
assert_eq!(peel_to_rust_type(&ty), Some(()));
1565+
}
1566+
1567+
#[test]
1568+
fn nested_optional_rust_type_peels_through() {
1569+
// `T??` collapses to `T?` per `Ty::optional`'s idempotence rule
1570+
// (a union already containing `null` is returned unchanged), so
1571+
// this is effectively the same shape as the single-optional case
1572+
// — still a single non-null member that peels.
1573+
let ty = Ty::optional(Ty::optional(rust_type()));
1574+
assert_eq!(peel_to_rust_type(&ty), Some(()));
1575+
}
1576+
1577+
#[test]
1578+
fn singleton_union_with_rust_type_and_null_matches() {
1579+
// `RustType | null` — only one non-null arm so the peel
1580+
// unambiguously picks `RustType`.
1581+
let ty = Ty::Union(
1582+
vec![
1583+
rust_type(),
1584+
Ty::Null {
1585+
attr: TyAttr::default(),
1586+
},
1587+
],
1588+
TyAttr::default(),
1589+
);
1590+
assert_eq!(peel_to_rust_type(&ty), Some(()));
1591+
}
1592+
1593+
#[test]
1594+
fn union_with_rust_type_plus_non_rust_arm_still_unique_matches() {
1595+
// `RustType | string` — there's still exactly one `RustType` arm,
1596+
// and `peel_to_rust_type` only cares about uniqueness of *that*
1597+
// shape (non-`RustType` arms count as "doesn't match" and don't
1598+
// contribute to the duplicate-count).
1599+
let ty = Ty::Union(
1600+
vec![
1601+
rust_type(),
1602+
Ty::String {
1603+
attr: TyAttr::default(),
1604+
},
1605+
],
1606+
TyAttr::default(),
1607+
);
1608+
assert_eq!(peel_to_rust_type(&ty), Some(()));
1609+
}
1610+
1611+
#[test]
1612+
fn union_with_two_rust_type_arms_is_ambiguous() {
1613+
// `RustType | RustType` — two arms peel to the target. The
1614+
// function rejects to avoid silently picking one.
1615+
let ty = Ty::Union(vec![rust_type(), rust_type()], TyAttr::default());
1616+
assert_eq!(peel_to_rust_type(&ty), None);
1617+
}
1618+
1619+
#[test]
1620+
fn plain_string_does_not_match() {
1621+
assert_eq!(
1622+
peel_to_rust_type(&Ty::String {
1623+
attr: TyAttr::default()
1624+
}),
1625+
None,
1626+
);
1627+
}
1628+
1629+
#[test]
1630+
fn unrelated_opaque_does_not_match() {
1631+
// A different opaque type — e.g. `baml.llm.PromptAst` — must
1632+
// not be confused with `baml.rust.RustType`.
1633+
let ty = Ty::Opaque(
1634+
TypeName::from_dotted_path("baml.llm.PromptAst"),
1635+
TyAttr::default(),
1636+
);
1637+
assert_eq!(peel_to_rust_type(&ty), None);
1638+
}
1639+
1640+
#[test]
1641+
fn optional_of_unrelated_type_does_not_match() {
1642+
let ty = Ty::optional(Ty::String {
1643+
attr: TyAttr::default(),
1644+
});
1645+
assert_eq!(peel_to_rust_type(&ty), None);
1646+
}
1647+
1648+
#[test]
1649+
fn union_with_no_rust_type_arm_does_not_match() {
1650+
let ty = Ty::Union(
1651+
vec![
1652+
Ty::String {
1653+
attr: TyAttr::default(),
1654+
},
1655+
Ty::Int {
1656+
attr: TyAttr::default(),
1657+
},
1658+
],
1659+
TyAttr::default(),
1660+
);
1661+
assert_eq!(peel_to_rust_type(&ty), None);
1662+
}
1663+
}
1664+
1665+
#[cfg(test)]
1666+
mod peel_function_ty_tests {
1667+
use baml_type::TyAttr;
1668+
1669+
use super::*;
1670+
1671+
/// `(int) -> string` — the canonical concrete function shape.
1672+
fn fn_ty() -> Ty {
1673+
Ty::Function {
1674+
params: vec![Ty::Int {
1675+
attr: TyAttr::default(),
1676+
}],
1677+
ret: Box::new(Ty::String {
1678+
attr: TyAttr::default(),
1679+
}),
1680+
throws: Box::new(Ty::Void {
1681+
attr: TyAttr::default(),
1682+
}),
1683+
attr: TyAttr::default(),
1684+
}
1685+
}
1686+
1687+
/// A second, distinct function shape — `() -> int` — used to verify the
1688+
/// uniqueness rule rejects two function members in a union.
1689+
fn other_fn_ty() -> Ty {
1690+
Ty::Function {
1691+
params: vec![],
1692+
ret: Box::new(Ty::Int {
1693+
attr: TyAttr::default(),
1694+
}),
1695+
throws: Box::new(Ty::Void {
1696+
attr: TyAttr::default(),
1697+
}),
1698+
attr: TyAttr::default(),
1699+
}
1700+
}
1701+
1702+
#[test]
1703+
fn direct_function_returns_itself() {
1704+
let ty = fn_ty();
1705+
let peeled = peel_function_ty(&ty).expect("must peel a direct Function");
1706+
assert!(matches!(peeled, Ty::Function { .. }));
1707+
}
1708+
1709+
#[test]
1710+
fn optional_function_peels_through() {
1711+
// `Ty::optional(fn)` lowers to `Ty::Union([fn, Null])`; the union
1712+
// arm in `peel_function_ty` picks the single function member.
1713+
let ty = Ty::optional(fn_ty());
1714+
let peeled = peel_function_ty(&ty).expect("Union<fn, Null> must peel");
1715+
assert!(matches!(peeled, Ty::Function { .. }));
1716+
}
1717+
1718+
#[test]
1719+
fn nested_optional_function_peels_through() {
1720+
// `Ty::optional` is idempotent — `T??` collapses to `T?` — so this
1721+
// is effectively the same shape as the single-optional case.
1722+
let ty = Ty::optional(Ty::optional(fn_ty()));
1723+
assert!(peel_function_ty(&ty).is_some());
1724+
}
1725+
1726+
#[test]
1727+
fn union_with_single_function_arm_peels_through() {
1728+
// `((int) -> string) | null` — only one function member.
1729+
let ty = Ty::Union(
1730+
vec![
1731+
fn_ty(),
1732+
Ty::Null {
1733+
attr: TyAttr::default(),
1734+
},
1735+
],
1736+
TyAttr::default(),
1737+
);
1738+
assert!(peel_function_ty(&ty).is_some());
1739+
}
1740+
1741+
#[test]
1742+
fn union_with_function_plus_non_function_arm_peels_through() {
1743+
// `((int) -> string) | string` — exactly one function member.
1744+
let ty = Ty::Union(
1745+
vec![
1746+
fn_ty(),
1747+
Ty::String {
1748+
attr: TyAttr::default(),
1749+
},
1750+
],
1751+
TyAttr::default(),
1752+
);
1753+
assert!(peel_function_ty(&ty).is_some());
1754+
}
1755+
1756+
#[test]
1757+
fn union_with_two_distinct_function_arms_is_ambiguous() {
1758+
// `((int) -> string) | (() -> int)` — two function members.
1759+
// The peel rejects to avoid silently picking one. Pins the
1760+
// determinism contract of the helper.
1761+
let ty = Ty::Union(vec![fn_ty(), other_fn_ty()], TyAttr::default());
1762+
assert!(peel_function_ty(&ty).is_none());
1763+
}
1764+
1765+
#[test]
1766+
fn plain_string_does_not_match() {
1767+
let ty = Ty::String {
1768+
attr: TyAttr::default(),
1769+
};
1770+
assert!(peel_function_ty(&ty).is_none());
1771+
}
1772+
1773+
#[test]
1774+
fn optional_of_non_function_does_not_match() {
1775+
let ty = Ty::optional(Ty::String {
1776+
attr: TyAttr::default(),
1777+
});
1778+
assert!(peel_function_ty(&ty).is_none());
1779+
}
1780+
1781+
#[test]
1782+
fn union_with_no_function_arm_does_not_match() {
1783+
let ty = Ty::Union(
1784+
vec![
1785+
Ty::String {
1786+
attr: TyAttr::default(),
1787+
},
1788+
Ty::Int {
1789+
attr: TyAttr::default(),
1790+
},
1791+
],
1792+
TyAttr::default(),
1793+
);
1794+
assert!(peel_function_ty(&ty).is_none());
1795+
}
1796+
1797+
#[test]
1798+
fn empty_union_does_not_match() {
1799+
let ty = Ty::Union(vec![], TyAttr::default());
1800+
assert!(peel_function_ty(&ty).is_none());
1801+
}
1802+
}

0 commit comments

Comments
 (0)