Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CURRENT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,10 +486,11 @@ Example for confidence:

```rust
fn __qa_confidence_type_ir() -> ::dspy_rs::TypeIR {
let ty = <f32 as ::dspy_rs::legacy_bridge::BamlTypeInternal>::baml_type_ir();
::dspy_rs::legacy_bridge::with_constraints(ty, vec![
let mut ty = ::dspy_rs::baml_type_ir::<f32>();
ty.meta_mut().constraints.extend(vec![
::dspy_rs::Constraint::new_check("range", "this >= 0.0 && this <= 1.0"),
])
]);
ty
}
```

Expand Down
4 changes: 2 additions & 2 deletions crates/bamltype-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ fn resolve_runtime_crate() -> syn::Result<Path> {
}

if let Some(dspy_path) = find_crate_path("dspy-rs") {
return Ok(syn::parse_quote!(#dspy_path::bamltype));
return Ok(syn::parse_quote!(#dspy_path::__macro_support::bamltype));
}

Err(syn::Error::new(
Expand Down Expand Up @@ -725,7 +725,7 @@ fn normalize_field_attrs(
let with_expr = quote! {
&#runtime_crate::facet_ext::WithAdapterFns {
type_ir: || <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::type_ir(),
register: |reg| <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::register(reg),
register: |ctx| <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::register(ctx),
apply: |partial, value, path| {
let converted = <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::try_from_baml(value, path)?;
partial.set(converted).map_err(|err| {
Expand Down
31 changes: 30 additions & 1 deletion crates/bamltype/src/adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,40 @@ use baml_types::{BamlValue, TypeIR};
use crate::BamlConvertError;

/// Registry type used by field codecs to register custom schema artifacts.
///
/// This is the canonical registry surface for facet-level codec extensions.
pub type AdapterSchemaRegistry = crate::schema_registry::SchemaRegistry;

/// Backward-friendly alias clarifying registry ownership at the codec boundary.
pub type FieldCodecSchemaRegistry = AdapterSchemaRegistry;

/// Context passed to `FieldCodec::register`.
#[derive(Debug)]
pub struct FieldCodecRegisterContext<'a> {
pub registry: &'a mut AdapterSchemaRegistry,
pub owner_internal_name: Option<&'a str>,
pub field_name: Option<&'a str>,
pub rendered_field_name: Option<&'a str>,
pub variant_name: Option<&'a str>,
pub rendered_variant_name: Option<&'a str>,
}

impl<'a> FieldCodecRegisterContext<'a> {
pub fn new(registry: &'a mut AdapterSchemaRegistry) -> Self {
Self {
registry,
owner_internal_name: None,
field_name: None,
rendered_field_name: None,
variant_name: None,
rendered_variant_name: None,
}
}
}

/// Field-level conversion + schema representation hook.
pub trait FieldCodec<T> {
fn type_ir() -> TypeIR;
fn register(_reg: &mut AdapterSchemaRegistry) {}
fn register(_ctx: FieldCodecRegisterContext<'_>) {}
fn try_from_baml(value: BamlValue, path: Vec<String>) -> Result<T, BamlConvertError>;
}
7 changes: 5 additions & 2 deletions crates/bamltype/src/facet_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use baml_types::{BamlValue, TypeIR};

use crate::adapters::AdapterSchemaRegistry;
use crate::adapters::FieldCodecRegisterContext;
use crate::runtime::BamlConvertError;

/// Field-level adapter application function.
Expand All @@ -12,14 +12,17 @@ pub type AdapterApplyFn = fn(
Vec<String>,
) -> Result<facet_reflect::Partial<'static>, BamlConvertError>;

/// Field-level adapter schema registration function.
pub type AdapterRegisterFn = for<'a> fn(FieldCodecRegisterContext<'a>);

/// Runtime hooks for `#[baml(with = "...")]`.
#[derive(Clone, Copy, Debug, facet::Facet)]
#[facet(opaque)]
pub struct WithAdapterFns {
/// Schema type representation callback.
pub type_ir: fn() -> TypeIR,
/// Schema registration callback.
pub register: fn(&mut AdapterSchemaRegistry),
pub register: AdapterRegisterFn,
/// Value conversion callback.
pub apply: AdapterApplyFn,
}
Expand Down
28 changes: 18 additions & 10 deletions crates/bamltype/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ pub use convert::{ConvertError, from_baml_value, from_baml_value_with_flags, to_

mod runtime;
pub use runtime::{
BamlConvertError, BamlTypeInternal, BamlTypeTrait, BamlValueConvert, ToBamlValue,
default_streaming_behavior, get_field,
BamlConvertError, baml_internal_name, baml_type_ir, default_streaming_behavior, get_field,
to_baml_value_lossy, try_from_baml_value,
};

pub mod adapters;
Expand Down Expand Up @@ -96,25 +96,33 @@ pub trait BamlSchema: for<'a> facet::Facet<'a> {
}

/// Runtime trait for types that expose BAML schema + conversion entry points.
pub trait BamlType: BamlTypeInternal + BamlValueConvert + Sized + 'static {
pub trait BamlType: Sized + 'static {
fn baml_output_format() -> &'static OutputFormatContent;
fn baml_internal_name() -> &'static str;
fn baml_type_ir() -> TypeIR;
fn try_from_baml_value(value: BamlValue) -> Result<Self, BamlConvertError>;
fn to_baml_value(&self) -> BamlValue;
}

impl<T: BamlSchema> BamlType for T {
fn baml_output_format() -> &'static OutputFormatContent {
&T::baml_schema().output_format
}

fn baml_internal_name() -> &'static str {
<Self as BamlTypeInternal>::baml_internal_name()
runtime::baml_internal_name::<T>()
}

fn baml_type_ir() -> TypeIR {
<Self as BamlTypeInternal>::baml_type_ir()
runtime::baml_type_ir::<T>()
}

fn try_from_baml_value(value: BamlValue) -> Result<Self, BamlConvertError> {
<Self as BamlValueConvert>::try_from_baml_value(value, Vec::new())
runtime::try_from_baml_value(value)
}
}

impl<T: BamlTypeTrait> BamlType for T {
fn baml_output_format() -> &'static OutputFormatContent {
<T as BamlTypeTrait>::baml_output_format()
fn to_baml_value(&self) -> BamlValue {
runtime::to_baml_value_lossy(self)
}
}

Expand Down
83 changes: 21 additions & 62 deletions crates/bamltype/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
//! Facet runtime traits and conversion helpers.
//! Facet runtime helpers and conversion glue.

use baml_types::{BamlMap, BamlValue, TypeIR, type_meta};
use facet::Facet;
use internal_baml_jinja::types::OutputFormatContent;

use crate::BamlSchema;
use crate::convert;
use crate::schema_builder::build_type_ir_from_shape;

Expand Down Expand Up @@ -75,77 +73,38 @@ impl From<convert::ConvertError> for BamlConvertError {
}
}

/// Internal type metadata.
pub trait BamlTypeInternal {
fn baml_internal_name() -> &'static str;
fn baml_type_ir() -> TypeIR;
}

/// Convert from BamlValue to Rust.
pub trait BamlValueConvert: Sized {
fn try_from_baml_value(value: BamlValue, path: Vec<String>) -> Result<Self, BamlConvertError>;
}

/// Convert from Rust to BamlValue.
pub trait ToBamlValue {
fn to_baml_value(&self) -> BamlValue;
}

/// Runtime trait used by schema rendering and parsing.
///
/// Named `BamlTypeTrait` to avoid collision with the `#[BamlType]` attribute macro.
pub trait BamlTypeTrait: BamlTypeInternal + BamlValueConvert + Sized + 'static {
fn baml_output_format() -> &'static OutputFormatContent;

fn baml_internal_name() -> &'static str {
<Self as BamlTypeInternal>::baml_internal_name()
}

fn baml_type_ir() -> TypeIR {
<Self as BamlTypeInternal>::baml_type_ir()
}
}

impl<T: Facet<'static>> BamlTypeInternal for T {
fn baml_internal_name() -> &'static str {
for attr in T::SHAPE.attributes {
if attr.ns != Some("bamltype") || attr.key != "internal_name" {
continue;
}

if let Some(name) = attr.get_as::<&'static str>() {
return name;
}
/// Compute the BAML internal name for a type.
pub fn baml_internal_name<T: Facet<'static>>() -> &'static str {
for attr in T::SHAPE.attributes {
if attr.ns != Some("bamltype") || attr.key != "internal_name" {
continue;
}

if let Some(name) = T::SHAPE.get_builtin_attr_value::<&'static str>("internal_name") {
name
} else {
std::any::type_name::<T>()
if let Some(name) = attr.get_as::<&'static str>() {
return name;
}
}

fn baml_type_ir() -> TypeIR {
build_type_ir_from_shape(T::SHAPE)
if let Some(name) = T::SHAPE.get_builtin_attr_value::<&'static str>("internal_name") {
name
} else {
std::any::type_name::<T>()
}
}

impl<T: Facet<'static>> BamlValueConvert for T {
fn try_from_baml_value(value: BamlValue, _path: Vec<String>) -> Result<Self, BamlConvertError> {
convert::from_baml_value(value).map_err(BamlConvertError::from)
}
/// Compute TypeIR for a type from facet shape data.
pub fn baml_type_ir<T: Facet<'static>>() -> TypeIR {
build_type_ir_from_shape(T::SHAPE)
}

impl<T: Facet<'static>> ToBamlValue for T {
fn to_baml_value(&self) -> BamlValue {
convert::to_baml_value(self).unwrap_or(BamlValue::Null)
}
/// Convert a BamlValue into a concrete Rust type with stable BamlConvertError shape.
pub fn try_from_baml_value<T: Facet<'static>>(value: BamlValue) -> Result<T, BamlConvertError> {
convert::from_baml_value(value).map_err(BamlConvertError::from)
}

impl<T: BamlSchema> BamlTypeTrait for T {
fn baml_output_format() -> &'static OutputFormatContent {
&T::baml_schema().output_format
}
/// Convert a Rust value into BamlValue, preserving legacy null-fallback behavior.
pub fn to_baml_value_lossy<T: Facet<'static>>(value: &T) -> BamlValue {
convert::to_baml_value(value).unwrap_or(BamlValue::Null)
}

/// Default streaming behavior helper.
Expand Down
13 changes: 10 additions & 3 deletions crates/bamltype/src/schema_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use facet::{Attr, ConstTypeId, Def, Field, Shape, Type, UserType};
use internal_baml_jinja::types::{Class, Enum, Name, OutputFormatContent};

use crate::SchemaBundle;
use crate::adapters::FieldCodecRegisterContext;
use crate::facet_ext;
use crate::schema_registry::SchemaRegistry;

Expand All @@ -25,8 +26,7 @@ pub fn build_schema_bundle(shape: &'static Shape) -> SchemaBundle {

/// Build a TypeIR from a facet Shape (without building full schema).
///
/// Used by runtime trait implementations to provide `BamlTypeInternal::baml_type_ir()`
/// for any `Facet` type.
/// Used by runtime helpers to provide `baml_type_ir::<T>()` for any `Facet` type.
pub fn build_type_ir_from_shape(shape: &'static Shape) -> TypeIR {
let mut builder = SchemaBuilder::new();
builder.build_type_ir(shape)
Expand Down Expand Up @@ -388,7 +388,14 @@ impl SchemaBuilder {
variant_rendered: Option<&str>,
) -> TypeIR {
if let Some(with) = facet_ext::with_adapter_fns(field.attributes) {
(with.register)(&mut self.registry);
(with.register)(FieldCodecRegisterContext {
registry: &mut self.registry,
owner_internal_name: Some(owner_internal_name),
field_name: Some(field.name),
rendered_field_name: Some(field.effective_name()),
variant_name,
rendered_variant_name: variant_rendered,
});
return (with.type_ir)();
}

Expand Down
35 changes: 11 additions & 24 deletions crates/bamltype/tests/contract_frozen_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,9 @@ impl facet_runtime::adapters::FieldCodec<i64> for FacetI64ObjectAdapter {
TypeIR::class("AdapterI64Wrapper")
}

fn register(reg: &mut facet_runtime::adapters::AdapterSchemaRegistry) {
fn register(ctx: facet_runtime::adapters::FieldCodecRegisterContext<'_>) {
use facet_runtime::internal_baml_jinja::types::{Class, Name};
let reg = ctx.registry;

if !reg.mark_type("AdapterI64Wrapper") {
return;
Expand Down Expand Up @@ -1037,15 +1038,10 @@ fn contract_with_adapter_schema_and_parse_fixture() {

fn assert_default_adapter_roundtrip<T>(value: T)
where
T: Clone
+ std::fmt::Debug
+ PartialEq
+ facet_runtime::ToBamlValue
+ facet_runtime::BamlValueConvert,
T: Clone + std::fmt::Debug + PartialEq + facet_runtime::facet::Facet<'static>,
{
let baml = facet_runtime::ToBamlValue::to_baml_value(&value);
let back = <T as facet_runtime::BamlValueConvert>::try_from_baml_value(baml, Vec::new())
.expect("roundtrip");
let baml = facet_runtime::to_baml_value_lossy(&value);
let back = facet_runtime::try_from_baml_value::<T>(baml).expect("roundtrip");
assert_eq!(back, value);
}

Expand Down Expand Up @@ -1101,11 +1097,8 @@ fn contract_default_adapter_error_shape_fixture() {
.collect(),
);

let err = <FacetUnsigned32 as facet_runtime::BamlValueConvert>::try_from_baml_value(
invalid,
Vec::new(),
)
.expect_err("should fail");
let err =
facet_runtime::try_from_baml_value::<FacetUnsigned32>(invalid).expect_err("should fail");

let snapshot = serde_json::to_string_pretty(&json!({
"expected": err.expected,
Expand All @@ -1127,11 +1120,8 @@ fn contract_default_adapter_error_shape_fixture() {

#[test]
fn contract_direct_integer_string_conversion_error_fixture() {
let err = <i64 as facet_runtime::BamlValueConvert>::try_from_baml_value(
BamlValue::String("123".into()),
Vec::new(),
)
.expect_err("should reject");
let err = facet_runtime::try_from_baml_value::<i64>(BamlValue::String("123".into()))
.expect_err("should reject");

let snapshot = serde_json::to_string_pretty(&json!({
"expected": err.expected,
Expand Down Expand Up @@ -1161,11 +1151,8 @@ fn contract_direct_map_pairs_conversion_error_fixture() {
);
let raw = BamlValue::List(vec![pair_entry]);

let err = <HashMap<String, i64> as facet_runtime::BamlValueConvert>::try_from_baml_value(
raw,
Vec::new(),
)
.expect_err("should reject");
let err =
facet_runtime::try_from_baml_value::<HashMap<String, i64>>(raw).expect_err("should reject");

let snapshot = serde_json::to_string_pretty(&json!({
"expected": err.expected,
Expand Down
Loading
Loading