Skip to content
Open
4 changes: 4 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct DerivedTS {
concrete: HashMap<Ident, Type>,
bound: Option<Vec<WherePredicate>>,
ts_enum: Option<Repr>,
is_enum: TokenStream,

export: bool,
export_to: Option<Expr>,
Expand Down Expand Up @@ -85,6 +86,7 @@ impl DerivedTS {
);
let assoc_type = generate_assoc_type(&rust_ty, &crate_rename, &generics, &self.concrete);
let name = self.generate_name_fn(&generics);
let is_enum = self.is_enum.clone();
let inline = self.generate_inline_fn();
let decl = self.generate_decl_fn(&rust_ty, &generics);
let dependencies = &self.dependencies;
Expand All @@ -95,6 +97,8 @@ impl DerivedTS {
#assoc_type
type OptionInnerType = Self;

const IS_ENUM: bool = #is_enum;

fn ident() -> String {
(#ident).to_string()
}
Expand Down
2 changes: 2 additions & 0 deletions macros/src/types/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result<DerivedTS> {
concrete: enum_attr.concrete,
bound: enum_attr.bound,
ts_enum: enum_attr.repr,
is_enum: quote!(true),
})
}

Expand Down Expand Up @@ -234,5 +235,6 @@ fn empty_enum(ts_name: Expr, enum_attr: EnumAttr) -> DerivedTS {
concrete: enum_attr.concrete,
bound: enum_attr.bound,
ts_enum: enum_attr.repr,
is_enum: quote!(false),
}
}
1 change: 1 addition & 0 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub(crate) fn named(attr: &StructAttr, ts_name: Expr, fields: &FieldsNamed) -> R
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(false),
})
}

Expand Down
7 changes: 6 additions & 1 deletion macros/src/types/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub(crate) fn newtype(
};

Ok(DerivedTS {
crate_rename,
crate_rename: crate_rename.clone(),
inline: inline_def,
inline_flattened: None,
docs: attr.docs.clone(),
Expand All @@ -51,5 +51,10 @@ pub(crate) fn newtype(
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: if field_attr.type_override.is_none() {
quote!(<#inner_ty as #crate_rename::TS>::IS_ENUM)
} else {
quote!(false)
},
})
}
1 change: 1 addition & 0 deletions macros/src/types/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub(crate) fn tuple(attr: &StructAttr, ts_name: Expr, fields: &FieldsUnnamed) ->
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(false),
})
}

Expand Down
2 changes: 2 additions & 0 deletions macros/src/types/type_as.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub(crate) fn type_as_struct(
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(<#type_as as #crate_rename::TS>::IS_ENUM),
})
}

Expand All @@ -50,5 +51,6 @@ pub(crate) fn type_as_enum(attr: &EnumAttr, ts_name: Expr, type_as: &Type) -> Re
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(<#type_as as #crate_rename::TS>::IS_ENUM),
})
}
2 changes: 2 additions & 0 deletions macros/src/types/type_override.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub(crate) fn type_override_struct(
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(false), // we dont know what the override is, so we preserve is_enum
})
}

Expand All @@ -48,5 +49,6 @@ pub(crate) fn type_override_enum(
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(true), // we dont know what the override is, so we preserve is_enum
})
}
3 changes: 3 additions & 0 deletions macros/src/types/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) fn empty_object(attr: &StructAttr, ts_name: Expr) -> DerivedTS {
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(false),
}
}

Expand All @@ -40,6 +41,7 @@ pub(crate) fn empty_array(attr: &StructAttr, ts_name: Expr) -> DerivedTS {
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(false),
}
}

