Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
562668a
Drop function result
oscartbeaumont Mar 3, 2026
4a51f9d
`Result` impl
oscartbeaumont Mar 3, 2026
9eed099
impl `Result<T, E>`
oscartbeaumont Mar 3, 2026
18662cc
Merge branch 'main' into drop-function-result
oscartbeaumont Mar 3, 2026
1efdeb2
wip: new macros
oscartbeaumont Mar 3, 2026
8b71905
wip
oscartbeaumont Mar 3, 2026
d284110
wip
oscartbeaumont Mar 3, 2026
07c8ccc
`Primitives::str`
oscartbeaumont Mar 3, 2026
f842633
wip: cleanup impls
oscartbeaumont Mar 3, 2026
b1555ec
failefix
oscartbeaumont Mar 3, 2026
80e807b
wip
oscartbeaumont Mar 3, 2026
9adb758
fix
oscartbeaumont Mar 3, 2026
0a071e9
wip
oscartbeaumont Mar 3, 2026
307276f
wip
oscartbeaumont Mar 3, 2026
46ee895
wip
oscartbeaumont Mar 3, 2026
4401592
cleanup macro
oscartbeaumont Mar 3, 2026
e575414
fix
oscartbeaumont Mar 3, 2026
02dad23
set module path on built-in impls
oscartbeaumont Mar 4, 2026
7e8c3ae
wip
oscartbeaumont Mar 4, 2026
b21ec3f
wip: restoring legacy impls
oscartbeaumont Mar 5, 2026
deabaad
wip
oscartbeaumont Mar 5, 2026
42ce28d
Claude go brrrr
oscartbeaumont Mar 5, 2026
2fa3a82
more cleanup of impls
oscartbeaumont Mar 5, 2026
14536f3
all legacy impls compiling
oscartbeaumont Mar 5, 2026
988554f
drop derive requirement
oscartbeaumont Mar 5, 2026
5aa3c3d
revise test results
oscartbeaumont Mar 5, 2026
afdc958
give built in NDT's impls a name
oscartbeaumont Mar 5, 2026
92a1c36
generic properly
oscartbeaumont Mar 5, 2026
7efda81
fix stack overflow on certain recursive types
oscartbeaumont Mar 5, 2026
596d9e5
fix regressions?
oscartbeaumont Mar 5, 2026
0e333f1
improvements to snapshots
oscartbeaumont Mar 5, 2026
497f6da
fiz
oscartbeaumont Mar 5, 2026
fc59306
Merge branch 'main' into drop-function-result
oscartbeaumont Mar 10, 2026
8bd8aa9
wip: overhaul generics in DT layer
oscartbeaumont Mar 10, 2026
a666504
cleanup
oscartbeaumont Mar 10, 2026
c009425
move to `Vec<T>` instead of trying to `Cow` it
oscartbeaumont Mar 10, 2026
16b01b2
wip: massive generic overhaul
oscartbeaumont Mar 10, 2026
f18908b
cleanup generic handling in macro
oscartbeaumont Mar 10, 2026
6f547d8
drop `generic_marker!` in favor of `generics` module
oscartbeaumont Mar 10, 2026
2075e63
nit
oscartbeaumont Mar 10, 2026
6a8d58b
fix clippy
oscartbeaumont Mar 10, 2026
7ca764a
fix tests
oscartbeaumont Mar 10, 2026
e3a9f5f
update trybuild output
oscartbeaumont Mar 10, 2026
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
113 changes: 113 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ unwrap_used = { level = "warn", priority = -1 }
panic = { level = "warn", priority = -1 }
todo = { level = "warn", priority = -1 }
panic_in_result_fn = { level = "warn", priority = -1 }

# I hate these. For internal code I don't care.
too_many_arguments = { level = "allow", priority = 0 }
type_complexity = { level = "allow", priority = 0 }

