From e11c06ddcbd62e52ff180543d35d7fb35d007a1b Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:20:36 +0200 Subject: [PATCH 01/11] removed unnecessary mut --- bauble/src/types.rs | 18 +++++++++--------- bauble/tests/syntax.rs | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 bauble/tests/syntax.rs diff --git a/bauble/src/types.rs b/bauble/src/types.rs index 430f35a..98cafd5 100644 --- a/bauble/src/types.rs +++ b/bauble/src/types.rs @@ -426,7 +426,7 @@ impl TypeRegistry { } else { self.register_type(|this, id| { this.asset_refs.insert(inner, id); - let mut ty = Type { + let ty = Type { meta: TypeMeta { path: TypePath::new(format!("Ref<{}>", this.key_type(inner).meta.path)) .expect("Invariant"), @@ -436,14 +436,14 @@ impl TypeRegistry { kind: TypeKind::Ref(inner), }; - this.on_register_type(id, &mut ty); + this.on_register_type(id, &ty); ty }) } } - fn on_register_type(&mut self, id: TypeId, ty: &mut Type) { + fn on_register_type(&mut self, id: TypeId, ty: &Type) { for tr in ty.meta.traits.iter() { let TypeKind::Trait(types) = &mut self.types[tr.0.0].kind else { panic!("Invariant") @@ -527,9 +527,9 @@ impl TypeRegistry { /// Makes it possible to register a type which is not represented in Rust. #[must_use] - pub fn register_dummy_type(&mut self, mut ty: Type) -> TypeId { + pub fn register_dummy_type(&mut self, ty: Type) -> TypeId { self.register_type(|this, id| { - this.on_register_type(id, &mut ty); + this.on_register_type(id, &ty); ty }) @@ -670,8 +670,8 @@ impl TypeRegistry { match id { Some(id) if self.to_be_assigned.remove(&id) => { - let mut ty = T::construct_type(self); - self.on_register_type(id, &mut ty); + let ty = T::construct_type(self); + self.on_register_type(id, &ty); self.types[id.0] = ty; id @@ -679,8 +679,8 @@ impl TypeRegistry { Some(id) => id, None => self.register_type(|this, id| { this.type_from_rust.insert(std::any::TypeId::of::(), id); - let mut ty = T::construct_type(this); - this.on_register_type(id, &mut ty); + let ty = T::construct_type(this); + this.on_register_type(id, &ty); ty }), diff --git a/bauble/tests/syntax.rs b/bauble/tests/syntax.rs new file mode 100644 index 0000000..fbd6261 --- /dev/null +++ b/bauble/tests/syntax.rs @@ -0,0 +1,20 @@ +use bauble::Bauble; + +#[derive(Bauble, PartialEq, Debug)] +struct Test { + x: i32, + y: u32, +} + +#[test] +pub fn ref_test() { + bauble::bauble_test!( + [Test] + "test: syntax::Test = syntax::Test{ x: -5, y: 5 }\n\ + r: Ref = $test" + [ + Test { x: -5, y: 5 }, + Test { x: -5, y: 5 }, + ] + ); +} From 00c5586635242d0b3d4280bdcea26b542a1450c9 Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Thu, 23 Oct 2025 18:28:05 +0200 Subject: [PATCH 02/11] allows testing references in `bauble_test`. Allows converting from a reference value to its underlying type. --- bauble/src/context.rs | 1 + bauble/src/types.rs | 11 ++++++++++- bauble/src/value/convert.rs | 16 +++++++++++++++- bauble/src/value/mod.rs | 9 ++++++++- bauble/src/value/symbols.rs | 6 +++--- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/bauble/src/context.rs b/bauble/src/context.rs index 964f64f..c9dca45 100644 --- a/bauble/src/context.rs +++ b/bauble/src/context.rs @@ -501,6 +501,7 @@ impl BaubleContext { /// With this method you can expose assets that aren't in bauble. pub fn register_asset(&mut self, path: TypePath<&str>, ty: TypeId) { let ref_ty = self.registry.get_or_register_asset_ref(ty); + self.root_node.build_type(ref_ty, &self.registry); self.root_node.build_asset(path, ref_ty); } diff --git a/bauble/src/types.rs b/bauble/src/types.rs index 98cafd5..5c3dd23 100644 --- a/bauble/src/types.rs +++ b/bauble/src/types.rs @@ -420,10 +420,19 @@ impl TypeRegistry { ) } + /// If `inner` is a reference, then this function should return the ID of `inner`. pub(crate) fn get_or_register_asset_ref(&mut self, inner: TypeId) -> TypeId { if let Some(id) = self.asset_refs.get(&inner) { *id } else { + // If the inner type is a reference then do not create a reference + // again. Look for an already existing reference type. + if let TypeKind::Ref(r) = self.key_type(inner).kind { + let ty = self.get_or_register_asset_ref(r); + assert_eq!(ty, inner, "Multiple reference types to the same reference"); + return ty; + } + self.register_type(|this, id| { this.asset_refs.insert(inner, id); let ty = Type { @@ -873,7 +882,7 @@ impl TypeRegistry { (TypeKind::Trait(types), _) => types.contains(input_id), (TypeKind::Ref(a), TypeKind::Ref(b)) => self.can_infer_from(*a, *b), (TypeKind::Ref(t), _) => self.can_infer_from(*t, input_id), - + (_, TypeKind::Ref(r)) => self.can_infer_from(output_id, *r), (TypeKind::Primitive(a), TypeKind::Primitive(b)) => a == b, (_, TypeKind::Generic(types)) => types.contains(output_id), diff --git a/bauble/src/value/convert.rs b/bauble/src/value/convert.rs index e7e0af1..b12b209 100644 --- a/bauble/src/value/convert.rs +++ b/bauble/src/value/convert.rs @@ -233,6 +233,8 @@ pub enum AnyVal<'a> { } pub struct ConvertMeta<'a> { + /// Allows looking up assets to find their associated object. + pub asset_to_value: &'a mut HashMap, Value>, pub symbols: &'a Symbols<'a>, pub additional_objects: &'a mut AdditionalObjects, pub object_name: TypePathElem<&'a str>, @@ -242,6 +244,7 @@ pub struct ConvertMeta<'a> { impl ConvertMeta<'_> { pub fn reborrow(&mut self) -> ConvertMeta { ConvertMeta { + asset_to_value: self.asset_to_value, symbols: self.symbols, additional_objects: self.additional_objects, object_name: self.object_name, @@ -251,6 +254,7 @@ impl ConvertMeta<'_> { fn with_object_name<'b>(&'b mut self, name: TypePathElem<&'b str>) -> ConvertMeta<'b> { ConvertMeta { + asset_to_value: self.asset_to_value, symbols: self.symbols, additional_objects: self.additional_objects, object_name: name, @@ -1072,10 +1076,20 @@ where Value::Or(variants) } - // A ref can be from a ref value. + // A ref can convert from a ref value. (types::TypeKind::Ref(_), Value::Ref(r)) => { Value::Ref(Self::get_asset(r, meta.symbols)?) } + (_, Value::Ref(r)) => { + let asset = Self::get_asset(r, meta.symbols)?; + println!("{asset}"); + println!("{:?}", meta.asset_to_value); + let value = meta + .asset_to_value + .get(&asset) + .ok_or(ConversionError::UnregisteredAsset.spanned(span))?; + value.clone() + } // Or from another value, and we instantiate a new object that we can refer to. (types::TypeKind::Ref(ty), _) => { let object_name = diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index bdae661..4cceba5 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -973,10 +973,12 @@ pub(crate) fn convert_values( } let default_span = crate::Span::new(file, 0..0); + let mut asset_to_value = HashMap::default(); macro_rules! meta { ($name:expr) => { ConvertMeta { + asset_to_value: &mut asset_to_value, symbols: &symbols, additional_objects: &mut additional_objects, object_name: $name, @@ -1097,7 +1099,12 @@ fn convert_object( ) -> Result { let value = value.convert(meta.reborrow(), expected_type, no_attr())?; - create_object(path, meta.object_name, value, meta.symbols) + let object = create_object(path, meta.object_name, value, meta.symbols)?; + meta.asset_to_value.insert( + path.join(&meta.object_name), + object.value.value.value.clone(), + ); + Ok(object) } fn create_object( diff --git a/bauble/src/value/symbols.rs b/bauble/src/value/symbols.rs index a4a97f1..8567250 100644 --- a/bauble/src/value/symbols.rs +++ b/bauble/src/value/symbols.rs @@ -388,14 +388,14 @@ impl<'a> Symbols<'a> { pub fn resolve_type(&self, path: &Path) -> Result { let item = self.resolve_item(path, RefKind::Type)?; - item.ty.ok_or( + item.ty.ok_or({ ConversionError::RefError(Box::new(RefError { uses: Some(self.uses.clone()), path: self.resolve_path(path)?.value, path_ref: item.into_owned(), kind: RefKind::Type, })) - .spanned(path.span()), - ) + .spanned(path.span()) + }) } } From 72a482654dc3a0b42e5d284dc72c54aea2aa276c Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Thu, 23 Oct 2025 18:47:46 +0200 Subject: [PATCH 03/11] added test cases for bauble references --- bauble/src/lib.rs | 11 +++-- bauble/src/value/convert.rs | 6 +-- bauble/src/value/mod.rs | 4 +- bauble/tests/lang.rs | 91 +++++++++++++++++++++++++++++++++++ bauble/tests/syntax.rs | 20 -------- bauble_macros/tests/derive.rs | 4 +- 6 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 bauble/tests/lang.rs delete mode 100644 bauble/tests/syntax.rs diff --git a/bauble/src/lib.rs b/bauble/src/lib.rs index 81122b8..6b9473a 100644 --- a/bauble/src/lib.rs +++ b/bauble/src/lib.rs @@ -39,9 +39,12 @@ pub mod private { #[macro_export] macro_rules! bauble_test { ( [$($ty:ty),* $(,)?] $source:literal [$($test_value:expr),* $(,)?]) => { - $crate::bauble_test!(__TEST_CTX [$($ty),*] $source [$($test_value),*]) + $crate::bauble_test!(__TEST_CTX [$($ty),*] [$source] [$($test_value),*]) }; - ($ctx_static:ident [$($ty:ty),* $(,)?] $source:literal [$($test_value:expr),* $(,)?]) => { + ( [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => { + $crate::bauble_test!(__TEST_CTX [$($ty),*] [$($source),*] [$($test_value),*]) + }; + ($ctx_static:ident [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => { static $ctx_static: std::sync::OnceLock> = std::sync::OnceLock::new(); { let file_path = $crate::path::TypePath::new("test").unwrap(); @@ -52,7 +55,9 @@ macro_rules! bauble_test { let mut ctx = ctx.build(); ctx.type_registry().validate(true).expect("Invalid type registry"); - ctx.register_file(file_path, format!("\n{}\n", $source)); + $( + ctx.register_file(file_path, format!("\n{}\n", $source)); + )* std::sync::RwLock::new(ctx) }); diff --git a/bauble/src/value/convert.rs b/bauble/src/value/convert.rs index b12b209..85c4444 100644 --- a/bauble/src/value/convert.rs +++ b/bauble/src/value/convert.rs @@ -234,7 +234,7 @@ pub enum AnyVal<'a> { pub struct ConvertMeta<'a> { /// Allows looking up assets to find their associated object. - pub asset_to_value: &'a mut HashMap, Value>, + pub asset_to_value: &'a mut HashMap<(TypePath, TypeId), Value>, pub symbols: &'a Symbols<'a>, pub additional_objects: &'a mut AdditionalObjects, pub object_name: TypePathElem<&'a str>, @@ -1082,11 +1082,9 @@ where } (_, Value::Ref(r)) => { let asset = Self::get_asset(r, meta.symbols)?; - println!("{asset}"); - println!("{:?}", meta.asset_to_value); let value = meta .asset_to_value - .get(&asset) + .get(&(asset, ty_id.value)) .ok_or(ConversionError::UnregisteredAsset.spanned(span))?; value.clone() } diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index 4cceba5..f229f0c 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -1099,9 +1099,9 @@ fn convert_object( ) -> Result { let value = value.convert(meta.reborrow(), expected_type, no_attr())?; - let object = create_object(path, meta.object_name, value, meta.symbols)?; + let object = create_object(path, meta.object_name, value.clone(), meta.symbols)?; meta.asset_to_value.insert( - path.join(&meta.object_name), + (path.join(&meta.object_name), value.ty.value), object.value.value.value.clone(), ); Ok(object) diff --git a/bauble/tests/lang.rs b/bauble/tests/lang.rs new file mode 100644 index 0000000..f3b3ed1 --- /dev/null +++ b/bauble/tests/lang.rs @@ -0,0 +1,91 @@ +use bauble::Bauble; + +#[derive(Bauble, PartialEq, Debug)] +struct Test { + x: i32, + y: u32, +} + +#[test] +pub fn ref_implicit() { + bauble::bauble_test!( + [Test] + "test = lang::Test{ x: -5, y: 5 }\n\ + r = $test" + [ + Test { x: -5, y: 5 }, + Test { x: -5, y: 5 }, + ] + ); +} + +#[test] +pub fn explicit_implicit() { + bauble::bauble_test!( + [Test] + "test = lang::Test{ x: -2, y: 2 }\n\ + r: Ref = $test" + [ + Test { x: -2, y: 2 }, + Test { x: -2, y: 2 }, + ] + ); +} + +#[test] +pub fn ref_explicit_out_of_order() { + bauble::bauble_test!( + TEST0 + [Test] + [ + "test = lang::Test{ x: -5, y: 5 }", + "r: Ref = $test" + ] + [ + Test { x: -5, y: 5 }, + Test { x: -5, y: 5 }, + ] + ); + + bauble::bauble_test!( + TEST1 + [Test] + [ + "r: Ref = $test", + "test = lang::Test{ x: -5, y: 5 }" + ] + [ + Test { x: -5, y: 5 }, + Test { x: -5, y: 5 }, + ] + ); +} + +#[test] +pub fn ref_implicit_out_of_order() { + bauble::bauble_test!( + TEST0 + [Test] + [ + "test = lang::Test{ x: -5, y: 5 }", + "r = $test::test" + ] + [ + Test { x: -5, y: 5 }, + Test { x: -5, y: 5 }, + ] + ); + + bauble::bauble_test!( + TEST1 + [Test] + [ + "r = $test::test", + "test = lang::Test{ x: -5, y: 5 }" + ] + [ + Test { x: -5, y: 5 }, + Test { x: -5, y: 5 }, + ] + ); +} diff --git a/bauble/tests/syntax.rs b/bauble/tests/syntax.rs deleted file mode 100644 index fbd6261..0000000 --- a/bauble/tests/syntax.rs +++ /dev/null @@ -1,20 +0,0 @@ -use bauble::Bauble; - -#[derive(Bauble, PartialEq, Debug)] -struct Test { - x: i32, - y: u32, -} - -#[test] -pub fn ref_test() { - bauble::bauble_test!( - [Test] - "test: syntax::Test = syntax::Test{ x: -5, y: 5 }\n\ - r: Ref = $test" - [ - Test { x: -5, y: 5 }, - Test { x: -5, y: 5 }, - ] - ); -} diff --git a/bauble_macros/tests/derive.rs b/bauble_macros/tests/derive.rs index 9ac960e..4bbcbc8 100644 --- a/bauble_macros/tests/derive.rs +++ b/bauble_macros/tests/derive.rs @@ -368,13 +368,13 @@ fn test_trait() { bauble_test!( TEST_CTX [Trans, Foo, Bar] - r#" + [r#" use derive::{Trans, Foo, Bar}; a: Trans = Foo(32) b: Trans = "meow" - "# + "#] [Trans(Box::new(Foo(32))), Trans(Box::new(Bar("meow".to_string())))] ); } From 110d94dd2b5d397aa18a686a6b682eb5453bb379 Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:51:48 +0200 Subject: [PATCH 04/11] added ref builtin --- bauble/src/builtin.rs | 75 +++++++++++++++++++++++++++++++++++ bauble/src/context.rs | 3 +- bauble/src/lib.rs | 2 + bauble/src/traits.rs | 19 +++++++++ bauble/src/types.rs | 19 ++++----- bauble/src/types/path.rs | 40 +++++++++++++++++++ bauble/src/value/convert.rs | 12 ------ bauble/src/value/mod.rs | 16 +++----- bauble/src/value/symbols.rs | 6 +-- bauble/tests/lang.rs | 54 ++++++++++++++++++------- bauble_macros/tests/derive.rs | 33 --------------- 11 files changed, 192 insertions(+), 87 deletions(-) create mode 100644 bauble/src/builtin.rs diff --git a/bauble/src/builtin.rs b/bauble/src/builtin.rs new file mode 100644 index 0000000..6037203 --- /dev/null +++ b/bauble/src/builtin.rs @@ -0,0 +1,75 @@ +use crate::{ + Bauble, BaubleAllocator, ToRustErrorKind, Val, Value, + path::TypePath, + types::{Type, TypeRegistry}, +}; +use std::{cell::UnsafeCell, fmt::Debug, marker::PhantomData}; + +/// The builtin reference type. +/// +/// Corresponds to a reference to `T` in Bauble. +/// +/// `S` is the inner representation used for `TypePath`. +pub struct Ref { + /// The path to the referenced asset. + pub path: TypePath, + /// Invariant over `T`. + _mark: PhantomData>, +} + +impl Ref { + /// Create a reference from the specified path. The path must not be valid. + pub fn from_path(path: TypePath) -> Self { + Self { + path, + _mark: PhantomData, + } + } +} + +impl PartialEq for Ref { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + } +} +impl Eq for Ref {} + +impl Debug for Ref { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.path, f) + } +} + +impl<'b, A: BaubleAllocator<'b>, T: Bauble<'b, A>> Bauble<'b, A> for Ref { + fn builtin(registry: &mut TypeRegistry) -> Option { + let inner = registry.get_or_register_type::(); + Some(registry.get_or_register_asset_ref(inner)) + } + + fn construct_type(_registry: &mut TypeRegistry) -> Type { + unreachable!("This is a builtin and should never be constructed."); + } + + fn from_bauble( + val: Val, + allocator: &A, + ) -> Result<>::Out, crate::ToRustError> { + match val.value.value { + Value::Ref(r) => Ok({ + let path = allocator.wrap_type_path(r); + let value = Self { + // SAFETY: path was derived from `allocator`. + path: unsafe { allocator.validate(path)? }, + _mark: PhantomData, + }; + + // SAFETY: allocator has wrapped the type path. + unsafe { allocator.wrap(value) } + }), + _ => Err(Self::error( + val.value.span, + ToRustErrorKind::WrongType { found: val.ty }, + )), + } + } +} diff --git a/bauble/src/context.rs b/bauble/src/context.rs index fa46726..cb391f5 100644 --- a/bauble/src/context.rs +++ b/bauble/src/context.rs @@ -1,10 +1,9 @@ -use indexmap::IndexMap; - use crate::{ Bauble, BaubleAllocator, BaubleErrors, path::{TypePath, TypePathElem}, types::{BaubleTrait, TypeId, TypeRegistry}, }; +use indexmap::IndexMap; #[allow(missing_docs)] pub type Source = ariadne::Source; diff --git a/bauble/src/lib.rs b/bauble/src/lib.rs index 6b9473a..d02d327 100644 --- a/bauble/src/lib.rs +++ b/bauble/src/lib.rs @@ -2,6 +2,7 @@ #![feature(iterator_try_collect, let_chains, ptr_metadata)] #![warn(missing_docs)] +mod builtin; mod context; mod error; mod parse; @@ -13,6 +14,7 @@ pub mod types; pub use bauble_macros::Bauble; +pub use builtin::Ref; pub use context::{BaubleContext, BaubleContextBuilder, FileId, PathReference, Source}; pub use error::{BaubleError, BaubleErrors, CustomError, Level, print_errors}; pub use spanned::{Span, SpanExt, Spanned}; diff --git a/bauble/src/traits.rs b/bauble/src/traits.rs index 3280629..49608d2 100644 --- a/bauble/src/traits.rs +++ b/bauble/src/traits.rs @@ -243,12 +243,18 @@ pub trait BaubleAllocator<'a> { where Self: 'a; + /// The inner representation of a type path created by this allocator. + type TypePathInner: 'static; + /// # Safety /// Allocations in `value` have to be allocated with this allocator unsafe fn wrap(&self, value: T) -> Self::Out; /// # Safety /// If validated an item must be placed within the same allocator. unsafe fn validate(&self, value: Self::Out) -> Result; + + /// Create a type path from this allocator. + fn wrap_type_path(&self, path: TypePath) -> Self::Out>; } /// The standard Rust allocator when used by Bauble. @@ -256,6 +262,7 @@ pub struct DefaultAllocator; impl BaubleAllocator<'_> for DefaultAllocator { type Out = T; + type TypePathInner = String; unsafe fn wrap(&self, value: T) -> Self::Out { value @@ -264,10 +271,22 @@ impl BaubleAllocator<'_> for DefaultAllocator { unsafe fn validate(&self, value: Self::Out) -> Result { Ok(value) } + + fn wrap_type_path(&self, path: TypePath) -> TypePath { + path + } } /// The trait used by types usable by Bauble. Any type that can be parsed by bauble should implement this trait. pub trait Bauble<'a, A: BaubleAllocator<'a> = DefaultAllocator>: Sized + 'static { + /// # DON'T CALL THIS, call `TypeRegistry::get_or_register_type` instead. + /// + /// Constructs a builtin type. A builtin type is a type which is not constructed by Bauble during parsing and + /// might warrant additional rules. + fn builtin(#[expect(unused)] registry: &mut types::TypeRegistry) -> Option { + None + } + /// # DON'T CALL THIS, call `TypeRegistry::get_or_register_type` instead. /// /// Constructs a reflection type that bauble uses to parse and resolve types. diff --git a/bauble/src/types.rs b/bauble/src/types.rs index 5c3dd23..32bdbe6 100644 --- a/bauble/src/types.rs +++ b/bauble/src/types.rs @@ -425,14 +425,6 @@ impl TypeRegistry { if let Some(id) = self.asset_refs.get(&inner) { *id } else { - // If the inner type is a reference then do not create a reference - // again. Look for an already existing reference type. - if let TypeKind::Ref(r) = self.key_type(inner).kind { - let ty = self.get_or_register_asset_ref(r); - assert_eq!(ty, inner, "Multiple reference types to the same reference"); - return ty; - } - self.register_type(|this, id| { this.asset_refs.insert(inner, id); let ty = Type { @@ -576,9 +568,9 @@ impl TypeRegistry { continue; } - let object_name = - TypePathElem::new(format!("{}_{i}", ty.meta.path.get_end().unwrap().1)) - .unwrap(); + let path_end = ty.meta.path.get_end().unwrap().1; + let object_path = path_end.strip_generic().append(&format!("_{i}")).unwrap(); + let object_name = object_path.get_end().unwrap().1; let object_path = file.join(&object_name); @@ -675,6 +667,10 @@ impl TypeRegistry { /// Register `T` if it is not registerted already, then get the type ID for `T`. pub fn get_or_register_type<'a, T: Bauble<'a, A>, A: BaubleAllocator<'a>>(&mut self) -> TypeId { + if let Some(id) = T::builtin(self) { + return id; + } + let id = self.type_id::(); match id { @@ -882,7 +878,6 @@ impl TypeRegistry { (TypeKind::Trait(types), _) => types.contains(input_id), (TypeKind::Ref(a), TypeKind::Ref(b)) => self.can_infer_from(*a, *b), (TypeKind::Ref(t), _) => self.can_infer_from(*t, input_id), - (_, TypeKind::Ref(r)) => self.can_infer_from(output_id, *r), (TypeKind::Primitive(a), TypeKind::Primitive(b)) => a == b, (_, TypeKind::Generic(types)) => types.contains(output_id), diff --git a/bauble/src/types/path.rs b/bauble/src/types/path.rs index 8b3382a..b5e5ef2 100644 --- a/bauble/src/types/path.rs +++ b/bauble/src/types/path.rs @@ -415,6 +415,10 @@ impl> TypePath { == Some(PATH_SEPERATOR)) } + pub fn is_generic(&self) -> bool { + self.0.as_ref().ends_with('>') + } + /// Returns true if this type path, referencing a type, is something that is /// allowed to be parsed by Bauble. /// @@ -487,6 +491,42 @@ impl> TypePath { pub fn is_subobject(&self) -> bool { self.iter().any(|part| part.as_str().contains('@')) } + + /// Appends `end` onto `self`. + /// + /// Appending is different from `join` in that it will not insert a separator, + /// and when dealing with generic, `append` will insert `end` before the + /// generic argument but onto the last identifier before `<`. + /// + /// Performs path validation on the returned value. + pub fn append(&self, end: &str) -> Result> { + if self.is_generic() { + let index = self + .as_str() + .find('<') + .expect("generic should always have '<'"); + let mut string = self.to_string(); + string.insert_str(index, end); + TypePath::new(string) + } else { + TypePath::new(format!("{self}{end}")) + } + } + + /// Returns a variant of `self` with no generics at the end. + pub fn strip_generic(self) -> TypePath { + if self.is_generic() { + let index = self + .0 + .as_ref() + .find('<') + .expect("generic should always have '<'"); + // No need to check since nothing is added onto the path. + TypePath::new_unchecked(self.0.as_ref().split_at(index).0.to_string()) + } else { + TypePath::new_unchecked(self.as_str().to_string()) + } + } } impl<'a> TypePath<&'a str> { diff --git a/bauble/src/value/convert.rs b/bauble/src/value/convert.rs index 85c4444..34f5102 100644 --- a/bauble/src/value/convert.rs +++ b/bauble/src/value/convert.rs @@ -233,8 +233,6 @@ pub enum AnyVal<'a> { } pub struct ConvertMeta<'a> { - /// Allows looking up assets to find their associated object. - pub asset_to_value: &'a mut HashMap<(TypePath, TypeId), Value>, pub symbols: &'a Symbols<'a>, pub additional_objects: &'a mut AdditionalObjects, pub object_name: TypePathElem<&'a str>, @@ -244,7 +242,6 @@ pub struct ConvertMeta<'a> { impl ConvertMeta<'_> { pub fn reborrow(&mut self) -> ConvertMeta { ConvertMeta { - asset_to_value: self.asset_to_value, symbols: self.symbols, additional_objects: self.additional_objects, object_name: self.object_name, @@ -254,7 +251,6 @@ impl ConvertMeta<'_> { fn with_object_name<'b>(&'b mut self, name: TypePathElem<&'b str>) -> ConvertMeta<'b> { ConvertMeta { - asset_to_value: self.asset_to_value, symbols: self.symbols, additional_objects: self.additional_objects, object_name: name, @@ -1080,14 +1076,6 @@ where (types::TypeKind::Ref(_), Value::Ref(r)) => { Value::Ref(Self::get_asset(r, meta.symbols)?) } - (_, Value::Ref(r)) => { - let asset = Self::get_asset(r, meta.symbols)?; - let value = meta - .asset_to_value - .get(&(asset, ty_id.value)) - .ok_or(ConversionError::UnregisteredAsset.spanned(span))?; - value.clone() - } // Or from another value, and we instantiate a new object that we can refer to. (types::TypeKind::Ref(ty), _) => { let object_name = diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index 0bafaa9..48e5629 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -788,7 +788,11 @@ pub(crate) fn register_assets( let symbols = Symbols { ctx: &*ctx, uses }; // To register an asset we need to determine its type. - let ty = if let Some(ty) = &binding.type_path { + let ty = if let Some(ty) = &binding.type_path + // If the value is a reference, then an explicit type should + // still be delayed. + && !matches!(binding.value.value.value, Value::Ref(_)) + { symbols.resolve_type(ty) } else { let res = value_type(&binding.value, &symbols) @@ -978,12 +982,10 @@ pub(crate) fn convert_values( } let default_span = crate::Span::new(file, 0..0); - let mut asset_to_value = HashMap::default(); macro_rules! meta { ($name:expr) => { ConvertMeta { - asset_to_value: &mut asset_to_value, symbols: &symbols, additional_objects: &mut additional_objects, object_name: $name, @@ -1103,13 +1105,7 @@ fn convert_object( mut meta: ConvertMeta, ) -> Result { let value = value.convert(meta.reborrow(), expected_type, no_attr())?; - - let object = create_object(path, meta.object_name, value.clone(), meta.symbols)?; - meta.asset_to_value.insert( - (path.join(&meta.object_name), value.ty.value), - object.value.value.value.clone(), - ); - Ok(object) + create_object(path, meta.object_name, value, meta.symbols) } fn create_object( diff --git a/bauble/src/value/symbols.rs b/bauble/src/value/symbols.rs index 8567250..a4a97f1 100644 --- a/bauble/src/value/symbols.rs +++ b/bauble/src/value/symbols.rs @@ -388,14 +388,14 @@ impl<'a> Symbols<'a> { pub fn resolve_type(&self, path: &Path) -> Result { let item = self.resolve_item(path, RefKind::Type)?; - item.ty.ok_or({ + item.ty.ok_or( ConversionError::RefError(Box::new(RefError { uses: Some(self.uses.clone()), path: self.resolve_path(path)?.value, path_ref: item.into_owned(), kind: RefKind::Type, })) - .spanned(path.span()) - }) + .spanned(path.span()), + ) } } diff --git a/bauble/tests/lang.rs b/bauble/tests/lang.rs index f3b3ed1..38ac357 100644 --- a/bauble/tests/lang.rs +++ b/bauble/tests/lang.rs @@ -1,4 +1,4 @@ -use bauble::Bauble; +use bauble::{Bauble, Ref, path::TypePath}; #[derive(Bauble, PartialEq, Debug)] struct Test { @@ -7,43 +7,67 @@ struct Test { } #[test] -pub fn ref_implicit() { +pub fn ref_implicit_type() { bauble::bauble_test!( + TEST0 [Test] - "test = lang::Test{ x: -5, y: 5 }\n\ - r = $test" + ["test = lang::Test{ x: -5, y: 5 }\n\ + r = $test"] [ Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + ] + ); + + bauble::bauble_test!( + TEST1 + [Test] + ["r = $test::test\n\ + test = lang::Test{ x: -5, y: 5 }"] + [ + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -5, y: 5 }, ] ); } #[test] -pub fn explicit_implicit() { +pub fn ref_explicit_type() { bauble::bauble_test!( + TEST0 [Test] - "test = lang::Test{ x: -2, y: 2 }\n\ - r: Ref = $test" + ["test = lang::Test{ x: -2, y: 2 }\n\ + r: Ref = $test"] [ Test { x: -2, y: 2 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + ] + ); + + bauble::bauble_test!( + TEST1 + [Test] + ["r: Ref = $test::test\n\ + test = lang::Test{ x: -2, y: 2 }"] + [ + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -2, y: 2 }, ] ); } #[test] -pub fn ref_explicit_out_of_order() { +pub fn ref_explicit_type_multiple_files() { bauble::bauble_test!( TEST0 [Test] [ "test = lang::Test{ x: -5, y: 5 }", - "r: Ref = $test" + "r: Ref = $test::test" ] [ Test { x: -5, y: 5 }, - Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), ] ); @@ -51,18 +75,18 @@ pub fn ref_explicit_out_of_order() { TEST1 [Test] [ - "r: Ref = $test", + "r: Ref = $test::test", "test = lang::Test{ x: -5, y: 5 }" ] [ - Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -5, y: 5 }, ] ); } #[test] -pub fn ref_implicit_out_of_order() { +pub fn ref_implicit_type_multiple_files() { bauble::bauble_test!( TEST0 [Test] @@ -72,7 +96,7 @@ pub fn ref_implicit_out_of_order() { ] [ Test { x: -5, y: 5 }, - Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), ] ); @@ -84,7 +108,7 @@ pub fn ref_implicit_out_of_order() { "test = lang::Test{ x: -5, y: 5 }" ] [ - Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -5, y: 5 }, ] ); diff --git a/bauble_macros/tests/derive.rs b/bauble_macros/tests/derive.rs index 4bbcbc8..dc86044 100644 --- a/bauble_macros/tests/derive.rs +++ b/bauble_macros/tests/derive.rs @@ -145,39 +145,6 @@ fn test_std_types() { ); } -#[test] -fn test_complex_flatten() { - #[derive(Bauble, PartialEq, Debug)] - #[bauble(flatten)] - struct Inner( - u32, - #[bauble(attribute = a, default)] u32, - #[bauble(attribute = b)] u32, - ); - - #[derive(Bauble, PartialEq, Debug)] - #[bauble(flatten)] - struct Transparent(Inner, #[bauble(attribute = a)] u32); - - bauble_test!( - [Transparent] - r#" - a: derive::Transparent = #[a = 1, b = 2] 3 - - copy t = #[a = 2] 1 - - // Since `b` isn't an attribute on `Transparent` that gets passed to `Inner`. And - // since we already have `a` defined here, the `a` attribute on `copy t` gets - // assigned to `Inner.1`. - b: derive::Transparent = #[a = 4, b = 3] $t - "# - [ - Transparent(Inner(3, 0, 2), 1), - Transparent(Inner(1, 2, 3), 4), - ] - ); -} - #[test] fn test_from() { #[derive(Bauble, PartialEq, Debug)] From 9c8986d416d3620d9cd80ce24a033c6dca8d016a Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Fri, 24 Oct 2025 01:02:45 +0200 Subject: [PATCH 05/11] added checking explicitly typed reference correctness --- bauble/src/value/mod.rs | 42 ++++++++++++++++++++++++++++++++++++++++- bauble/tests/lang.rs | 20 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index 48e5629..4d84459 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -665,6 +665,7 @@ impl PathKind { pub struct DelayedRegister { pub asset: Spanned, pub reference: Spanned, + pub expected_ty_path: Option>, } pub(crate) fn resolve_delayed( @@ -672,6 +673,7 @@ pub(crate) fn resolve_delayed( ctx: &mut crate::context::BaubleContext, ) -> std::result::Result<(), Vec>> { loop { + let mut errors = Vec::new(); let old_len = delayed.len(); // Try to register delayed registers, and remove them as they succeed. @@ -685,6 +687,28 @@ pub(crate) fn resolve_delayed( } } && let Some((ty, _)) = &r.asset { + if let Some(desired_ty) = &d.expected_ty_path { + let span = desired_ty.span; + if let Some(desired_ty) = match &desired_ty.value { + PathKind::Direct(path) => ctx.get_ref(path.borrow()), + PathKind::Indirect(path, ident) => { + ctx.ref_with_ident(path.borrow(), ident.borrow()) + } + } && let Some(desired_ty) = desired_ty.ty + { + if desired_ty != *ty { + errors.push( + ConversionError::ExpectedExactType { + expected: desired_ty, + got: Some(*ty), + } + .spanned(span), + ); + } + } else { + return true; + }; + } ctx.register_asset(d.asset.value.borrow(), *ty); false } else { @@ -692,6 +716,10 @@ pub(crate) fn resolve_delayed( } }); + if !errors.is_empty() { + return Err(errors); + } + if delayed.is_empty() { return Ok(()); } @@ -711,7 +739,6 @@ pub(crate) fn resolve_delayed( } } - let mut errors = Vec::new(); for scc in petgraph::algo::tarjan_scc(&graph) { if scc.len() == 1 { if map[&scc[0]].could_be(scc[0].borrow()) { @@ -819,7 +846,20 @@ pub(crate) fn register_assets( && let Value::Ref(reference) = &*binding.value.value && let Ok(reference) = symbols.resolve_path(reference) { + let expected_ty_path = if let Some(expected_ty_path) = &binding.type_path { + match symbols.resolve_path(expected_ty_path) { + Ok(s) => Some(s), + Err(e) => { + errors.push(e); + None + } + } + } else { + None + }; + delayed.push(DelayedRegister { + expected_ty_path, asset: path.spanned(span), reference, }); diff --git a/bauble/tests/lang.rs b/bauble/tests/lang.rs index 38ac357..c681e80 100644 --- a/bauble/tests/lang.rs +++ b/bauble/tests/lang.rs @@ -113,3 +113,23 @@ pub fn ref_implicit_type_multiple_files() { ] ); } + +#[test] +#[should_panic] +pub fn ref_explicit_type_incorrect() { + #[derive(Bauble, PartialEq, Eq, Debug)] + struct Incorrect(u32); + + bauble::bauble_test!( + TEST2 + [Test, Incorrect] + ["i: Incorrect = Incorrect(0)\n\ + r: Ref = $test::test\n\ + test = lang::Test{ x: -2, y: 2 }"] + [ + Incorrect(0), + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + Test { x: -2, y: 2 }, + ] + ); +} From 4ad1b6a2182aa9f0d1eb38ab5f6f3d5f96fe4fb9 Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:26:15 +0200 Subject: [PATCH 06/11] addressed part of review --- bauble/src/lib.rs | 4 ++-- bauble/src/value/mod.rs | 1 + bauble/tests/lang.rs | 29 ++++++++++------------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/bauble/src/lib.rs b/bauble/src/lib.rs index d02d327..9bdc957 100644 --- a/bauble/src/lib.rs +++ b/bauble/src/lib.rs @@ -46,7 +46,7 @@ macro_rules! bauble_test { ( [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => { $crate::bauble_test!(__TEST_CTX [$($ty),*] [$($source),*] [$($test_value),*]) }; - ($ctx_static:ident [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => { + ($ctx_static:ident [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => {{ static $ctx_static: std::sync::OnceLock> = std::sync::OnceLock::new(); { let file_path = $crate::path::TypePath::new("test").unwrap(); @@ -114,5 +114,5 @@ macro_rules! bauble_test { compare_objects(objects); compare_objects(re_objects); } - }; + }}; } diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index 4d84459..3f5b12b 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -665,6 +665,7 @@ impl PathKind { pub struct DelayedRegister { pub asset: Spanned, pub reference: Spanned, + /// The type we want a potential reference to resolve into. pub expected_ty_path: Option>, } diff --git a/bauble/tests/lang.rs b/bauble/tests/lang.rs index c681e80..bbc98d0 100644 --- a/bauble/tests/lang.rs +++ b/bauble/tests/lang.rs @@ -9,10 +9,9 @@ struct Test { #[test] pub fn ref_implicit_type() { bauble::bauble_test!( - TEST0 [Test] - ["test = lang::Test{ x: -5, y: 5 }\n\ - r = $test"] + "test = lang::Test{ x: -5, y: 5 }\n\ + r = $test" [ Test { x: -5, y: 5 }, Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), @@ -20,10 +19,9 @@ pub fn ref_implicit_type() { ); bauble::bauble_test!( - TEST1 [Test] - ["r = $test::test\n\ - test = lang::Test{ x: -5, y: 5 }"] + "r = $test::test\n\ + test = lang::Test{ x: -5, y: 5 }" [ Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -5, y: 5 }, @@ -34,10 +32,9 @@ pub fn ref_implicit_type() { #[test] pub fn ref_explicit_type() { bauble::bauble_test!( - TEST0 [Test] - ["test = lang::Test{ x: -2, y: 2 }\n\ - r: Ref = $test"] + "test = lang::Test{ x: -2, y: 2 }\n\ + r: Ref = $test" [ Test { x: -2, y: 2 }, Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), @@ -45,10 +42,9 @@ pub fn ref_explicit_type() { ); bauble::bauble_test!( - TEST1 [Test] - ["r: Ref = $test::test\n\ - test = lang::Test{ x: -2, y: 2 }"] + "r: Ref = $test::test\n\ + test = lang::Test{ x: -2, y: 2 }" [ Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -2, y: 2 }, @@ -59,7 +55,6 @@ pub fn ref_explicit_type() { #[test] pub fn ref_explicit_type_multiple_files() { bauble::bauble_test!( - TEST0 [Test] [ "test = lang::Test{ x: -5, y: 5 }", @@ -72,7 +67,6 @@ pub fn ref_explicit_type_multiple_files() { ); bauble::bauble_test!( - TEST1 [Test] [ "r: Ref = $test::test", @@ -88,7 +82,6 @@ pub fn ref_explicit_type_multiple_files() { #[test] pub fn ref_implicit_type_multiple_files() { bauble::bauble_test!( - TEST0 [Test] [ "test = lang::Test{ x: -5, y: 5 }", @@ -101,7 +94,6 @@ pub fn ref_implicit_type_multiple_files() { ); bauble::bauble_test!( - TEST1 [Test] [ "r = $test::test", @@ -121,11 +113,10 @@ pub fn ref_explicit_type_incorrect() { struct Incorrect(u32); bauble::bauble_test!( - TEST2 [Test, Incorrect] - ["i: Incorrect = Incorrect(0)\n\ + "i: Incorrect = Incorrect(0)\n\ r: Ref = $test::test\n\ - test = lang::Test{ x: -2, y: 2 }"] + test = lang::Test{ x: -2, y: 2 }" [ Incorrect(0), Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), From 30872b706d79983d645f9534d43d5a32a57f76e3 Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:03:13 +0100 Subject: [PATCH 07/11] addressed part of review --- OVERVIEW.md | 3 + bauble/src/builtin.rs | 15 ++++- bauble/src/lib.rs | 8 +-- bauble/src/value/mod.rs | 8 ++- bauble/tests/integration.rs | 126 ++++++++++++++++++++++++++++++++++++ bauble/tests/lang.rs | 126 ------------------------------------ 6 files changed, 154 insertions(+), 132 deletions(-) delete mode 100644 bauble/tests/lang.rs diff --git a/OVERVIEW.md b/OVERVIEW.md index b77cd98..bfc1678 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -106,6 +106,9 @@ A reference is a Bauble object which may not be present in the current module. A Bauble object's value may use a reference to avoid code duplication in the local file (referencing a previous object to use its values), or to reference the value of a Bauble object from a different file (module). References are specified using a bauble `Path`. +Bauble does expose the builtin type for references, [`Ref`], which can be used for convenience to represent references from Bauble in Rust. +It is not required to use this type to represent references, custom types which are capable of parsing reference values are equal to the builtin [`Ref`] type, it is just a convenience. + # Sub-objects A single Bauble object may contain sub-objects. diff --git a/bauble/src/builtin.rs b/bauble/src/builtin.rs index 6037203..005fb77 100644 --- a/bauble/src/builtin.rs +++ b/bauble/src/builtin.rs @@ -7,7 +7,20 @@ use std::{cell::UnsafeCell, fmt::Debug, marker::PhantomData}; /// The builtin reference type. /// -/// Corresponds to a reference to `T` in Bauble. +/// This corresponds to the type used internally by Bauble for objects which are +/// references. That is to say, if you were to write +/// ``` +/// own: MyType = MyType { ... } +/// ref = $own +/// ``` +/// then `ref` has the type `Ref` and the value `own`. +/// +/// This type is not required for parsing or using references. Custom types like +/// `MyRefType` can still be made to be used to parse various references and be +/// used in Bauble. This is a convenience type and is capable of handling various +/// references without needing to be registered to Bauble manually. Besides +/// convenience this type offers no advantage, and if special behaviour is requred +/// for parsing and handling references, prefer to use a custom type from Rust. /// /// `S` is the inner representation used for `TypePath`. pub struct Ref { diff --git a/bauble/src/lib.rs b/bauble/src/lib.rs index 9bdc957..e9ffda4 100644 --- a/bauble/src/lib.rs +++ b/bauble/src/lib.rs @@ -41,12 +41,12 @@ pub mod private { #[macro_export] macro_rules! bauble_test { ( [$($ty:ty),* $(,)?] $source:literal [$($test_value:expr),* $(,)?]) => { - $crate::bauble_test!(__TEST_CTX [$($ty),*] [$source] [$($test_value),*]) + { $crate::bauble_test!(__TEST_CTX [$($ty),*] [$source] [$($test_value),*]); } }; ( [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => { - $crate::bauble_test!(__TEST_CTX [$($ty),*] [$($source),*] [$($test_value),*]) + { $crate::bauble_test!(__TEST_CTX [$($ty),*] [$($source),*] [$($test_value),*]); } }; - ($ctx_static:ident [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => {{ + ($ctx_static:ident [$($ty:ty),* $(,)?] [$($source:literal),* $(,)?] [$($test_value:expr),* $(,)?]) => { static $ctx_static: std::sync::OnceLock> = std::sync::OnceLock::new(); { let file_path = $crate::path::TypePath::new("test").unwrap(); @@ -114,5 +114,5 @@ macro_rules! bauble_test { compare_objects(objects); compare_objects(re_objects); } - }}; + }; } diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index 3f5b12b..1c71e38 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -818,7 +818,13 @@ pub(crate) fn register_assets( // To register an asset we need to determine its type. let ty = if let Some(ty) = &binding.type_path // If the value is a reference, then an explicit type should - // still be delayed. + // still be delayed. The reason why it should be delayed is + // because the type of the reference `Ref`, where `T` is + // the type of the referenced object, may not exist at this + // point, and as such trying to resolve the type may cause + // issues because the type is not known and depends on the + // order the reference was created compared to the + // referenced object, which is undesired behaviour. && !matches!(binding.value.value.value, Value::Ref(_)) { symbols.resolve_type(ty) diff --git a/bauble/tests/integration.rs b/bauble/tests/integration.rs index 7ceb0ab..c482ec7 100644 --- a/bauble/tests/integration.rs +++ b/bauble/tests/integration.rs @@ -2,6 +2,7 @@ use bauble::Bauble; use bauble::BaubleContext; use bauble::Object; +use bauble::Ref; use bauble::path::{TypePath, TypePathElem}; #[derive(Bauble, PartialEq, Debug)] @@ -377,3 +378,128 @@ fn reference_with_use() { &[a, b], ); } + +#[derive(Bauble, PartialEq, Debug)] +struct RefTest { + x: i32, + y: u32, +} + +#[test] +pub fn ref_implicit_type() { + bauble::bauble_test!( + [RefTest] + "test = lang::Test{ x: -5, y: 5 }\n\ + r = $test" + [ + Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + ] + ); + + bauble::bauble_test!( + [RefTest] + "r = $test::test\n\ + test = lang::Test{ x: -5, y: 5 }" + [ + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + Test { x: -5, y: 5 }, + ] + ); +} + +#[test] +pub fn ref_explicit_type() { + bauble::bauble_test!( + [RefTest] + "test = lang::Test{ x: -2, y: 2 }\n\ + r: Ref = $test" + [ + Test { x: -2, y: 2 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + ] + ); + + bauble::bauble_test!( + [RefTest] + "r: Ref = $test::test\n\ + test = lang::Test{ x: -2, y: 2 }" + [ + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + Test { x: -2, y: 2 }, + ] + ); +} + +#[test] +pub fn ref_explicit_type_multiple_files() { + bauble::bauble_test!( + [RefTest] + [ + "test = lang::Test{ x: -5, y: 5 }", + "r: Ref = $test::test" + ] + [ + Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + ] + ); + + bauble::bauble_test!( + [RefTest] + [ + "r: Ref = $test::test", + "test = lang::Test{ x: -5, y: 5 }" + ] + [ + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + Test { x: -5, y: 5 }, + ] + ); +} + +#[test] +pub fn ref_implicit_type_multiple_files() { + bauble::bauble_test!( + [RefTest] + [ + "test = lang::Test{ x: -5, y: 5 }", + "r = $test::test" + ] + [ + Test { x: -5, y: 5 }, + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + ] + ); + + bauble::bauble_test!( + [RefTest] + [ + "r = $test::test", + "test = lang::Test{ x: -5, y: 5 }" + ] + [ + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + Test { x: -5, y: 5 }, + ] + ); +} + +#[test] +#[should_panic] +pub fn ref_explicit_type_incorrect() { + #[derive(Bauble, PartialEq, Eq, Debug)] + struct Incorrect(u32); + + bauble::bauble_test!( + [RefTest, Incorrect] + "i: Incorrect = Incorrect(0)\n\ + r: Ref = $test::test\n\ + test = lang::Test{ x: -2, y: 2 }" + [ + Incorrect(0), + Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), + Test { x: -2, y: 2 }, + ] + ); +} diff --git a/bauble/tests/lang.rs b/bauble/tests/lang.rs deleted file mode 100644 index bbc98d0..0000000 --- a/bauble/tests/lang.rs +++ /dev/null @@ -1,126 +0,0 @@ -use bauble::{Bauble, Ref, path::TypePath}; - -#[derive(Bauble, PartialEq, Debug)] -struct Test { - x: i32, - y: u32, -} - -#[test] -pub fn ref_implicit_type() { - bauble::bauble_test!( - [Test] - "test = lang::Test{ x: -5, y: 5 }\n\ - r = $test" - [ - Test { x: -5, y: 5 }, - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - ] - ); - - bauble::bauble_test!( - [Test] - "r = $test::test\n\ - test = lang::Test{ x: -5, y: 5 }" - [ - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - Test { x: -5, y: 5 }, - ] - ); -} - -#[test] -pub fn ref_explicit_type() { - bauble::bauble_test!( - [Test] - "test = lang::Test{ x: -2, y: 2 }\n\ - r: Ref = $test" - [ - Test { x: -2, y: 2 }, - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - ] - ); - - bauble::bauble_test!( - [Test] - "r: Ref = $test::test\n\ - test = lang::Test{ x: -2, y: 2 }" - [ - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - Test { x: -2, y: 2 }, - ] - ); -} - -#[test] -pub fn ref_explicit_type_multiple_files() { - bauble::bauble_test!( - [Test] - [ - "test = lang::Test{ x: -5, y: 5 }", - "r: Ref = $test::test" - ] - [ - Test { x: -5, y: 5 }, - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - ] - ); - - bauble::bauble_test!( - [Test] - [ - "r: Ref = $test::test", - "test = lang::Test{ x: -5, y: 5 }" - ] - [ - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - Test { x: -5, y: 5 }, - ] - ); -} - -#[test] -pub fn ref_implicit_type_multiple_files() { - bauble::bauble_test!( - [Test] - [ - "test = lang::Test{ x: -5, y: 5 }", - "r = $test::test" - ] - [ - Test { x: -5, y: 5 }, - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - ] - ); - - bauble::bauble_test!( - [Test] - [ - "r = $test::test", - "test = lang::Test{ x: -5, y: 5 }" - ] - [ - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - Test { x: -5, y: 5 }, - ] - ); -} - -#[test] -#[should_panic] -pub fn ref_explicit_type_incorrect() { - #[derive(Bauble, PartialEq, Eq, Debug)] - struct Incorrect(u32); - - bauble::bauble_test!( - [Test, Incorrect] - "i: Incorrect = Incorrect(0)\n\ - r: Ref = $test::test\n\ - test = lang::Test{ x: -2, y: 2 }" - [ - Incorrect(0), - Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), - Test { x: -2, y: 2 }, - ] - ); -} From 7cac810963a3a417dcb299d992ecbd3f316ecf6d Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:22:55 +0100 Subject: [PATCH 08/11] fixed up doc comment --- bauble/src/traits.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bauble/src/traits.rs b/bauble/src/traits.rs index 49608d2..05cb907 100644 --- a/bauble/src/traits.rs +++ b/bauble/src/traits.rs @@ -281,8 +281,8 @@ impl BaubleAllocator<'_> for DefaultAllocator { pub trait Bauble<'a, A: BaubleAllocator<'a> = DefaultAllocator>: Sized + 'static { /// # DON'T CALL THIS, call `TypeRegistry::get_or_register_type` instead. /// - /// Constructs a builtin type. A builtin type is a type which is not constructed by Bauble during parsing and - /// might warrant additional rules. + /// Constructs a builtin type. A builtin type is a type which is not constructed by Bauble during loading + /// of objects and might warrant additional rules. fn builtin(#[expect(unused)] registry: &mut types::TypeRegistry) -> Option { None } From b087c04d617174c320120b3c33122b814136cd0e Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:30:12 +0100 Subject: [PATCH 09/11] fixed incorrectly pasted tests --- bauble/tests/integration.rs | 50 ++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/bauble/tests/integration.rs b/bauble/tests/integration.rs index c482ec7..f1f0e11 100644 --- a/bauble/tests/integration.rs +++ b/bauble/tests/integration.rs @@ -379,17 +379,11 @@ fn reference_with_use() { ); } -#[derive(Bauble, PartialEq, Debug)] -struct RefTest { - x: i32, - y: u32, -} - #[test] pub fn ref_implicit_type() { bauble::bauble_test!( - [RefTest] - "test = lang::Test{ x: -5, y: 5 }\n\ + [Test] + "test = integration::Test{ x: -5, y: 5 }\n\ r = $test" [ Test { x: -5, y: 5 }, @@ -398,9 +392,9 @@ pub fn ref_implicit_type() { ); bauble::bauble_test!( - [RefTest] + [Test] "r = $test::test\n\ - test = lang::Test{ x: -5, y: 5 }" + test = integration::Test{ x: -5, y: 5 }" [ Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -5, y: 5 }, @@ -411,9 +405,9 @@ pub fn ref_implicit_type() { #[test] pub fn ref_explicit_type() { bauble::bauble_test!( - [RefTest] - "test = lang::Test{ x: -2, y: 2 }\n\ - r: Ref = $test" + [Test] + "test = integration::Test{ x: -2, y: 2 }\n\ + r: Ref = $test" [ Test { x: -2, y: 2 }, Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), @@ -421,9 +415,9 @@ pub fn ref_explicit_type() { ); bauble::bauble_test!( - [RefTest] - "r: Ref = $test::test\n\ - test = lang::Test{ x: -2, y: 2 }" + [Test] + "r: Ref = $test::test\n\ + test = integration::Test{ x: -2, y: 2 }" [ Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), Test { x: -2, y: 2 }, @@ -434,10 +428,10 @@ pub fn ref_explicit_type() { #[test] pub fn ref_explicit_type_multiple_files() { bauble::bauble_test!( - [RefTest] + [Test] [ - "test = lang::Test{ x: -5, y: 5 }", - "r: Ref = $test::test" + "test = integration::Test{ x: -5, y: 5 }", + "r: Ref = $test::test" ] [ Test { x: -5, y: 5 }, @@ -446,10 +440,10 @@ pub fn ref_explicit_type_multiple_files() { ); bauble::bauble_test!( - [RefTest] + [Test] [ - "r: Ref = $test::test", - "test = lang::Test{ x: -5, y: 5 }" + "r: Ref = $test::test", + "test = integration::Test{ x: -5, y: 5 }" ] [ Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), @@ -461,9 +455,9 @@ pub fn ref_explicit_type_multiple_files() { #[test] pub fn ref_implicit_type_multiple_files() { bauble::bauble_test!( - [RefTest] + [Test] [ - "test = lang::Test{ x: -5, y: 5 }", + "test = integration::Test{ x: -5, y: 5 }", "r = $test::test" ] [ @@ -473,10 +467,10 @@ pub fn ref_implicit_type_multiple_files() { ); bauble::bauble_test!( - [RefTest] + [Test] [ "r = $test::test", - "test = lang::Test{ x: -5, y: 5 }" + "test = integration::Test{ x: -5, y: 5 }" ] [ Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), @@ -492,10 +486,10 @@ pub fn ref_explicit_type_incorrect() { struct Incorrect(u32); bauble::bauble_test!( - [RefTest, Incorrect] + [Test, Incorrect] "i: Incorrect = Incorrect(0)\n\ r: Ref = $test::test\n\ - test = lang::Test{ x: -2, y: 2 }" + test = integration::Test{ x: -2, y: 2 }" [ Incorrect(0), Ref::::from_path(TypePath::new_unchecked("test::test").to_owned()), From eb38e53e2d79fd69b08f815c16ae959dcf240ccf Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:55:06 +0100 Subject: [PATCH 10/11] fixed issue with checking for incorrect type when resolving references --- bauble/src/traits.rs | 4 +-- bauble/src/value/mod.rs | 54 ++++++++++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/bauble/src/traits.rs b/bauble/src/traits.rs index 05cb907..b134bf7 100644 --- a/bauble/src/traits.rs +++ b/bauble/src/traits.rs @@ -281,8 +281,8 @@ impl BaubleAllocator<'_> for DefaultAllocator { pub trait Bauble<'a, A: BaubleAllocator<'a> = DefaultAllocator>: Sized + 'static { /// # DON'T CALL THIS, call `TypeRegistry::get_or_register_type` instead. /// - /// Constructs a builtin type. A builtin type is a type which is not constructed by Bauble during loading - /// of objects and might warrant additional rules. + /// Constructs a builtin type. A builtin type is a type which is not constructed by Bauble during context + /// construction and might warrant additional rules. fn builtin(#[expect(unused)] registry: &mut types::TypeRegistry) -> Option { None } diff --git a/bauble/src/value/mod.rs b/bauble/src/value/mod.rs index 1c71e38..8efcd21 100644 --- a/bauble/src/value/mod.rs +++ b/bauble/src/value/mod.rs @@ -688,27 +688,53 @@ pub(crate) fn resolve_delayed( } } && let Some((ty, _)) = &r.asset { + // TODO: for now, it is assumed all references which explicitly + // specify their inner type should have that inner type resolved + // by this point. If that is not the case, this should be a + // nested reference, and in the case it is a nested reference + // this code can optionally work or not work, depending on the + // order the references appear in Bauble. This is unpredictable + // and weird, it is likely best to simply error in general if it + // is noticed that nested references are explictly being written + // in bauble at all, and they should instead prefer having their + // types implicitly solved. if let Some(desired_ty) = &d.expected_ty_path { let span = desired_ty.span; - if let Some(desired_ty) = match &desired_ty.value { + let path = &desired_ty.value; + let Some(desired_ty) = (match path { PathKind::Direct(path) => ctx.get_ref(path.borrow()), PathKind::Indirect(path, ident) => { ctx.ref_with_ident(path.borrow(), ident.borrow()) } - } && let Some(desired_ty) = desired_ty.ty - { - if desired_ty != *ty { - errors.push( - ConversionError::ExpectedExactType { - expected: desired_ty, - got: Some(*ty), - } - .spanned(span), - ); - } - } else { - return true; + }) else { + errors.push( + ConversionError::Custom(crate::CustomError::new(format!( + "Invalid explicit reference path '{path}'" + ))) + .spanned(span), + ); + return false; }; + + let Some(desired_ty) = desired_ty.ty else { + errors.push( + ConversionError::Custom(crate::CustomError::new(format!( + "Expected path to refer to type '{path}'", + ))) + .spanned(span), + ); + return false; + }; + + if desired_ty != *ty { + errors.push( + ConversionError::ExpectedExactType { + expected: desired_ty, + got: Some(*ty), + } + .spanned(span), + ); + } } ctx.register_asset(d.asset.value.borrow(), *ty); false From 3aad602df9d853c7e2a4857fa4367eec3decc1d7 Mon Sep 17 00:00:00 2001 From: Hazelwiss <67006455+hazelwiss@users.noreply.github.com> Date: Mon, 27 Oct 2025 20:29:27 +0100 Subject: [PATCH 11/11] ignoring codeblock for tests --- bauble/src/builtin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bauble/src/builtin.rs b/bauble/src/builtin.rs index 005fb77..da2d321 100644 --- a/bauble/src/builtin.rs +++ b/bauble/src/builtin.rs @@ -9,7 +9,7 @@ use std::{cell::UnsafeCell, fmt::Debug, marker::PhantomData}; /// /// This corresponds to the type used internally by Bauble for objects which are /// references. That is to say, if you were to write -/// ``` +/// ```ignore /// own: MyType = MyType { ... } /// ref = $own /// ```