Expand All @@ -58,5 +60,6 @@ pub(crate) fn null(attr: &StructAttr, ts_name: Expr) -> DerivedTS {
concrete: attr.concrete.clone(),
bound: attr.bound.clone(),
ts_enum: None,
is_enum: quote!(false),
}
}
19 changes: 10 additions & 9 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ pub trait TS {
#[doc(hidden)]
const IS_OPTION: bool = false;

#[doc(hidden)]
const IS_ENUM: bool = false;

/// JSDoc comment to describe this type in TypeScript - when `TS` is derived, docs are
/// automatically read from your doc comments or `#[doc = ".."]` attributes
fn docs() -> Option<String> {
Expand Down Expand Up @@ -986,17 +989,19 @@ impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {

fn name() -> String {
format!(
"{{ [key in {}]?: {} }}",
"{{ [key in {}]{}: {} }}",
<K as crate::TS>::name(),
<V as crate::TS>::name()
if <K as crate::TS>::IS_ENUM { "?" } else { "" },
<V as crate::TS>::name(),
)
}

fn inline() -> String {
format!(
"{{ [key in {}]?: {} }}",
"{{ [key in {}]{}: {} }}",
<K as crate::TS>::inline(),
<V as crate::TS>::inline()
if <K as crate::TS>::IS_ENUM { "?" } else { "" },
<V as crate::TS>::inline(),
)
}

Expand Down Expand Up @@ -1027,11 +1032,7 @@ impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
}

fn inline_flattened() -> String {
format!(
"({{ [key in {}]?: {} }})",
<K as crate::TS>::inline(),
<V as crate::TS>::inline()
)
format!("({})", <Self as crate::TS>::inline())
}
}

Expand Down
2 changes: 1 addition & 1 deletion ts-rs/tests/integration/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ struct C {
fn test_def() {
assert_eq!(
C::inline(),
"{ b: { c: number, a: number, b: number, } & ({ [key in string]?: number }), d: number, }"
"{ b: { c: number, a: number, b: number, } & ({ [key in string]: number }), d: number, }"
);
}
2 changes: 1 addition & 1 deletion ts-rs/tests/integration/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fn test() {

assert_eq!(
Container::decl(),
"type Container = { foo: Generic<number>, bar: Array<Generic<number>>, baz: { [key in string]?: Generic<string> }, };"
"type Container = { foo: Generic<number>, bar: Array<Generic<number>>, baz: { [key in string]: Generic<string> }, };"
);
}

Expand Down
21 changes: 18 additions & 3 deletions ts-rs/tests/integration/hashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct Hashes {
fn hashmap() {
assert_eq!(
Hashes::decl(),
"type Hashes = { map: { [key in string]?: string }, set: Array<string>, };"
"type Hashes = { map: { [key in string]: string }, set: Array<string>, };"
)
}

Expand All @@ -35,7 +35,7 @@ struct HashesHasher {
fn hashmap_with_custom_hasher() {
assert_eq!(
HashesHasher::decl(),
"type HashesHasher = { map: { [key in string]?: string }, set: Array<string>, };"
"type HashesHasher = { map: { [key in string]: string }, set: Array<string>, };"
)
}

Expand All @@ -59,6 +59,13 @@ struct BTreeMapWithCustomTypes {
map: BTreeMap<CustomKey, CustomValue>,
}

#[derive(TS)]
#[ts(export, export_to = "hashmap/")]
enum EnumKey {
Foo,
Bar,
}

#[test]
fn with_custom_types() {
assert_eq!(
Expand All @@ -67,6 +74,14 @@ fn with_custom_types() {
);
assert_eq!(
HashMapWithCustomTypes::decl(),
"type HashMapWithCustomTypes = { map: { [key in CustomKey]?: CustomValue }, };"
"type HashMapWithCustomTypes = { map: { [key in CustomKey]: CustomValue }, };"
);
assert_eq!(
HashMap::<EnumKey, String>::name(),
"{ [key in EnumKey]?: string }"
);
assert_eq!(
HashMap::<EnumKey, String>::inline(),
r#"{ [key in "Foo" | "Bar"]?: string }"#
);
}
2 changes: 1 addition & 1 deletion ts-rs/tests/integration/indexmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ struct Indexes {
fn indexmap() {
assert_eq!(
Indexes::decl(),
"type Indexes = { map: { [key in string]?: string }, set: Array<string>, };"
"type Indexes = { map: { [key in string]: string }, set: Array<string>, };"
)
}
4 changes: 2 additions & 2 deletions ts-rs/tests/integration/issue_168.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ fn issue_168() {
FooInlined::export_to_string().unwrap(),
"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\
\n\
export type FooInlined = { map: { [key in number]?: { map: { [key in number]?: { map: { [key in number]?: string }, } }, } }, };\n"
export type FooInlined = { map: { [key in number]: { map: { [key in number]: { map: { [key in number]: string }, } }, } }, };\n"
);
assert_eq!(
Foo::export_to_string().unwrap(),
format!("// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\
import type {{ Bar }} from \"./Bar{extension}\";\n\
\n\
export type Foo = {{ map: {{ [key in number]?: Bar }}, }};\n")
export type Foo = {{ map: {{ [key in number]: Bar }}, }};\n")
);
}
4 changes: 2 additions & 2 deletions ts-rs/tests/integration/issue_70.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ struct Struct {
fn issue_70() {
assert_eq!(
Enum::decl(),
"type Enum = { \"A\": { [key in string]?: string } } | { \"B\": { [key in string]?: string } };"
"type Enum = { \"A\": { [key in string]: string } } | { \"B\": { [key in string]: string } };"
);
assert_eq!(
Struct::decl(),
"type Struct = { a: { [key in string]?: string }, b: { [key in string]?: string }, };"
"type Struct = { a: { [key in string]: string }, b: { [key in string]: string }, };"
);
}

Expand Down
2 changes: 1 addition & 1 deletion ts-rs/tests/integration/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ fn contains_borrow() {
fn contains_borrow_type_args() {
assert_eq!(
A::decl(),
"type A = { a: Array<number>, b: Array<B<number>>, c: { [key in string]?: boolean }, };"
"type A = { a: Array<number>, b: Array<B<number>>, c: { [key in string]: boolean }, };"
);
}
8 changes: 4 additions & 4 deletions ts-rs/tests/integration/self_referential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ fn enum_externally_tagged() {
{ \"C\": E } | \
{ \"D\": E } | \
{ \"E\": [E, E, E, E] } | \
{ \"F\": { a: E, b: E, c: { [key in string]?: E }, d: E | null, e?: E | null, f?: E, } } | \
{ \"G\": [Array<E>, Array<E>, { [key in string]?: E }] };"
{ \"F\": { a: E, b: E, c: { [key in string]: E }, d: E | null, e?: E | null, f?: E, } } | \
{ \"G\": [Array<E>, Array<E>, { [key in string]: E }] };"
);
}

Expand Down Expand Up @@ -170,7 +170,7 @@ fn enum_adjacently_tagged() {
\"content\": { \
a: A, \
b: A, \
c: { [key in string]?: A }, \
c: { [key in string]: A }, \
d: A | null, \
e?: A | null, \
f?: A, \
Expand All @@ -181,7 +181,7 @@ fn enum_adjacently_tagged() {
\"content\": [\
Array<A>, \
[A, A, A, A], \
{ [key in string]?: A }\
{ [key in string]: A }\
] \
};"
);
Expand Down
16 changes: 8 additions & 8 deletions ts-rs/tests/integration/serde_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ fn using_serde_json() {
assert_eq!(serde_json::Number::inline(), "number");
assert_eq!(
serde_json::Map::<String, i32>::inline(),
"{ [key in string]?: number }"
"{ [key in string]: number }"
);
assert_eq!(
serde_json::Value::decl(),
"type JsonValue = number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null;",
"type JsonValue = number | string | boolean | Array<JsonValue> | { [key in string]: JsonValue } | null;",
);

assert_eq!(
UsingSerdeJson::decl(),
"type UsingSerdeJson = { \
num: number, \
map1: { [key in string]?: number }, \
map2: { [key in string]?: UsingSerdeJson }, \
map3: { [key in string]?: { [key in string]?: number } }, \
map4: { [key in string]?: number }, \
map5: { [key in string]?: JsonValue }, \
map1: { [key in string]: number }, \
map2: { [key in string]: UsingSerdeJson }, \
map3: { [key in string]: { [key in string]: number } }, \
map4: { [key in string]: number }, \
map5: { [key in string]: JsonValue }, \
any: JsonValue, \
};"
)
Expand All @@ -53,7 +53,7 @@ fn inlined_value() {
assert_eq!(
InlinedValue::decl(),
"type InlinedValue = { \
any: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null, \
any: number | string | boolean | Array<JsonValue> | { [key in string]: JsonValue } | null, \
};"
);
}
Expand Down
Loading