Skip to content
3 changes: 3 additions & 0 deletions OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
88 changes: 88 additions & 0 deletions bauble/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::{
Bauble, BaubleAllocator, ToRustErrorKind, Val, Value,
path::TypePath,
types::{Type, TypeRegistry},
};
use std::{cell::UnsafeCell, fmt::Debug, marker::PhantomData};

/// The builtin reference type.
///
/// 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
/// ```
/// then `ref` has the type `Ref<MyType>` 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<T, S = String> {
/// The path to the referenced asset.
pub path: TypePath<S>,
/// Invariant over `T`.
_mark: PhantomData<UnsafeCell<T>>,
}

impl<T, S> Ref<T, S> {
/// Create a reference from the specified path. The path must not be valid.
pub fn from_path(path: TypePath<S>) -> Self {
Self {
path,
_mark: PhantomData,
}
}
}

impl<T, S: PartialEq> PartialEq for Ref<T, S> {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<T, S: Eq> Eq for Ref<T, S> {}

impl<T, S: Debug> Debug for Ref<T, S> {
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<T, A::TypePathInner> {
fn builtin(registry: &mut TypeRegistry) -> Option<crate::types::TypeId> {
let inner = registry.get_or_register_type::<T, A>();
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<<A as BaubleAllocator<'b>>::Out<Self>, 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 },
)),
}
}
}
4 changes: 2 additions & 2 deletions bauble/src/context.rs
Original file line number Diff line number Diff line change
@@ -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<String>;
Expand Down Expand Up @@ -503,6 +502,7 @@ impl BaubleContext {
/// Returns ID of internal Ref type for `ty`.
pub fn register_asset(&mut self, path: TypePath<&str>, ty: TypeId) -> 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);
ref_ty
}
Expand Down
13 changes: 10 additions & 3 deletions bauble/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![feature(iterator_try_collect, let_chains, ptr_metadata)]
#![warn(missing_docs)]

mod builtin;
mod context;
mod error;
mod parse;
Expand All @@ -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};
Expand All @@ -39,9 +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),*]); }
};
($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::RwLock<$crate::BaubleContext>> = std::sync::OnceLock::new();
{
let file_path = $crate::path::TypePath::new("test").unwrap();
Expand All @@ -52,7 +57,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)
});
Expand Down
19 changes: 19 additions & 0 deletions bauble/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,26 @@ 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<T>(&self, value: T) -> Self::Out<T>;
/// # Safety
/// If validated an item must be placed within the same allocator.
unsafe fn validate<T>(&self, value: Self::Out<T>) -> Result<T, ToRustError>;

/// Create a type path from this allocator.
fn wrap_type_path(&self, path: TypePath) -> Self::Out<TypePath<Self::TypePathInner>>;
}

/// The standard Rust allocator when used by Bauble.
pub struct DefaultAllocator;

impl BaubleAllocator<'_> for DefaultAllocator {
type Out<T> = T;
type TypePathInner = String;

unsafe fn wrap<T>(&self, value: T) -> Self::Out<T> {
value
Expand All @@ -264,10 +271,22 @@ impl BaubleAllocator<'_> for DefaultAllocator {
unsafe fn validate<T>(&self, value: Self::Out<T>) -> Result<T, ToRustError> {
Ok(value)
}

fn wrap_type_path(&self, path: TypePath) -> TypePath<Self::TypePathInner> {
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 context
/// construction and might warrant additional rules.
fn builtin(#[expect(unused)] registry: &mut types::TypeRegistry) -> Option<TypeId> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to add builtin types to avoid construct_type for Ref<T>

None
}

/// # DON'T CALL THIS, call `TypeRegistry::get_or_register_type` instead.
///
/// Constructs a reflection type that bauble uses to parse and resolve types.
Expand Down
30 changes: 17 additions & 13 deletions bauble/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,13 +420,14 @@ 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 {
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"),
Expand All @@ -436,14 +437,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")
Expand Down Expand Up @@ -527,9 +528,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
})
Expand Down Expand Up @@ -567,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);

Expand Down Expand Up @@ -666,21 +667,25 @@ 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::<T, A>();

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
}
Some(id) => id,
None => self.register_type(|this, id| {
this.type_from_rust.insert(std::any::TypeId::of::<T>(), 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
}),
Expand Down Expand Up @@ -873,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::Primitive(a), TypeKind::Primitive(b)) => a == b,

(_, TypeKind::Generic(types)) => types.contains(output_id),
Expand Down
40 changes: 40 additions & 0 deletions bauble/src/types/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,10 @@ impl<S: AsRef<str>> TypePath<S> {
== 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.
///
Expand Down Expand Up @@ -487,6 +491,42 @@ impl<S: AsRef<str>> TypePath<S> {
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<TypePath<String>> {
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<String> {
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> {
Expand Down
2 changes: 1 addition & 1 deletion bauble/src/value/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ 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)?)
}
Expand Down
Loading
Loading