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

Improve location of some compiler errors #257

Merged
merged 2 commits into from
Mar 8, 2024
Merged
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
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>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This enables checking if the string contains valid syntax for a path

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(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of failure, this will point inside the string, rather than after it

Copy link
Contributor Author

@escritorio-gustavo escritorio-gustavo Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before:
image

After:
image

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
Loading