Skip to content

Commit

Permalink
Merge pull request #257 from Aleph-Alpha/better-error-locations
Browse files Browse the repository at this point in the history
Improve location of some compiler errors
  • Loading branch information
escritorio-gustavo committed Mar 8, 2024
2 parents cd59b2c + b8a1df9 commit 474cc86
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 36 deletions.
13 changes: 7 additions & 6 deletions macros/src/attr/field.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use syn::{spanned::Spanned, Attribute, Ident, Result};
use syn::{Attribute, Ident, Result, Path};

use super::parse_assign_str;
use super::{parse_assign_str, parse_assign_from_str};
use crate::utils::{parse_attrs, parse_docs};

#[derive(Default)]
pub struct FieldAttr {
pub type_as: Option<String>,
pub type_as: Option<Path>,
pub type_override: Option<String>,
pub rename: Option<String>,
pub inline: bool,
Expand Down Expand Up @@ -70,18 +70,19 @@ impl FieldAttr {

impl_parse! {
FieldAttr(input, out) {
"as" => out.type_as = Some(parse_assign_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)?),
"inline" => out.inline = true,
"skip" => out.skip = true,
"optional" => {
use syn::{Token, Error};
use syn::{Token, Error};
let nullable = if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let span = input.span();
match Ident::parse(input)?.to_string().as_str() {
"nullable" => true,
other => Err(Error::new(other.span(), "expected 'nullable'"))?
_ => Err(Error::new(span, "expected 'nullable'"))?
}
} else {
false
Expand Down
8 changes: 8 additions & 0 deletions macros/src/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,11 @@ fn parse_assign_str(input: ParseStream) -> Result<String> {
fn parse_assign_inflection(input: ParseStream) -> Result<Inflection> {
parse_assign_str(input).and_then(Inflection::try_from)
}

fn parse_assign_from_str<T>(input: ParseStream) -> Result<T> where T: Parse {
input.parse::<Token![=]>()?;
match Lit::parse(input)? {
Lit::Str(string) => string.parse(),
other => Err(Error::new(other.span(), "expected string")),
}
}
12 changes: 5 additions & 7 deletions macros/src/types/enum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Fields, Generics, ItemEnum, Type, Variant};
use syn::{Fields, Generics, ItemEnum, Variant};

use crate::{
attr::{EnumAttr, FieldAttr, StructAttr, Tagged, VariantAttr},
Expand Down Expand Up @@ -116,11 +116,10 @@ fn format_variant(
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
} else {
let ty = match (type_override, type_as) {
(Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
(Some(_), Some(_)) => syn_err_spanned!(variant; "`type` is not compatible with `as`"),
(Some(type_override), None) => quote! { #type_override },
(None, Some(type_as)) => {
let ty = syn::parse_str::<Type>(&type_as)?;
quote!(<#ty as ts_rs::TS>::name())
quote!(<#type_as as ts_rs::TS>::name())
}
(None, None) => {
let ty = &unnamed.unnamed[0].ty;
Expand Down Expand Up @@ -166,11 +165,10 @@ fn format_variant(
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
} else {
let ty = match (type_override, type_as) {
(Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
(Some(_), Some(_)) => syn_err_spanned!(variant; "`type` is not compatible with `as`"),
(Some(type_override), None) => quote! { #type_override },
(None, Some(type_as)) => {
let ty = syn::parse_str::<Type>(&type_as)?;
quote!(<#ty as ts_rs::TS>::name())
quote!(<#type_as as ts_rs::TS>::name())
}
(None, None) => {
let ty = &unnamed.unnamed[0].ty;
Expand Down
22 changes: 11 additions & 11 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, FieldsNamed, GenericArgument, Generics, PathArguments, Result, Type};
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, Field, FieldsNamed, GenericArgument, Generics, PathArguments, Result, Type};

use crate::{
attr::{FieldAttr, Inflection, Optional, StructAttr},
Expand Down Expand Up @@ -93,11 +93,11 @@ fn format_field(
}

if type_as.is_some() && type_override.is_some() {
syn_err!("`type` is not compatible with `as`")
syn_err_spanned!(field; "`type` is not compatible with `as`")
}

let parsed_ty = if let Some(ref type_as) = type_as {
syn::parse_str::<Type>(type_as)?
syn::parse_str::<Type>(&type_as.to_token_stream().to_string())?
} else {
field.ty.clone()
};
Expand All @@ -120,10 +120,10 @@ fn format_field(

if flatten {
match (&type_as, &type_override, &rename, inline) {
(Some(_), _, _, _) => syn_err!("`as` is not compatible with `flatten`"),
(_, Some(_), _, _) => syn_err!("`type` is not compatible with `flatten`"),
(_, _, Some(_), _) => syn_err!("`rename` is not compatible with `flatten`"),
(_, _, _, true) => syn_err!("`inline` is not compatible with `flatten`"),
(Some(_), _, _, _) => syn_err_spanned!(field; "`as` is not compatible with `flatten`"),
(_, Some(_), _, _) => syn_err_spanned!(field; "`type` is not compatible with `flatten`"),
(_, _, Some(_), _) => syn_err_spanned!(field; "`rename` is not compatible with `flatten`"),
(_, _, _, true) => syn_err_spanned!(field; "`inline` is not compatible with `flatten`"),
_ => {}
}

Expand Down Expand Up @@ -175,12 +175,12 @@ fn extract_option_argument(ty: &Type) -> Result<&Type> {
PathArguments::AngleBracketed(args) if args.args.len() == 1 => {
match &args.args[0] {
GenericArgument::Type(inner_ty) => Ok(inner_ty),
_ => syn_err!("`Option` argument must be a type"),
other => syn_err!(other.span(); "`Option` argument must be a type"),
}
}
_ => syn_err!("`Option` type must have a single generic argument"),
other => syn_err!(other.span(); "`Option` type must have a single generic argument"),
}
}
_ => syn_err!("`optional` can only be used on an Option<T> type"),
other => syn_err!(other.span(); "`optional` can only be used on an Option<T> type"),
}
}
12 changes: 6 additions & 6 deletions macros/src/types/newtype.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use quote::quote;
use quote::{quote, ToTokens};
use syn::{FieldsUnnamed, Generics, Result, Type};

use crate::{
Expand Down Expand Up @@ -32,19 +32,19 @@ pub(crate) fn newtype(
} = FieldAttr::from_attrs(&inner.attrs)?;

match (&rename_inner, skip, optional.optional, flatten) {
(Some(_), ..) => syn_err!("`rename` is not applicable to newtype fields"),
(Some(_), ..) => syn_err_spanned!(fields; "`rename` is not applicable to newtype fields"),
(_, true, ..) => return super::unit::null(attr, name, generics.clone()),
(_, _, true, ..) => syn_err!("`optional` is not applicable to newtype fields"),
(_, _, _, true) => syn_err!("`flatten` is not applicable to newtype fields"),
(_, _, true, ..) => syn_err_spanned!(fields; "`optional` is not applicable to newtype fields"),
(_, _, _, true) => syn_err_spanned!(fields; "`flatten` is not applicable to newtype fields"),
_ => {}
};

if type_as.is_some() && type_override.is_some() {
syn_err!("`type` is not compatible with `as`")
syn_err_spanned!(fields; "`type` is not compatible with `as`")
}

let inner_ty = if let Some(ref type_as) = type_as {
syn::parse_str::<Type>(type_as)?
syn::parse_str::<Type>(&type_as.to_token_stream().to_string())?
} else {
inner.ty.clone()
};
Expand Down
12 changes: 6 additions & 6 deletions macros/src/types/tuple.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use syn::{Field, FieldsUnnamed, Generics, Result, Type};

use crate::{
Expand Down Expand Up @@ -66,25 +66,25 @@ fn format_field(
}

let ty = if let Some(ref type_as) = type_as {
syn::parse_str::<Type>(type_as)?
syn::parse_str::<Type>(&type_as.to_token_stream().to_string())?
} else {
field.ty.clone()
};

if type_as.is_some() && type_override.is_some() {
syn_err!("`type` is not compatible with `as`")
syn_err_spanned!(field; "`type` is not compatible with `as`")
}

if rename.is_some() {
syn_err!("`rename` is not applicable to tuple structs")
syn_err_spanned!(field; "`rename` is not applicable to tuple structs")
}

if optional.optional {
syn_err!("`optional` is not applicable to tuple fields")
syn_err_spanned!(field; "`optional` is not applicable to tuple fields")
}

if flatten {
syn_err!("`flatten` is not applicable to tuple fields")
syn_err_spanned!(field; "`flatten` is not applicable to tuple fields")
}

formatted_fields.push(match type_override {
Expand Down
6 changes: 6 additions & 0 deletions macros/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ macro_rules! syn_err {
};
}

macro_rules! syn_err_spanned {
($s:expr; $l:literal $(, $a:expr)*) => {
return Err(syn::Error::new_spanned($s, format!($l $(, $a)*)))
};
}

macro_rules! impl_parse {
($i:ident ($input:ident, $out:ident) { $($k:pat => $e:expr),* $(,)? }) => {
impl std::convert::TryFrom<&syn::Attribute> for $i {
Expand Down

0 comments on commit 474cc86

Please sign in to comment.