[profile.dev.package]
insta.opt-level = 3
Expand Down
8 changes: 4 additions & 4 deletions specta-jsonschema/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn schema_object_to_datatype(obj: &JsonMap<String, Value>) -> Result<DataType, E
// Handle const values (literals)
if obj.get("const").is_some() {
// Specta does not currently expose a direct literal DataType variant.
return Ok(DataType::Primitive(Primitive::String));
return Ok(DataType::Primitive(Primitive::str));
}

// Handle enum values (for string enums)
Expand Down Expand Up @@ -112,7 +112,7 @@ fn instance_type_name_to_datatype(
Ok(DataType::Tuple(Tuple::new(vec![])))
}
"boolean" => Ok(DataType::Primitive(Primitive::bool)),
"string" => Ok(DataType::Primitive(Primitive::String)),
"string" => Ok(DataType::Primitive(Primitive::str)),
"number" => {
if let Some(format) = obj.get("format").and_then(Value::as_str) {
match format {
Expand Down Expand Up @@ -190,13 +190,13 @@ fn instance_type_name_to_datatype(
Value::Object(_) => {
let value_dt = value_to_datatype(additional)?;
return Ok(DataType::Map(Map::new(
DataType::Primitive(Primitive::String),
DataType::Primitive(Primitive::str),
value_dt,
)));
}
Value::Bool(true) => {
return Ok(DataType::Map(Map::new(
DataType::Primitive(Primitive::String),
DataType::Primitive(Primitive::str),
Struct::named().build(),
)));
}
Expand Down
12 changes: 4 additions & 8 deletions specta-jsonschema/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,18 @@ pub fn datatype_to_schema(
Reference::Opaque(_) => Err(Error::UnsupportedDataType(
"Opaque references are not supported by JSON Schema exporter".to_string(),
)),
// JsonSchema doesn't have generics, so we use a placeholder,
// This should typically be resolved before export.
Reference::Generic(_) => Ok(json!({})), // Empty schema accepts anything
}
}

// Generic
DataType::Generic(_g) => {
// JSON Schema doesn't have generics, so we use a placeholder
// This should typically be resolved before export
Ok(json!({})) // Empty schema accepts anything
}
}
}

fn primitive_to_schema(p: &Primitive) -> Value {
match p {
Primitive::bool => json!({"type": "boolean"}),
Primitive::String => json!({"type": "string"}),
Primitive::str => json!({"type": "string"}),
Primitive::char => json!({"type": "string", "minLength": 1, "maxLength": 1}),

// Integers
Expand Down
2 changes: 1 addition & 1 deletion specta-kotlin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
// fn datatype(t: &DataType) -> Result<String, String> {
// Ok(match t {
// DataType::Primitive(p) => match p {
// Primitive::String => "String",
// Primitive::str => "String",
// Primitive::char => "Char",
// Primitive::i8 => "Byte",
// Primitive::i16 => "Short",
Expand Down
88 changes: 41 additions & 47 deletions specta-macros/src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,34 +142,47 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
&used_generic_types,
);

let shadow_generics = {
let g = generics.params.iter().map(|param| match param {
// Pulled from outside
GenericParam::Lifetime(_) | GenericParam::Const(_) => quote!(),
// We shadow the generics to replace them.
GenericParam::Type(t) => {
let ident = &t.ident;
let placeholder_ident = format_ident!("PLACEHOLDER_{}", t.ident);
quote!(type #ident = datatype::GenericPlaceholder<#placeholder_ident>;)
}
});

quote!(#(#g)*)
};

let generic_placeholders = generics.params.iter().filter_map(|param| match param {
let (generic_placeholders, shadow_generics): (Vec<_>, Vec<_>) = generics.params.iter().filter_map(|param| match param {
GenericParam::Lifetime(_) | GenericParam::Const(_) => None,
GenericParam::Type(t) => {
let ident = format_ident!("PLACEHOLDER_{}", t.ident);
let ident_str = t.ident.to_string();
Some(quote!(
pub struct #ident;
impl datatype::ConstGenericPlaceholder for #ident {
const PLACEHOLDER: &'static str = #ident_str;
let ident = &t.ident;
let placeholder_ident = format_ident!("PLACEHOLDER_{ident}");
Some((quote!(
pub struct #placeholder_ident;
impl #crate_ref::Type for #placeholder_ident {
fn definition(_: &mut #crate_ref::TypeCollection) -> datatype::DataType {
datatype::GenericReference::new::<Self>().into()
}
}
))
), quote!(type #ident = #placeholder_ident;)))
}
});
}).unzip();

let (generics_for_ndt, generics_for_ref): (Vec<_>, Vec<_>) = generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Lifetime(_) | GenericParam::Const(_) => None,
GenericParam::Type(t) => {
let i = &t.ident;
let placeholder_ident = format_ident!("PLACEHOLDER_{}", t.ident);
if !used_generic_types.iter().any(|used| used == i) {
return None;
}
let i_str = i.to_string();
Some((
quote!((
#crate_ref::datatype::GenericReference::new::<#placeholder_ident>(),
Cow::Borrowed(#i_str),
)),
quote!((
#crate_ref::datatype::GenericReference::new::<#placeholder_ident>(),
<#i as #crate_ref::Type>::definition(types),
)),
))
}
})
.unzip();

let collect = (cfg!(feature = "DO_NOT_USE_collect") && container_attrs.collect.unwrap_or(true))
.then(|| {
Expand All @@ -194,26 +207,6 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
let inline = container_attrs.inline || container_attrs.r#type.is_some();
let deprecated = container_attrs.common.deprecated_as_tokens();

let reference_generics = generics.params.iter().filter_map(|param| match param {
GenericParam::Lifetime(_) | GenericParam::Const(_) => None,
GenericParam::Type(t) => {
let i = &t.ident;
if !used_generic_types.iter().any(|used| used == i) {
return None;
}
let i_str = i.to_string();
Some(quote!((#crate_ref::datatype::Generic::new(#i_str), <#i as #crate_ref::Type>::definition(types))))
}
});

let definition_generics = generics.params.iter().filter_map(|p| match p {
GenericParam::Type(t) => {
let ident = t.ident.to_string();
Some(quote!(std::borrow::Cow::Borrowed(#ident).into()))
}
_ => None,
});

Ok(quote! {
#[allow(non_camel_case_types)]
const _: () = {
Expand All @@ -226,9 +219,11 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
#(#generic_placeholders)*

static SENTINEL: &str = concat!(module_path!(), "::", stringify!(#raw_ident));
static GENERICS: &[(datatype::GenericReference, Cow<'static, str>)] = &[#(#generics_for_ndt),*];
datatype::DataType::Reference(
datatype::NamedDataType::init_with_sentinel(
vec![#(#reference_generics),*],
GENERICS,
vec![#(#generics_for_ref),*],
#inline,
types,
SENTINEL,
Expand All @@ -237,9 +232,8 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
ndt.set_docs(Cow::Borrowed(#comments));
ndt.set_deprecated(#deprecated);
ndt.set_module_path(Cow::Borrowed(module_path!()));
*ndt.generics_mut() = vec![#(#definition_generics),*];
ndt.set_ty({
#shadow_generics
#(#shadow_generics)*

#dt_expr
});
Expand Down
2 changes: 1 addition & 1 deletion specta-openapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub fn to_openapi(typ: &DataType) -> ReferenceOr<Schema> {
schema_data,
schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `bigint`
}),
primitive_def!(String char) => ReferenceOr::Item(Schema {
primitive_def!(str char) => ReferenceOr::Item(Schema {
schema_data,
schema_kind: SchemaKind::Type(Type::String(StringType::default())), // TODO: Configure string type. Ts: `string`
}),
Expand Down
Loading
Loading