extern crate quote;
extern crate syn;

mod features;

#[macro_use]
mod macros;

use quote::quote;
use syn::{Data, DeriveInput};

#[test]
fn test_unit() {
    let input = quote! {
        struct Unit;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Inherited,
   ⋮    ident: "Unit",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}

#[test]
fn test_struct() {
    let input = quote! {
        #[derive(Debug, Clone)]
        pub struct Item {
            pub ident: Ident,
            pub attrs: Vec<Attribute>
        }
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    attrs: [
   ⋮        Attribute {
   ⋮            style: Outer,
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "derive",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮            tokens: `( Debug , Clone )`,
   ⋮        },
   ⋮    ],
   ⋮    vis: Visibility::Public,
   ⋮    ident: "Item",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Fields::Named {
   ⋮            named: [
   ⋮                Field {
   ⋮                    vis: Visibility::Public,
   ⋮                    ident: Some("ident"),
   ⋮                    colon_token: Some,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "Ident",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮                Field {
   ⋮                    vis: Visibility::Public,
   ⋮                    ident: Some("attrs"),
   ⋮                    colon_token: Some,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "Vec",
   ⋮                                    arguments: PathArguments::AngleBracketed {
   ⋮                                        args: [
   ⋮                                            Type(Type::Path {
   ⋮                                                path: Path {
   ⋮                                                    segments: [
   ⋮                                                        PathSegment {
   ⋮                                                            ident: "Attribute",
   ⋮                                                            arguments: None,
   ⋮                                                        },
   ⋮                                                    ],
   ⋮                                                },
   ⋮                                            }),
   ⋮                                        ],
   ⋮                                    },
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮}
    "###);

    snapshot!(input.attrs[0].parse_meta().unwrap(), @r###"
   ⋮Meta::List {
   ⋮    path: Path {
   ⋮        segments: [
   ⋮            PathSegment {
   ⋮                ident: "derive",
   ⋮                arguments: None,
   ⋮            },
   ⋮        ],
   ⋮    },
   ⋮    nested: [
   ⋮        Meta(Path(Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "Debug",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        })),
   ⋮        Meta(Path(Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "Clone",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        })),
   ⋮    ],
   ⋮}
    "###);
}

#[test]
fn test_union() {
    let input = quote! {
        union MaybeUninit<T> {
            uninit: (),
            value: T
        }
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Inherited,
   ⋮    ident: "MaybeUninit",
   ⋮    generics: Generics {
   ⋮        lt_token: Some,
   ⋮        params: [
   ⋮            Type(TypeParam {
   ⋮                ident: "T",
   ⋮            }),
   ⋮        ],
   ⋮        gt_token: Some,
   ⋮    },
   ⋮    data: Data::Union {
   ⋮        fields: FieldsNamed {
   ⋮            named: [
   ⋮                Field {
   ⋮                    vis: Inherited,
   ⋮                    ident: Some("uninit"),
   ⋮                    colon_token: Some,
   ⋮                    ty: Type::Tuple,
   ⋮                },
   ⋮                Field {
   ⋮                    vis: Inherited,
   ⋮                    ident: Some("value"),
   ⋮                    colon_token: Some,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "T",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮}
    "###);
}

#[test]
#[cfg(feature = "full")]
fn test_enum() {
    let input = quote! {
        /// See the std::result module documentation for details.
        #[must_use]
        pub enum Result<T, E> {
            Ok(T),
            Err(E),
            Surprise = 0isize,

            // Smuggling data into a proc_macro_derive,
            // in the style of https://github.com/dtolnay/proc-macro-hack
            ProcMacroHack = (0, "data").0
        }
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    attrs: [
   ⋮        Attribute {
   ⋮            style: Outer,
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "doc",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮            tokens: `= r" See the std::result module documentation for details."`,
   ⋮        },
   ⋮        Attribute {
   ⋮            style: Outer,
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "must_use",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮            tokens: ``,
   ⋮        },
   ⋮    ],
   ⋮    vis: Visibility::Public,
   ⋮    ident: "Result",
   ⋮    generics: Generics {
   ⋮        lt_token: Some,
   ⋮        params: [
   ⋮            Type(TypeParam {
   ⋮                ident: "T",
   ⋮            }),
   ⋮            Type(TypeParam {
   ⋮                ident: "E",
   ⋮            }),
   ⋮        ],
   ⋮        gt_token: Some,
   ⋮    },
   ⋮    data: Data::Enum {
   ⋮        variants: [
   ⋮            Variant {
   ⋮                ident: "Ok",
   ⋮                fields: Fields::Unnamed {
   ⋮                    unnamed: [
   ⋮                        Field {
   ⋮                            vis: Inherited,
   ⋮                            ty: Type::Path {
   ⋮                                path: Path {
   ⋮                                    segments: [
   ⋮                                        PathSegment {
   ⋮                                            ident: "T",
   ⋮                                            arguments: None,
   ⋮                                        },
   ⋮                                    ],
   ⋮                                },
   ⋮                            },
   ⋮                        },
   ⋮                    ],
   ⋮                },
   ⋮            },
   ⋮            Variant {
   ⋮                ident: "Err",
   ⋮                fields: Fields::Unnamed {
   ⋮                    unnamed: [
   ⋮                        Field {
   ⋮                            vis: Inherited,
   ⋮                            ty: Type::Path {
   ⋮                                path: Path {
   ⋮                                    segments: [
   ⋮                                        PathSegment {
   ⋮                                            ident: "E",
   ⋮                                            arguments: None,
   ⋮                                        },
   ⋮                                    ],
   ⋮                                },
   ⋮                            },
   ⋮                        },
   ⋮                    ],
   ⋮                },
   ⋮            },
   ⋮            Variant {
   ⋮                ident: "Surprise",
   ⋮                fields: Unit,
   ⋮                discriminant: Some(Expr::Lit {
   ⋮                    lit: 0isize,
   ⋮                }),
   ⋮            },
   ⋮            Variant {
   ⋮                ident: "ProcMacroHack",
   ⋮                fields: Unit,
   ⋮                discriminant: Some(Expr::Field {
   ⋮                    base: Expr::Tuple {
   ⋮                        elems: [
   ⋮                            Expr::Lit {
   ⋮                                lit: 0,
   ⋮                            },
   ⋮                            Expr::Lit {
   ⋮                                lit: "data",
   ⋮                            },
   ⋮                        ],
   ⋮                    },
   ⋮                    member: Unnamed(Index {
   ⋮                        index: 0,
   ⋮                    }),
   ⋮                }),
   ⋮            },
   ⋮        ],
   ⋮    },
   ⋮}
    "###);

    let meta_items: Vec<_> = input
        .attrs
        .into_iter()
        .map(|attr| attr.parse_meta().unwrap())
        .collect();

    snapshot!(meta_items, @r###"
   ⋮[
   ⋮    Meta::NameValue {
   ⋮        path: Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "doc",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮        lit: " See the std::result module documentation for details.",
   ⋮    },
   ⋮    Path(Path {
   ⋮        segments: [
   ⋮            PathSegment {
   ⋮                ident: "must_use",
   ⋮                arguments: None,
   ⋮            },
   ⋮        ],
   ⋮    }),
   ⋮]
    "###);
}

#[test]
fn test_attr_with_path() {
    let input = quote! {
        #[::attr_args::identity
            fn main() { assert_eq!(foo(), "Hello, world!"); }]
        struct Dummy;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    attrs: [
   ⋮        Attribute {
   ⋮            style: Outer,
   ⋮            path: Path {
   ⋮                leading_colon: Some,
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "attr_args",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                    PathSegment {
   ⋮                        ident: "identity",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮            tokens: `fn main ( ) { assert_eq ! ( foo ( ) , "Hello, world!" ) ; }`,
   ⋮        },
   ⋮    ],
   ⋮    vis: Inherited,
   ⋮    ident: "Dummy",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);

    assert!(input.attrs[0].parse_meta().is_err());
}

#[test]
fn test_attr_with_non_mod_style_path() {
    let input = quote! {
        #[inert <T>]
        struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    attrs: [
   ⋮        Attribute {
   ⋮            style: Outer,
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "inert",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮            tokens: `< T >`,
   ⋮        },
   ⋮    ],
   ⋮    vis: Inherited,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);

    assert!(input.attrs[0].parse_meta().is_err());
}

#[test]
fn test_attr_with_mod_style_path_with_self() {
    let input = quote! {
        #[foo::self]
        struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    attrs: [
   ⋮        Attribute {
   ⋮            style: Outer,
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "foo",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                    PathSegment {
   ⋮                        ident: "self",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮            tokens: ``,
   ⋮        },
   ⋮    ],
   ⋮    vis: Inherited,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);

    snapshot!(input.attrs[0].parse_meta().unwrap(), @r###"
   ⋮Path(Path {
   ⋮    segments: [
   ⋮        PathSegment {
   ⋮            ident: "foo",
   ⋮            arguments: None,
   ⋮        },
   ⋮        PathSegment {
   ⋮            ident: "self",
   ⋮            arguments: None,
   ⋮        },
   ⋮    ],
   ⋮})
    "###);
}

#[test]
fn test_pub_restricted() {
    // Taken from tests/rust/src/test/ui/resolve/auxiliary/privacy-struct-ctor.rs
    let input = quote! {
        pub(in m) struct Z(pub(in m::n) u8);
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Visibility::Restricted {
   ⋮        in_token: Some,
   ⋮        path: Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "m",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮    ident: "Z",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Fields::Unnamed {
   ⋮            unnamed: [
   ⋮                Field {
   ⋮                    vis: Visibility::Restricted {
   ⋮                        in_token: Some,
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "m",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                                PathSegment {
   ⋮                                    ident: "n",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "u8",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}

#[test]
fn test_vis_crate() {
    let input = quote! {
        crate struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Visibility::Crate,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}

#[test]
fn test_pub_restricted_crate() {
    let input = quote! {
        pub(crate) struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Visibility::Restricted {
   ⋮        path: Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "crate",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}

#[test]
fn test_pub_restricted_super() {
    let input = quote! {
        pub(super) struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Visibility::Restricted {
   ⋮        path: Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "super",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}

#[test]
fn test_pub_restricted_in_super() {
    let input = quote! {
        pub(in super) struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Visibility::Restricted {
   ⋮        in_token: Some,
   ⋮        path: Path {
   ⋮            segments: [
   ⋮                PathSegment {
   ⋮                    ident: "super",
   ⋮                    arguments: None,
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}

#[test]
fn test_fields_on_unit_struct() {
    let input = quote! {
        struct S;
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Inherited,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Unit,
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);

    let data = match input.data {
        Data::Struct(data) => data,
        _ => panic!("expected a struct"),
    };

    assert_eq!(0, data.fields.iter().count());
}

#[test]
fn test_fields_on_named_struct() {
    let input = quote! {
        struct S {
            foo: i32,
            pub bar: String,
        }
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Inherited,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Fields::Named {
   ⋮            named: [
   ⋮                Field {
   ⋮                    vis: Inherited,
   ⋮                    ident: Some("foo"),
   ⋮                    colon_token: Some,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "i32",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮                Field {
   ⋮                    vis: Visibility::Public,
   ⋮                    ident: Some("bar"),
   ⋮                    colon_token: Some,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "String",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮    },
   ⋮}
    "###);

    let data = match input.data {
        Data::Struct(data) => data,
        _ => panic!("expected a struct"),
    };

    snapshot!(data.fields.into_iter().collect::<Vec<_>>(), @r###"
   ⋮[
   ⋮    Field {
   ⋮        vis: Inherited,
   ⋮        ident: Some("foo"),
   ⋮        colon_token: Some,
   ⋮        ty: Type::Path {
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "i32",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮        },
   ⋮    },
   ⋮    Field {
   ⋮        vis: Visibility::Public,
   ⋮        ident: Some("bar"),
   ⋮        colon_token: Some,
   ⋮        ty: Type::Path {
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "String",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮        },
   ⋮    },
   ⋮]
    "###);
}

#[test]
fn test_fields_on_tuple_struct() {
    let input = quote! {
        struct S(i32, pub String);
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Inherited,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Fields::Unnamed {
   ⋮            unnamed: [
   ⋮                Field {
   ⋮                    vis: Inherited,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "i32",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮                Field {
   ⋮                    vis: Visibility::Public,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "String",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);

    let data = match input.data {
        Data::Struct(data) => data,
        _ => panic!("expected a struct"),
    };

    snapshot!(data.fields.iter().collect::<Vec<_>>(), @r###"
   ⋮[
   ⋮    Field {
   ⋮        vis: Inherited,
   ⋮        ty: Type::Path {
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "i32",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮        },
   ⋮    },
   ⋮    Field {
   ⋮        vis: Visibility::Public,
   ⋮        ty: Type::Path {
   ⋮            path: Path {
   ⋮                segments: [
   ⋮                    PathSegment {
   ⋮                        ident: "String",
   ⋮                        arguments: None,
   ⋮                    },
   ⋮                ],
   ⋮            },
   ⋮        },
   ⋮    },
   ⋮]
    "###);
}

#[test]
fn test_ambiguous_crate() {
    let input = quote! {
        // The field type is `(crate::X)` not `crate (::X)`.
        struct S(crate::X);
    };

    snapshot!(input as DeriveInput, @r###"
   ⋮DeriveInput {
   ⋮    vis: Inherited,
   ⋮    ident: "S",
   ⋮    generics: Generics,
   ⋮    data: Data::Struct {
   ⋮        fields: Fields::Unnamed {
   ⋮            unnamed: [
   ⋮                Field {
   ⋮                    vis: Inherited,
   ⋮                    ty: Type::Path {
   ⋮                        path: Path {
   ⋮                            segments: [
   ⋮                                PathSegment {
   ⋮                                    ident: "crate",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                                PathSegment {
   ⋮                                    ident: "X",
   ⋮                                    arguments: None,
   ⋮                                },
   ⋮                            ],
   ⋮                        },
   ⋮                    },
   ⋮                },
   ⋮            ],
   ⋮        },
   ⋮        semi_token: Some,
   ⋮    },
   ⋮}
    "###);
}
