Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Add in support for duplicate type names across modules #72

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ name = "export"
required-features = ["export"]

[features]
default = ["serde", "typescript"]
default = ["serde", "typescript", "export"]

##! Internal Features
## Support for exporting the types of Rust functions.
Expand Down
19 changes: 15 additions & 4 deletions examples/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@ use specta::{
#[derive(Type)]
pub struct TypeOne {
pub field1: String,
pub field2: TypeTwo,
pub field2: two::TypeTwo,
pub field3: two::TypeThree,
}

#[derive(Type)]
pub struct TypeTwo {
pub my_field: String,
mod two {
use super::*;

#[derive(Type)]
pub struct TypeThree {
pub field1: String,
// pub field2: TypeTwo,
}

#[derive(Type)]
pub struct TypeTwo {
pub my_field: String,
}
}

fn main() {
Expand Down
3 changes: 2 additions & 1 deletion macros/src/data_type_from/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
generics: vec![],
fields: vec![#(#fields),*],
tag: None,
}
module_path: None,
}
}
}

Expand Down
1 change: 1 addition & 0 deletions macros/src/type/attr/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct ContainerAttr {
pub export: Option<bool>, // Option is used because if not explicitly set, we enable it
pub doc: Vec<String>,
pub deprecated: Option<String>,
pub module_path: Option<&'static str>,
}

