Skip to content

Commit

Permalink
Add ui tests and improve error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Jun 25, 2019
1 parent 19a324f commit 7efdfc6
Show file tree
Hide file tree
Showing 17 changed files with 279 additions and 156 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ matrix:
- cargo test --no-default-features
- cargo test

- rust: nightly
name: cargo test (ui tests)
script:
- cargo clean
- RUSTFLAGS='--cfg ui_tests' cargo test --test compile-test

- rust: nightly
name: cargo check (minimal versions)
script:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

* Improved error messages.

# 0.3.2 - 2019-03-30

* Avoided suffixes on tuple index.
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ project_attr = ["syn/visit-mut"]
[dependencies]
proc-macro2 = "0.4.13"
quote = "0.6.8"
syn = { version = "0.15.22", features = ["full"] }
syn = { version = "0.15.29", features = ["full"] }

[dev-dependencies]
compiletest = { version = "0.3.21", package = "compiletest_rs", features = ["stable", "tmp"] }
3 changes: 0 additions & 3 deletions src/compile_fail/mod.rs

This file was deleted.

76 changes: 0 additions & 76 deletions src/compile_fail/unpin_conflict.rs

This file was deleted.

8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@

extern crate proc_macro;

#[macro_use]
mod utils;

#[cfg(feature = "project_attr")]
mod project;
mod unsafe_project;
mod utils;

mod compile_fail;

use proc_macro::TokenStream;

Expand Down Expand Up @@ -323,7 +323,7 @@ use proc_macro::TokenStream;
/// [`project`]: ./attr.project.html
#[proc_macro_attribute]
pub fn unsafe_project(args: TokenStream, input: TokenStream) -> TokenStream {
TokenStream::from(unsafe_project::attribute(&args.to_string(), input.into()))
TokenStream::from(unsafe_project::attribute(args.into(), input.into()))
}

/// An attribute to support pattern matching.
Expand Down
8 changes: 3 additions & 5 deletions src/project.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::identity;

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
Expand All @@ -9,13 +7,13 @@ use syn::{
*,
};

use crate::utils::{proj_ident, Result, VecExt};
use crate::utils::{proj_ident, VecExt};

/// The attribute name.
const NAME: &str = "project";

pub(super) fn attribute(input: TokenStream) -> TokenStream {
parse(input).unwrap_or_else(identity)
parse(input).unwrap_or_else(|e| e.to_compile_error())
}

fn parse(input: TokenStream) -> Result<TokenStream> {
Expand All @@ -28,7 +26,7 @@ fn parse(input: TokenStream) -> Result<TokenStream> {
}
}

syn::parse2(input).map_err(|err| err.to_compile_error()).map(|mut stmt| {
syn::parse2(input).map(|mut stmt| {
replace_stmt(&mut stmt);
stmt.into_token_stream()
})
Expand Down
24 changes: 13 additions & 11 deletions src/unsafe_project/enums.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Variant};
use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, ItemEnum, Result, Variant};

use crate::utils::{proj_ident, Result, VecExt};
use crate::utils::{proj_ident, VecExt};

use super::*;

