Skip to content

Commit

Permalink
Merge branch 'Aleph-Alpha:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
decahedron1 authored Jul 31, 2024
2 parents fd45ca7 + 5c3b8c9 commit fa54412
Show file tree
Hide file tree
Showing 25 changed files with 1,944 additions and 1,767 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# master
### Breaking
### Features

- The `bson-uuid-impl` feature now supports `bson::oid::ObjectId` as well ([#340](https://github.com/Aleph-Alpha/ts-rs/pull/340))

### Fixes

- Properly handle block doc comments ([#342](https://github.com/Aleph-Alpha/ts-rs/pull/342))

# 9.0.1
### Fixes
- Allow using `#[ts(flatten)]` on fields using generic parameters ([#336](https://github.com/Aleph-Alpha/ts-rs/pull/336))
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ When running `cargo test`, the TypeScript bindings will be exported to the file
| bigdecimal-impl | Implement `TS` for types from *bigdecimal* |
| url-impl | Implement `TS` for types from *url* |
| uuid-impl | Implement `TS` for types from *uuid* |
| bson-uuid-impl | Implement `TS` for types from *bson* |
| bson-uuid-impl | Implement `TS` for *bson::oid::ObjectId* and *bson::uuid* |
| bytes-impl | Implement `TS` for types from *bytes* |
| indexmap-impl | Implement `TS` for types from *indexmap* |
| ordered-float-impl | Implement `TS` for types from *ordered_float* |
Expand Down
335 changes: 173 additions & 162 deletions macros/src/attr/struct.rs
Original file line number Diff line number Diff line change
@@ -1,162 +1,173 @@
use std::collections::HashMap;

use syn::{parse_quote, Attribute, Fields, Ident, Path, Result, Type, WherePredicate};

use super::{
parse_assign_from_str, parse_assign_inflection, parse_bound, parse_concrete, Attr,
ContainerAttr, Serde,
};
use crate::{
attr::{parse_assign_str, EnumAttr, Inflection, VariantAttr},
utils::{parse_attrs, parse_docs},
};

#[derive(Default, Clone)]
pub struct StructAttr {
crate_rename: Option<Path>,
pub type_as: Option<Type>,
pub type_override: Option<String>,
pub rename_all: Option<Inflection>,
pub rename: Option<String>,
pub export_to: Option<String>,
pub export: bool,
pub tag: Option<String>,
pub docs: String,
pub concrete: HashMap<Ident, Type>,
pub bound: Option<Vec<WherePredicate>>,
}

impl StructAttr {
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = parse_attrs::<Self>(attrs)?;

if cfg!(feature = "serde-compat") {
let serde_attr = crate::utils::parse_serde_attrs::<StructAttr>(attrs);
result = result.merge(serde_attr.0);
}

let docs = parse_docs(attrs)?;
result.docs = docs;

Ok(result)
}

pub fn from_variant(
enum_attr: &EnumAttr,
variant_attr: &VariantAttr,
variant_fields: &Fields,
) -> Self {
Self {
crate_rename: Some(enum_attr.crate_rename()),
rename: variant_attr.rename.clone(),
rename_all: variant_attr.rename_all.or(match variant_fields {
Fields::Named(_) => enum_attr.rename_all_fields,
Fields::Unnamed(_) | Fields::Unit => None,
}),
// inline and skip are not supported on StructAttr
..Self::default()
}
}
}

impl Attr for StructAttr {
type Item = Fields;

fn merge(self, other: Self) -> Self {
Self {
crate_rename: self.crate_rename.or(other.crate_rename),
type_as: self.type_as.or(other.type_as),
type_override: self.type_override.or(other.type_override),
rename: self.rename.or(other.rename),
rename_all: self.rename_all.or(other.rename_all),
export_to: self.export_to.or(other.export_to),
export: self.export || other.export,
tag: self.tag.or(other.tag),
docs: other.docs,
concrete: self.concrete.into_iter().chain(other.concrete).collect(),
bound: match (self.bound, other.bound) {
(Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
(Some(bound), None) | (None, Some(bound)) => Some(bound),
(None, None) => None,
},
}
}

fn assert_validity(&self, item: &Self::Item) -> Result<()> {
if self.type_override.is_some() {
if self.type_as.is_some() {
syn_err!("`as` is not compatible with `type`");
}

if self.rename_all.is_some() {
syn_err!("`rename_all` is not compatible with `type`");
}

if self.tag.is_some() {
syn_err!("`tag` is not compatible with `type`");
}
}

if self.type_as.is_some() {
if self.tag.is_some() {
syn_err!("`tag` is not compatible with `as`");
}

if self.rename_all.is_some() {
syn_err!("`rename_all` is not compatible with `as`");
}
}

if !matches!(item, Fields::Named(_)) {
if self.tag.is_some() {
syn_err!("`tag` cannot be used with unit or tuple structs");
}

if self.rename_all.is_some() {
syn_err!("`rename_all` cannot be used with unit or tuple structs");
}
}

Ok(())
}
}

impl ContainerAttr for StructAttr {
fn crate_rename(&self) -> Path {
self.crate_rename
.clone()
.unwrap_or_else(|| parse_quote!(::ts_rs))
}
}

impl_parse! {
StructAttr(input, out) {
"crate" => out.crate_rename = Some(parse_assign_from_str(input)?),
"as" => out.type_as = Some(parse_assign_from_str(input)?),
"type" => out.type_override = Some(parse_assign_str(input)?),
"rename" => out.rename = Some(parse_assign_str(input)?),
"rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
"tag" => out.tag = Some(parse_assign_str(input)?),
"export" => out.export = true,
"export_to" => out.export_to = Some(parse_assign_str(input)?),
"concrete" => out.concrete = parse_concrete(input)?,
"bound" => out.bound = Some(parse_bound(input)?),
}
}

impl_parse! {
Serde<StructAttr>(input, out) {
"rename" => out.0.rename = Some(parse_assign_str(input)?),
"rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?),
"tag" => out.0.tag = Some(parse_assign_str(input)?),
"bound" => out.0.bound = Some(parse_bound(input)?),
// parse #[serde(default)] to not emit a warning
"deny_unknown_fields" | "default" => {
use syn::Token;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
parse_assign_str(input)?;
}
},
}
}
use std::collections::HashMap;

use syn::{parse_quote, Attribute, Fields, Ident, Path, Result, Type, WherePredicate};

use super::{
parse_assign_from_str, parse_assign_inflection, parse_bound, parse_concrete, Attr,
ContainerAttr, Serde, Tagged,
};
use crate::{
attr::{parse_assign_str, EnumAttr, Inflection, VariantAttr},
utils::{parse_attrs, parse_docs},
};

#[derive(Default, Clone)]
pub struct StructAttr {
crate_rename: Option<Path>,
pub type_as: Option<Type>,
pub type_override: Option<String>,
pub rename_all: Option<Inflection>,
pub rename: Option<String>,
pub export_to: Option<String>,
pub export: bool,
pub tag: Option<String>,
pub docs: String,
pub concrete: HashMap<Ident, Type>,
pub bound: Option<Vec<WherePredicate>>,
}

impl StructAttr {
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = parse_attrs::<Self>(attrs)?;

if cfg!(feature = "serde-compat") {
let serde_attr = crate::utils::parse_serde_attrs::<StructAttr>(attrs);
result = result.merge(serde_attr.0);
}

let docs = parse_docs(attrs)?;
result.docs = docs;

Ok(result)
}

pub fn from_variant(
enum_attr: &EnumAttr,
variant_attr: &VariantAttr,
variant_fields: &Fields,
) -> Self {
Self {
crate_rename: Some(enum_attr.crate_rename()),
rename: variant_attr.rename.clone(),
rename_all: variant_attr.rename_all.or(match variant_fields {
Fields::Named(_) => enum_attr.rename_all_fields,
Fields::Unnamed(_) | Fields::Unit => None,
}),
tag: match variant_fields {
Fields::Named(_) => match enum_attr
.tagged()
.expect("The variant attribute is known to be valid at this point")
{
Tagged::Internally { tag } => Some(tag.to_owned()),
_ => None,
},
_ => None,
},

// inline and skip are not supported on StructAttr
..Self::default()
}
}
}

impl Attr for StructAttr {
type Item = Fields;

fn merge(self, other: Self) -> Self {
Self {
crate_rename: self.crate_rename.or(other.crate_rename),
type_as: self.type_as.or(other.type_as),
type_override: self.type_override.or(other.type_override),
rename: self.rename.or(other.rename),
rename_all: self.rename_all.or(other.rename_all),
export_to: self.export_to.or(other.export_to),
export: self.export || other.export,
tag: self.tag.or(other.tag),
docs: other.docs,
concrete: self.concrete.into_iter().chain(other.concrete).collect(),
bound: match (self.bound, other.bound) {
(Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
(Some(bound), None) | (None, Some(bound)) => Some(bound),
(None, None) => None,
},
}
}

fn assert_validity(&self, item: &Self::Item) -> Result<()> {
if self.type_override.is_some() {
if self.type_as.is_some() {
syn_err!("`as` is not compatible with `type`");
}

if self.rename_all.is_some() {
syn_err!("`rename_all` is not compatible with `type`");
}

if self.tag.is_some() {
syn_err!("`tag` is not compatible with `type`");
}
}

if self.type_as.is_some() {
if self.tag.is_some() {
syn_err!("`tag` is not compatible with `as`");
}

if self.rename_all.is_some() {
syn_err!("`rename_all` is not compatible with `as`");
}
}

if !matches!(item, Fields::Named(_)) {
if self.tag.is_some() {
syn_err!("`tag` cannot be used with unit or tuple structs");
}

if self.rename_all.is_some() {
syn_err!("`rename_all` cannot be used with unit or tuple structs");
}
}

Ok(())
}
}

impl ContainerAttr for StructAttr {
fn crate_rename(&self) -> Path {
self.crate_rename
.clone()
.unwrap_or_else(|| parse_quote!(::ts_rs))
}
}

impl_parse! {
StructAttr(input, out) {
"crate" => out.crate_rename = Some(parse_assign_from_str(input)?),
"as" => out.type_as = Some(parse_assign_from_str(input)?),
"type" => out.type_override = Some(parse_assign_str(input)?),
"rename" => out.rename = Some(parse_assign_str(input)?),
"rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
"tag" => out.tag = Some(parse_assign_str(input)?),
"export" => out.export = true,
"export_to" => out.export_to = Some(parse_assign_str(input)?),
"concrete" => out.concrete = parse_concrete(input)?,
"bound" => out.bound = Some(parse_bound(input)?),
}
}

impl_parse! {
Serde<StructAttr>(input, out) {
"rename" => out.0.rename = Some(parse_assign_str(input)?),
"rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?),
"tag" => out.0.tag = Some(parse_assign_str(input)?),
"bound" => out.0.bound = Some(parse_bound(input)?),
// parse #[serde(default)] to not emit a warning
"deny_unknown_fields" | "default" => {
use syn::Token;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
parse_assign_str(input)?;
}
},
}
}
Loading

0 comments on commit fa54412

Please sign in to comment.