impl_parse! {
Expand Down
3 changes: 3 additions & 0 deletions macros/src/type/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ pub fn parse_enum(
fields: vec![#(#fields),*],
generics: vec![],
tag: None,
module_path: Some(<Self as #crate_ref::Type>::MODULE_PATH)
}))
}
},
Expand Down Expand Up @@ -245,7 +246,9 @@ pub fn parse_enum(
name: #name,
sid: SID,
generics: vec![#(#reference_generics),*],
module_path: <Self as #crate_ref::Type>::MODULE_PATH
})

},
can_flatten,
))
Expand Down
10 changes: 10 additions & 0 deletions macros/src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,12 @@ pub fn derive(
const SID: #crate_name::TypeSid = #crate_name::sid!(@with_specta_path; #name; #crate_name);
const IMPL_LOCATION: #crate_name::ImplLocation = #crate_name::impl_location!(@with_specta_path; #crate_name);

const PATH_MACRO: &str = module_path!();

#[automatically_derived]
#type_impl_heading {
const MODULE_PATH: &'static str = module_path!();

fn inline(opts: #crate_name::DefOpts, generics: &[#crate_name::DataType]) -> std::result::Result<#crate_name::DataType, #crate_name::ExportError> {
Ok(#crate_name::DataType::Named(<Self as #crate_name::NamedType>::named_data_type(opts, generics)?))
}
Expand Down Expand Up @@ -190,6 +194,11 @@ pub fn named_data_type_wrapper(
None => quote!(None),
};

let module_path = match &container_attrs.module_path {
Some(path) => quote!(Some(#path)),
None => quote!(Some(PATH_MACRO)),
};

quote! {
#crate_ref::NamedDataType {
name: #name,
Expand All @@ -198,6 +207,7 @@ pub fn named_data_type_wrapper(
comments: #comments,
export: #should_export,
deprecated: #deprecated,
module_path: #module_path,
item: #t
}
}
Expand Down
2 changes: 2 additions & 0 deletions macros/src/type/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ pub fn parse_struct(
generics: vec![#(#definition_generics),*],
fields: vec![#(#fields),*],
tag: #tag,
module_path: Some(PATH_MACRO),
}
)
},
Expand Down Expand Up @@ -290,6 +291,7 @@ pub fn parse_struct(
name: #name,
sid: SID,
generics: vec![#(#reference_generics),*],
module_path: <Self as #crate_ref::Type>::MODULE_PATH
})
}
};
Expand Down
3 changes: 3 additions & 0 deletions src/datatype/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ pub struct NamedDataType {
pub deprecated: Option<&'static str>,
/// the actual type definition.
pub item: NamedDataTypeItem,
/// A string containing the module path for the given struct. Used for namespace-based exporting
pub module_path: Option<&'static str>,
}

impl From<NamedDataType> for DataType {
Expand Down Expand Up @@ -105,6 +107,7 @@ pub struct DataTypeReference {
pub name: &'static str,
pub sid: TypeSid,
pub generics: Vec<DataType>,
pub module_path: &'static str,
}

/// A generic parameter to another type.
Expand Down
2 changes: 2 additions & 0 deletions src/datatype/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct ObjectType {
pub generics: Vec<&'static str>,
pub fields: Vec<ObjectField>,
pub tag: Option<&'static str>,
pub module_path: Option<&'static str>,
}

impl ObjectType {
Expand All @@ -37,6 +38,7 @@ impl ObjectType {
comments: &[],
export: None,
deprecated: None,
module_path: self.module_path,
item: NamedDataTypeItem::Object(self),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/datatype/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl TupleType {
export: None,
deprecated: None,
item: NamedDataTypeItem::Tuple(self),
module_path: None,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/export.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ts::{ExportConfiguration, TsExportError};
use crate::ts::{ExportConfiguration, ModuleExportBehavior, TsExportError};
use crate::*;
use once_cell::sync::Lazy;
use std::collections::{BTreeMap, BTreeSet};
Expand Down Expand Up @@ -46,7 +46,7 @@ pub fn ts_with_cfg(path: &str, conf: &ExportConfiguration) -> Result<(), TsExpor
if let Some((existing_sid, existing_impl_location)) =
map.insert(dt.name, (sid, dt.impl_location))
{
if existing_sid != sid {
if existing_sid != sid && conf.modules == ModuleExportBehavior::Disabled {
return Err(TsExportError::DuplicateTypeName(
dt.name,
dt.impl_location,
Expand Down
7 changes: 7 additions & 0 deletions src/lang/ts/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ pub enum BigIntExportBehavior {
FailWithReason(&'static str),
}

#[derive(Default, PartialEq, Eq)]
pub enum ModuleExportBehavior {
Enabled,
#[default]
Disabled,
}

/// The signature for a function responsible for exporting Typescript comments.
pub type CommentFormatterFn = fn(&[&str]) -> String;

Expand Down
10 changes: 9 additions & 1 deletion src/lang/ts/export_config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{comments, BigIntExportBehavior, CommentFormatterFn};
use super::{comments, BigIntExportBehavior, CommentFormatterFn, ModuleExportBehavior};

/// Options for controlling the behavior of the Typescript exporter.
pub struct ExportConfiguration {
/// How BigInts should be exported.
pub(crate) bigint: BigIntExportBehavior,
pub(crate) modules: ModuleExportBehavior,
/// How comments should be rendered.
pub(crate) comment_exporter: Option<CommentFormatterFn>,
/// Whether to export types by default.
Expand All @@ -24,6 +25,12 @@ impl ExportConfiguration {
self
}

/// Configure the module handling behavior
pub fn modules(mut self, modules: ModuleExportBehavior) -> Self {
self.modules = modules;
self
}

/// Configure a function which is responsible for styling the comments to be exported
pub fn comment_style(mut self, exporter: Option<CommentFormatterFn>) -> Self {
self.comment_exporter = exporter;
Expand All @@ -46,6 +53,7 @@ impl Default for ExportConfiguration {
fn default() -> Self {
Self {
bigint: Default::default(),
modules: Default::default(),
comment_exporter: Some(comments::js_doc),
#[cfg(feature = "export")]
export_by_default: None,
Expand Down
74 changes: 56 additions & 18 deletions src/lang/ts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ pub fn export<T: NamedType>(conf: &ExportConfiguration) -> Result<String, TsExpo
parent_inline: false,
type_map: &mut type_map,
})?;

let result = export_named_datatype(conf, &named_data_type, &type_map);

if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
return Err(TsExportError::DuplicateTypeName(ty_name, l0, l1));
if conf.modules == ModuleExportBehavior::Disabled {
if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
return Err(TsExportError::DuplicateTypeName(ty_name, l0, l1));
}
}

result
Expand Down Expand Up @@ -62,8 +65,10 @@ pub fn inline<T: Type>(conf: &ExportConfiguration) -> Result<String, TsExportErr
&type_map,
);

if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
return Err(TsExportError::DuplicateTypeName(ty_name, l0, l1));
if conf.modules == ModuleExportBehavior::Disabled {
if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
return Err(TsExportError::DuplicateTypeName(ty_name, l0, l1));
}
}

result
Expand All @@ -88,12 +93,22 @@ fn export_datatype_inner(
name,
comments,
item,
module_path,
..
}: &NamedDataType,
type_map: &TypeDefs,
) -> Result<String, TsExportError> {
// let out_temp: Vec<String> = module_path.iter().map(|m| m.to_string()).collect();
// let mut out = out_temp.join("_");
// out.push_str(name);

let module_path = String::from(module_path.unwrap_or(""));
let mut module_path = module_path.replace("::", "_");
module_path.push('_');
module_path.push_str(name);

let ctx = ctx.with(PathItem::Type(name));
let name = sanitise_type_name(ctx.clone(), NamedLocation::Type, name)?;
let name = sanitise_type_name(ctx.clone(), NamedLocation::Type, &module_path)?;

let inline_ts = datatype_inner(
ctx.clone(),
Expand Down Expand Up @@ -133,6 +148,7 @@ fn export_datatype_inner(
.comment_exporter
.map(|v| v(comments))
.unwrap_or_default();

Ok(format!(
"{comments}export type {name}{generics} = {inline_ts}"
))
Expand Down Expand Up @@ -247,18 +263,30 @@ fn datatype_inner(
variants.dedup();
variants.join(" | ")
}
DataType::Reference(DataTypeReference { name, generics, .. }) => match &generics[..] {
[] => name.to_string(),
generics => {
let generics = generics
.iter()
.map(|v| datatype_inner(ctx.with(PathItem::Type(name)), v, type_map))
.collect::<Result<Vec<_>, _>>()?
.join(", ");

format!("{name}<{generics}>")
DataType::Reference(DataTypeReference {
name,
generics,
module_path,
..
}) => {
let mut updated_name = module_path.to_string();
updated_name = updated_name.replace("::", "_");
updated_name.push('_');
updated_name.push_str(name);

match &generics[..] {
[] => updated_name,
generics => {
let generics = generics
.iter()
.map(|v| datatype_inner(ctx.with(PathItem::Type(name)), v, type_map))
.collect::<Result<Vec<_>, _>>()?
.join(", ");

format!("{updated_name}<{generics}>")
}
}
},
}
DataType::Generic(GenericType(ident)) => ident.to_string(),
})
}
Expand All @@ -284,7 +312,12 @@ fn tuple_datatype(
fn object_datatype(
ctx: ExportContext,
name: Option<&'static str>,
ObjectType { fields, tag, .. }: &ObjectType,
ObjectType {
fields,
tag,
module_path,
..
}: &ObjectType,
type_map: &TypeDefs,
) -> Result<String, TsExportError> {
match &fields[..] {
Expand All @@ -305,9 +338,14 @@ fn object_datatype(
.map(|f| object_field_to_ts(ctx.with(PathItem::Field(f.key)), f, type_map))
.collect::<Result<Vec<_>, _>>()?;

let module_string = match module_path {
Some(path) => path.to_string(),
None => String::new(),
};

if let Some(tag) = tag {
unflattened_fields.push(format!(
"{tag}: \"{}\"",
"{module_string}{tag}: \"{}\"",
name.ok_or_else(|| TsExportError::UnableToTagUnnamedType(ctx.export_path()))?
));
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ doc_comment::doctest!("../README.md");
pub enum Any {}

impl Type for Any {
const MODULE_PATH: &'static str = module_path!();

fn inline(_: DefOpts, _: &[DataType]) -> Result<DataType, ExportError> {
Ok(DataType::Any)
}
Expand Down
Loading