pub(super) fn parse(args: &str, item: ItemEnum) -> Result<TokenStream> {
pub(super) fn parse(args: TokenStream, mut item: ItemEnum) -> Result<TokenStream> {
let mut impl_unpin = ImplUnpin::new(args, &item.generics)?;

if item.variants.is_empty() {
parse_failed("enums without variants")
} else if item.variants.iter().any(|v| v.discriminant.is_some()) {
parse_failed("enums with discriminants")
} else {
ImplUnpin::parse(args, &item.generics).map(|impl_unpin| proj_impl(item, impl_unpin))
return Err(error!(item.variants, "cannot be implemented for enums without variants"));
} else if let Some(e) = item.variants.iter().find_map(|v| {
v.discriminant
.as_ref()
.map(|(_, e)| error!(e, "an enum with discriminants is not supported"))
}) {
return Err(e);
}
}

fn proj_impl(mut item: ItemEnum, mut impl_unpin: ImplUnpin) -> TokenStream {
let proj_ident = proj_ident(&item.ident);
let (proj_item_body, proj_arms) = variants(&mut item, &proj_ident, &mut impl_unpin);

Expand Down Expand Up @@ -44,7 +46,7 @@ fn proj_impl(mut item: ItemEnum, mut impl_unpin: ImplUnpin) -> TokenStream {

let mut item = item.into_token_stream();
item.extend(proj_items);
item
Ok(item)
}

fn variants(
Expand Down
59 changes: 30 additions & 29 deletions src/unsafe_project/mod.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,68 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::{spanned::Spanned, Generics, Item, Result, Type};

mod enums;
mod structs;

use std::convert::identity;

use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{parse_quote, Generics, Item, Type};

use crate::utils::{failed, Result};

/// The attribute name.
const NAME: &str = "unsafe_project";
/// The annotation for pinned type.
const PIN: &str = "pin";

pub(super) fn attribute(args: &str, input: TokenStream) -> TokenStream {
pub(super) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream {
let span = span!(input);
match syn::parse2(input) {
Ok(Item::Struct(item)) => structs::parse(args, item),
Ok(Item::Enum(item)) => enums::parse(args, item),
_ => failed(NAME, "may only be used on structs or enums"),
_ => Err(error!(span, "may only be used on structs or enums")),
}
.unwrap_or_else(identity)
}

#[inline(never)]
fn parse_failed<T>(msg: &str) -> Result<T> {
failed(NAME, &format!("cannot be implemented for {}", msg))
.unwrap_or_else(|e| e.to_compile_error())
}

/// Makes the generics of projected type from the reference of the original generics.
fn proj_generics(generics: &Generics) -> Generics {
let mut generics = generics.clone();
generics.params.insert(0, parse_quote!('__a));
generics.params.insert(0, syn::parse_quote!('__a));
generics
}

// =================================================================================================
// conditional Unpin implementation

struct ImplUnpin(Option<Generics>);
#[derive(Default)]
struct ImplUnpin(
Option<(
// generics
Generics,
// span
Span,
)>,
);

impl ImplUnpin {
/// Parses attribute arguments.
fn parse(args: &str, generics: &Generics) -> Result<Self> {
match args {
"" => Ok(Self(None)),
"Unpin" => Ok(Self(Some(generics.clone()))),
_ => failed(NAME, "an invalid argument was passed"),
fn new(args: TokenStream, generics: &Generics) -> Result<Self> {
match &*args.to_string() {
"" => Ok(Self::default()),
"Unpin" => Ok(Self(Some((generics.clone(), args.span())))),
_ => Err(error!(args, "an invalid argument was passed")),
}
}

fn push(&mut self, ty: &Type) {
if let Some(generics) = &mut self.0 {
generics.make_where_clause().predicates.push(parse_quote!(#ty: ::core::marker::Unpin));
if let Some((generics, _)) = &mut self.0 {
generics
.make_where_clause()
.predicates
.push(syn::parse_quote!(#ty: ::core::marker::Unpin));
}
}

/// Creates `Unpin` implementation.
fn build(self, ident: &Ident) -> TokenStream {
self.0
.map(|generics| {
.map(|(generics, span)| {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
quote_spanned! { span =>

This comment has been minimized.

Copy link
@shepmaster

shepmaster Jul 21, 2019

One thing I noticed while skimming through the diff of 0.3.2/0.3.3 is this line. quote says:

There should be no space before the => token.

I can't imagine this would matter, but figured I'd point it out...

This comment has been minimized.

Copy link
@taiki-e

taiki-e Jul 21, 2019

Author Owner

Yeah, it certainly does not seem to be the recommended style.

impl #impl_generics ::core::marker::Unpin for #ident #ty_generics #where_clause {}
}
})
Expand Down
14 changes: 6 additions & 8 deletions src/unsafe_project/structs.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, Index, ItemStruct};
use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, Index, ItemStruct, Result};

use crate::utils::{proj_ident, Result, VecExt};
use crate::utils::{proj_ident, VecExt};

use super::*;

pub(super) fn parse(args: &str, item: ItemStruct) -> Result<TokenStream> {
ImplUnpin::parse(args, &item.generics).and_then(|impl_unpin| proj_impl(item, impl_unpin))
}
pub(super) fn parse(args: TokenStream, mut item: ItemStruct) -> Result<TokenStream> {
let mut impl_unpin = ImplUnpin::new(args, &item.generics)?;

fn proj_impl(mut item: ItemStruct, mut impl_unpin: ImplUnpin) -> Result<TokenStream> {
let (proj_item_body, proj_init_body) = match &mut item.fields {
Fields::Named(FieldsNamed { named: fields, .. })
| Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. })
if fields.is_empty() =>
{
parse_failed("structs with zero fields")?
return Err(error!(item.fields, "cannot be implemented for structs with zero fields"))
}
Fields::Unit => parse_failed("structs with units")?,
Fields::Unit => return Err(error!(item, "cannot be implemented for structs with units")),

Fields::Named(fields) => named(fields, &mut impl_unpin),
Fields::Unnamed(fields) => unnamed(fields, &mut impl_unpin),
Expand Down
35 changes: 19 additions & 16 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
use std::result;

use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use proc_macro2::{Ident, Span};
use syn::Attribute;

pub(crate) type Result<T> = result::Result<T, TokenStream>;

/// Makes the ident of projected type from the reference of the original ident.
pub(crate) fn proj_ident(ident: &Ident) -> Ident {
Ident::new(&format!("__{}Projection", ident), Span::call_site())
}

#[inline(never)]
pub(crate) fn failed<T>(name: &str, msg: &str) -> Result<T> {
#[inline(never)]
fn compile_err(msg: &str) -> TokenStream {
quote!(compile_error!(#msg);)
}

Err(compile_err(&format!("`{}` {}", name, msg)))
}

pub(crate) trait VecExt {
fn find_remove(&mut self, ident: &str) -> bool;
}
Expand All @@ -33,3 +18,21 @@ impl VecExt for Vec<Attribute> {
.is_some()
}
}

macro_rules! span {
($expr:expr) => {
$expr.clone()
};
}

macro_rules! error {
($msg:expr) => {
syn::Error::new_spanned($msg, $msg)
};
($span:expr, $msg:expr) => {
syn::Error::new_spanned($span, $msg)
};
($span:expr, $($tt:tt)*) => {
error!($span, format!($($tt)*))
};
}
Loading

0 comments on commit 7efdfc6

Please sign in to comment.