<commit_before>use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use std::{fs::File, io::BufWriter, path::PathBuf};
use syn::{parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput};

use crate::to_adoc::ToAdoc;

pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // Write some nice AsciiDoc output
    let r#struct = Struct {
        name: input.ident.to_string(),
        docs: collect_docs_from_attrs(&input.attrs),
        examples: collect_examples_from_attrs(&input.attrs),
        fields: collect_fields(&input.data),
    };
    let adoc = adoc_output_path(&input.ident);
    let mut file =
        BufWriter::new(File::create(&adoc).expect(&format!("cannot write to {}", adoc.display())));
    r#struct.write_adoc(&mut file).expect(&format!(
        "cannot write {} as asciidoc to {}",
        r#struct.name,
        adoc.display()
    ));

    // Let's test those examples
    let struct_examples = r#struct.examples.iter().enumerate().map(|(idx, ex)| {
        let typ = &input.ident;
        let test_fn_name = syn::Ident::new(
            &format!("test_{}_example_{}", input.ident, idx),
            input.ident.span(),
        );
        let content = &ex.content;
        match ex.lang.as_str() {
            "yaml" | "yml" => {
                quote! {
                    #[test]
                    fn #test_fn_name() {
                        let input = #content;
                        let _result: #typ = serde_yaml::from_str(&input).unwrap();
                    }
                }
            }
            _ => quote! {},
        }
    });
    let field_examples = r#struct
        .fields
        .iter()
        .flat_map(|field| field.examples.iter().map(move |ex| (field, ex)))
        .enumerate()
        .map(|(idx, (field, ex))| {
            let typ = &field.raw_type;
            let test_fn_name = syn::Ident::new(
                &format!("test_{}_{}_example_{}", input.ident, field.name, idx),
                input.ident.span(),
            );
            let field_name = syn::Ident::new(&field.name, field.span);
            let attrs = &field.serde_attrs;
            let content = &ex.content;
            match ex.lang.as_str() {
                "yaml" | "yml" => {
                    quote_spanned! { field.span =>
                        #[test]
                        fn #test_fn_name() {
                            #[derive(Debug, Serialize, Deserialize)]
                            struct TestHelper {
                                #(#attrs)*
                                #field_name: #typ,
                            }

                            let input = #content;
                            let _result: TestHelper = serde_yaml::from_str(&input).unwrap();
                        }
                    }
                }
                _ => quote! {},
            }
        });

    TokenStream::from(quote! {
        #(#struct_examples)*
        #(#field_examples)*
    })
}

#[derive(Debug)]
pub(crate) struct Struct {
    pub(crate) name: String,
    pub(crate) docs: String,
    pub(crate) examples: Vec<Example>,
    pub(crate) fields: Vec<Field>,
}

#[derive(Debug)]
pub(crate) struct Field {
    pub(crate) name: String,
    pub(crate) docs: String,
    pub(crate) examples: Vec<Example>,
    pub(crate) required: bool,
    pub(crate) r#type: Type,
    // some helper fields for generating tests for examples
    raw_type: syn::Type,
    span: proc_macro2::Span,
    serde_attrs: Vec<syn::Attribute>,
}

#[derive(Debug)]
pub(crate) struct Example {
    pub(crate) lang: String,
    pub(crate) content: String,
}

#[derive(Debug)]
pub(crate) enum Type {
    Flat(TypeName),
    Nested {
        container_name: TypeName,
        nested: Vec<Type>,
    },
}

#[derive(Debug)]
pub enum TypeName {
    Primitive(String),
    Link(String),
}

fn adoc_output_path(name: &syn::Ident) -> PathBuf {
    let target = std::env::var("COLLECT_DOCS_TARGET").unwrap_or("./docs/generated".into());
    std::fs::create_dir_all(&target).expect(&format!("could not create directory {}", target));
    PathBuf::from(&target)
        .join(name.to_string())
        .with_extension("adoc")
}

// TODO: Parse `#[serde(rename = "foo")]` attributes and use as field names!
fn collect_fields(data: &Data) -> Vec<Field> {
    match data {
        Data::Struct(DataStruct {
            fields: syn::Fields::Unit,
            ..
        }) => vec![],
        Data::Struct(DataStruct {
            fields: fields @ syn::Fields::Unnamed(..),
            ..
        })
        | Data::Struct(DataStruct {
            fields: fields @ syn::Fields::Named(..),
            ..
        }) => fields
            .iter()
            .filter(|f| {
                if let syn::Visibility::Public(..) = f.vis {
                    true
                } else {
                    false
                }
            })
            .enumerate()
            .map(|(idx, field)| {
                let (required, r#type) = Type::toplevel_from_syn(&field.ty);
                Field {
                    name: field
                        .ident
                        .as_ref()
                        .map(|f| f.to_string())
                        .unwrap_or(format!("unnamed internal field #{}", idx)),
                    docs: collect_docs_from_attrs(&field.attrs),
                    examples: collect_examples_from_attrs(&field.attrs),
                    required,
                    r#type,
                    raw_type: field.ty.clone(),
                    span: field.span(),
                    serde_attrs: field
                        .attrs
                        .iter()
                        .filter(|attr| attr.path.is_ident("serde"))
                        .cloned()
                        .collect(),
                }
            })
            .collect(),
        _ => vec![],
    }
}

fn collect_docs_from_attrs(attrs: &[syn::Attribute]) -> String {
    attrs
        .iter()
        .filter(|attr| attr.path.is_ident("doc"))
        .fold(String::new(), |mut res, attr| {
            use once_cell::sync::Lazy;
            use regex::Regex;
            use std::fmt::Write;

            // dbg!(attr.parse_meta());

            let doc = TokenStream::from(attr.tokens.clone()).to_string();

            static RE: Lazy<Regex> =
                Lazy::new(|| regex::Regex::new(r#"^\s*=\s*"\s?(?P<content>.*?)"\s*$"#).unwrap());
            let doc = RE.replace_all(&doc, "$content");

            writeln!(&mut res, "{}", unescape_literal(&doc)).unwrap();
            res
        })
}

fn collect_examples_from_attrs(attrs: &[syn::Attribute]) -> Vec<Example> {
    use unindent::unindent;

    attrs
        .iter()
        .filter(|attr| attr.path.is_ident("example"))
        .map(|attr| {
            let list: Vec<syn::NestedMeta> = match attr.parse_meta() {
                Ok(syn::Meta::List(syn::MetaList { nested, .. })) => {
                    nested.iter().cloned().collect()
                }
                _ => unimplemented!("can't parse example attribute"),
            };
            assert_eq!(
                list.len(),
                2,
                "example attribute must be of form `#[example(lang_ident, \"lorem: ipsum\")]`"
            );
            // let (lang, example) = (&list[0], &list[1]);
            let lang = match &list[0] {
                syn::NestedMeta::Meta(syn::Meta::Path(syn::Path { segments, .. })) => {
                    segments.iter().next().unwrap().ident.to_string()
                }
                _ => unimplemented!("can't read lang of {:?}", list[0]),
            };
            let content = match &list[1] {
                syn::NestedMeta::Lit(syn::Lit::Str(val)) => unindent(&val.value()),
                _ => unimplemented!("can't content lang of {:?}", list[1]),
            };

            Example { lang, content }
        })
        .collect()
}

fn unescape_literal(lit: &str) -> String {
    lit.replace("\\\"", "\"")
        .replace("\\'", "'")
        .replace(r"\\", r"\")
}

impl TypeName {
    fn from_ident(ident: &str) -> Self {
        match ident {
            "bool" | "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" | "usize"
            | "isize" | "String" | "PathBuf" | "Vec" | "HashMap" | "BTreeMap" | "T" => {
                Self::Primitive(ident.to_string())
            }
            _ => Self::Link(ident.to_string()),
        }
    }
}

impl Type {
    fn toplevel_from_syn(t: &syn::Type) -> (bool, Type) {
        use syn::Type as T;
        let mut required = true;

        let type_name = match t {
            T::Path(syn::TypePath {
                path: syn::Path { segments, .. },
                ..
            }) => {
                let syn::PathSegment { ident, arguments } =
                    segments.last().expect("type path was empty");

                // Do a little dance to not print the other `Option` wrapper but emit `required=false`
                if ident == "Option" {
                    required = false;
                    match arguments {
                        syn::PathArguments::AngleBracketed(
                            syn::AngleBracketedGenericArguments { args, .. },
                        ) => {
                            let type_arg = args
                                .iter()
                                .filter_map(|arg| {
                                    if let syn::GenericArgument::Type(t) = arg {
                                        Some(t)
                                    } else {
                                        None
                                    }
                                })
                                .next()
                                .expect("no type param on `Option`");
                            Type::from_syn(&type_arg)
                        }
                        _ => unimplemented!(
                            "only type paths with regular `<T>`-style generics implemented so far"
                        ),
                    }
                } else {
                    Type::from_syn(t)
                }
            }
            _ => unimplemented!("only type paths implemented so far"),
        };

        (required, type_name)
    }

    fn from_syn(t: &syn::Type) -> Self {
        use syn::Type as T;

        match t {
            T::Path(syn::TypePath {
                path: syn::Path { segments, .. },
                ..
            }) => {
                let syn::PathSegment { ident, arguments } =
                    segments.last().expect("type path was empty");

                let args = match arguments {
                    syn::PathArguments::None => vec![],
                    syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
                        args,
                        ..
                    }) => args
                        .iter()
                        .filter_map(|arg| {
                            if let syn::GenericArgument::Type(t) = arg {
                                Some(t)
                            } else {
                                None
                            }
                        })
                        .map(Type::from_syn)
                        .collect::<Vec<_>>(),
                    _ => unimplemented!(
                        "only type paths with regular `<T>`-style generics implemented so far"
                    ),
                };

                let name = TypeName::from_ident(&ident.to_string());

                if args.is_empty() {
                    Type::Flat(name)
                } else {
                    Type::Nested {
                        container_name: name,
                        nested: args,
                    }
                }
            }
            _ => unimplemented!("only type paths implemented so far"),
        }
    }
}
<commit_msg>Collect docs: Respect serde rename field<commit_after>use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use std::{fs::File, io::BufWriter, path::PathBuf};
use syn::{parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput};

use crate::to_adoc::ToAdoc;

pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // Write some nice AsciiDoc output
    let r#struct = Struct {
        name: input.ident.to_string(),
        docs: collect_docs_from_attrs(&input.attrs),
        examples: collect_examples_from_attrs(&input.attrs),
        fields: collect_fields(&input.data),
    };
    let adoc = adoc_output_path(&input.ident);
    let mut file =
        BufWriter::new(File::create(&adoc).expect(&format!("cannot write to {}", adoc.display())));
    r#struct.write_adoc(&mut file).expect(&format!(
        "cannot write {} as asciidoc to {}",
        r#struct.name,
        adoc.display()
    ));

    // Let's test those examples
    let struct_examples = r#struct.examples.iter().enumerate().map(|(idx, ex)| {
        let typ = &input.ident;
        let test_fn_name = syn::Ident::new(
            &format!("test_{}_example_{}", input.ident, idx),
            input.ident.span(),
        );
        let content = &ex.content;
        match ex.lang.as_str() {
            "yaml" | "yml" => {
                quote! {
                    #[test]
                    fn #test_fn_name() {
                        let input = #content;
                        let _result: #typ = serde_yaml::from_str(&input).unwrap();
                    }
                }
            }
            _ => quote! {},
        }
    });
    let field_examples = r#struct
        .fields
        .iter()
        .flat_map(|field| field.examples.iter().map(move |ex| (field, ex)))
        .enumerate()
        .map(|(idx, (field, ex))| {
            let typ = &field.raw_type;
            let test_fn_name = syn::Ident::new(
                &format!("test_{}_{}_example_{}", input.ident, field.name, idx),
                input.ident.span(),
            );
            let field_name = field
                .rust_name
                .clone()
                .unwrap_or_else(|| syn::Ident::new(&format!("field_{}", idx), field.span));
            let attrs = &field.serde_attrs;
            let content = &ex.content;
            match ex.lang.as_str() {
                "yaml" | "yml" => {
                    quote_spanned! { field.span =>
                        #[test]
                        fn #test_fn_name() {
                            #[derive(Debug, Serialize, Deserialize)]
                            struct TestHelper {
                                #(#attrs)*
                                #field_name: #typ,
                            }

                            let input = #content;
                            let _result: TestHelper = serde_yaml::from_str(&input).unwrap();
                        }
                    }
                }
                _ => quote! {},
            }
        });

    TokenStream::from(quote! {
        #(#struct_examples)*
        #(#field_examples)*
    })
}

#[derive(Debug)]
pub(crate) struct Struct {
    pub(crate) name: String,
    pub(crate) docs: String,
    pub(crate) examples: Vec<Example>,
    pub(crate) fields: Vec<Field>,
}

#[derive(Debug)]
pub(crate) struct Field {
    pub(crate) name: String,
    pub(crate) docs: String,
    pub(crate) examples: Vec<Example>,
    pub(crate) required: bool,
    pub(crate) r#type: Type,
    // some helper fields for generating tests for examples
    rust_name: Option<syn::Ident>,
    raw_type: syn::Type,
    span: proc_macro2::Span,
    serde_attrs: Vec<syn::Attribute>,
}

#[derive(Debug)]
pub(crate) struct Example {
    pub(crate) lang: String,
    pub(crate) content: String,
}

#[derive(Debug)]
pub(crate) enum Type {
    Flat(TypeName),
    Nested {
        container_name: TypeName,
        nested: Vec<Type>,
    },
}

#[derive(Debug)]
pub enum TypeName {
    Primitive(String),
    Link(String),
}

fn adoc_output_path(name: &syn::Ident) -> PathBuf {
    let target = std::env::var("COLLECT_DOCS_TARGET").unwrap_or("./docs/generated".into());
    std::fs::create_dir_all(&target).expect(&format!("could not create directory {}", target));
    PathBuf::from(&target)
        .join(name.to_string())
        .with_extension("adoc")
}

fn collect_fields(data: &Data) -> Vec<Field> {
    match data {
        Data::Struct(DataStruct {
            fields: syn::Fields::Unit,
            ..
        }) => vec![],
        Data::Struct(DataStruct {
            fields: fields @ syn::Fields::Unnamed(..),
            ..
        })
        | Data::Struct(DataStruct {
            fields: fields @ syn::Fields::Named(..),
            ..
        }) => fields
            .iter()
            .filter(|f| {
                if let syn::Visibility::Public(..) = f.vis {
                    true
                } else {
                    false
                }
            })
            .enumerate()
            .map(|(idx, field)| {
                let (required, r#type) = Type::toplevel_from_syn(&field.ty);
                let serde_name = find_serde_rename(&field.attrs);
                Field {
                    name: serde_name.unwrap_or_else(|| {
                        field
                            .ident
                            .as_ref()
                            .map(|f| f.to_string())
                            .unwrap_or(format!("unnamed internal field #{}", idx))
                    }),
                    rust_name: field.ident.clone(),
                    docs: collect_docs_from_attrs(&field.attrs),
                    examples: collect_examples_from_attrs(&field.attrs),
                    required,
                    r#type,
                    raw_type: field.ty.clone(),
                    span: field.span(),
                    serde_attrs: field
                        .attrs
                        .iter()
                        .filter(|attr| attr.path.is_ident("serde"))
                        .cloned()
                        .collect(),
                }
            })
            .collect(),
        _ => vec![],
    }
}

fn collect_docs_from_attrs(attrs: &[syn::Attribute]) -> String {
    attrs
        .iter()
        .filter(|attr| attr.path.is_ident("doc"))
        .fold(String::new(), |mut res, attr| {
            use once_cell::sync::Lazy;
            use regex::Regex;
            use std::fmt::Write;

            // dbg!(attr.parse_meta());

            let doc = TokenStream::from(attr.tokens.clone()).to_string();

            static RE: Lazy<Regex> =
                Lazy::new(|| regex::Regex::new(r#"^\s*=\s*"\s?(?P<content>.*?)"\s*$"#).unwrap());
            let doc = RE.replace_all(&doc, "$content");

            writeln!(&mut res, "{}", unescape_literal(&doc)).unwrap();
            res
        })
}

fn collect_examples_from_attrs(attrs: &[syn::Attribute]) -> Vec<Example> {
    use unindent::unindent;

    attrs
        .iter()
        .filter(|attr| attr.path.is_ident("example"))
        .map(|attr| {
            let list: Vec<syn::NestedMeta> = match attr.parse_meta() {
                Ok(syn::Meta::List(syn::MetaList { nested, .. })) => {
                    nested.iter().cloned().collect()
                }
                _ => unimplemented!("can't parse example attribute"),
            };
            assert_eq!(
                list.len(),
                2,
                "example attribute must be of form `#[example(lang_ident, \"lorem: ipsum\")]`"
            );
            // let (lang, example) = (&list[0], &list[1]);
            let lang = match &list[0] {
                syn::NestedMeta::Meta(syn::Meta::Path(syn::Path { segments, .. })) => {
                    segments.iter().next().unwrap().ident.to_string()
                }
                _ => unimplemented!("can't read lang of {:?}", list[0]),
            };
            let content = match &list[1] {
                syn::NestedMeta::Lit(syn::Lit::Str(val)) => unindent(&val.value()),
                _ => unimplemented!("can't content lang of {:?}", list[1]),
            };

            Example { lang, content }
        })
        .collect()
}

fn find_serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
    attrs
        .iter()
        .filter(|attr| attr.path.is_ident("serde"))
        .filter_map(|attr| match attr.parse_meta() {
            Ok(syn::Meta::List(syn::MetaList { nested, .. })) => nested
                .iter()
                .filter_map(|m| match m {
                    syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
                        path,
                        lit,
                        ..
                    })) if path.is_ident("rename") => match lit {
                        syn::Lit::Str(val) => Some(val.value()),
                        _ => unimplemented!(
                            "found serde rename that didn't have a string as value: {:?}",
                            m
                        ),
                    },
                    _ => None,
                })
                .next(),
            _ => return None,
        })
        .next()
}

fn unescape_literal(lit: &str) -> String {
    lit.replace("\\\"", "\"")
        .replace("\\'", "'")
        .replace(r"\\", r"\")
}

impl TypeName {
    fn from_ident(ident: &str) -> Self {
        match ident {
            "bool" | "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" | "usize"
            | "isize" | "String" | "PathBuf" | "Vec" | "HashMap" | "BTreeMap" | "T" => {
                Self::Primitive(ident.to_string())
            }
            _ => Self::Link(ident.to_string()),
        }
    }
}

impl Type {
    fn toplevel_from_syn(t: &syn::Type) -> (bool, Type) {
        use syn::Type as T;
        let mut required = true;

        let type_name = match t {
            T::Path(syn::TypePath {
                path: syn::Path { segments, .. },
                ..
            }) => {
                let syn::PathSegment { ident, arguments } =
                    segments.last().expect("type path was empty");

                // Do a little dance to not print the other `Option` wrapper but emit `required=false`
                if ident == "Option" {
                    required = false;
                    match arguments {
                        syn::PathArguments::AngleBracketed(
                            syn::AngleBracketedGenericArguments { args, .. },
                        ) => {
                            let type_arg = args
                                .iter()
                                .filter_map(|arg| {
                                    if let syn::GenericArgument::Type(t) = arg {
                                        Some(t)
                                    } else {
                                        None
                                    }
                                })
                                .next()
                                .expect("no type param on `Option`");
                            Type::from_syn(&type_arg)
                        }
                        _ => unimplemented!(
                            "only type paths with regular `<T>`-style generics implemented so far"
                        ),
                    }
                } else {
                    Type::from_syn(t)
                }
            }
            _ => unimplemented!("only type paths implemented so far"),
        };

        (required, type_name)
    }

    fn from_syn(t: &syn::Type) -> Self {
        use syn::Type as T;

        match t {
            T::Path(syn::TypePath {
                path: syn::Path { segments, .. },
                ..
            }) => {
                let syn::PathSegment { ident, arguments } =
                    segments.last().expect("type path was empty");

                let args = match arguments {
                    syn::PathArguments::None => vec![],
                    syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
                        args,
                        ..
                    }) => args
                        .iter()
                        .filter_map(|arg| {
                            if let syn::GenericArgument::Type(t) = arg {
                                Some(t)
                            } else {
                                None
                            }
                        })
                        .map(Type::from_syn)
                        .collect::<Vec<_>>(),
                    _ => unimplemented!(
                        "only type paths with regular `<T>`-style generics implemented so far"
                    ),
                };

                let name = TypeName::from_ident(&ident.to_string());

                if args.is_empty() {
                    Type::Flat(name)
                } else {
                    Type::Nested {
                        container_name: name,
                        nested: args,
                    }
                }
            }
            _ => unimplemented!("only type paths implemented so far"),
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate git_global;
extern crate regex;

mod utils;

use std::io::Cursor;

use regex::Regex;

use git_global::{subcommands, Report};

fn report_to_string(report: &Report) -> String {
    let mut out = Cursor::new(Vec::new());
    report.print(&mut out);
    String::from_utf8(out.into_inner()).unwrap()
}

#[test]
fn test_info() {
    utils::with_base_dir_of_three_repos(|config| {
        let basedir = config.basedir.clone();
        let cache = config.cache_file.clone().to_str().unwrap().to_string();
        let report = subcommands::info::execute(config).unwrap();
        let expected = vec![
            format!(r"^git-global {}$", crate_version!()),
            format!(r"^============+"),
            format!(r"^Number of repos: 3$"),
            format!(r"^Base directory: {}$", basedir),
            format!(r"^Cache file: {}$", cache),
            format!(r"^Cache file age: 0d, 0h, 0m, .s$"),
            format!(r"^Ignored patterns:$"),
            format!(r"^$"),
        ];
        let output = report_to_string(&report);
        for (i, line) in output.lines().enumerate() {
            let pattern = &expected[i];
            let re = Regex::new(pattern).unwrap();
            assert!(
                re.is_match(line),
                "Line {} didn't match; got {}, want {}",
                i + 1,
                line,
                pattern
            )
        }
    });
}

#[test]
fn test_list() {
    utils::with_base_dir_of_three_repos(|config| {
        let basedir = config.basedir.clone();
        let report = subcommands::list::execute(config).unwrap();
        // There are no global messages; the per-repo messages are simply a list
        // of the repo paths themselves.
        assert_eq!(
            report_to_string(&report),
            format!(
                "{0}/a\n\
                 {0}/b\n\
                 {0}/c\n",
                basedir
            )
        );
    });
}

#[test]
fn test_scan() {
    utils::with_base_dir_of_three_repos(|config| {
        let report = subcommands::scan::execute(config).unwrap();
        // There is one global message about the three repos we found.
        assert_eq!(
            report_to_string(&report),
            "Found 3 repos. Use `git global list` to show them.\n"
        );
    });
}

#[test]
fn test_status() {
    utils::with_base_dir_of_three_repos(|config| {
        let report = subcommands::status::execute(config).unwrap();
        // There are no global messages.
        assert_eq!(report_to_string(&report), "");
    });
}
<commit_msg>Make tests more platform independent<commit_after>#[macro_use]
extern crate clap;
extern crate git_global;
extern crate regex;

mod utils;

use std::io::Cursor;
use std::path::PathBuf;

use regex::{escape, Regex};

use git_global::{subcommands, Report};

fn report_to_string(report: &Report) -> String {
    let mut out = Cursor::new(Vec::new());
    report.print(&mut out);
    String::from_utf8(out.into_inner()).unwrap()
}

#[test]
fn test_info() {
    utils::with_base_dir_of_three_repos(|config| {
        let basedir = config.basedir.clone();
        let cache = config.cache_file.clone().to_str().unwrap().to_string();
        let report = subcommands::info::execute(config).unwrap();
        let expected = vec![
            format!(r"^git-global {}$", crate_version!()),
            format!(r"^============+"),
            format!(r"^Number of repos: 3$"),
            format!(r"^Base directory: {}$", escape(&basedir)),
            format!(r"^Cache file: {}$", escape(&cache)),
            format!(r"^Cache file age: 0d, 0h, 0m, .s$"),
            format!(r"^Ignored patterns:$"),
            format!(r"^$"),
        ];
        let output = report_to_string(&report);
        for (i, line) in output.lines().enumerate() {
            let pattern = &expected[i];
            let re = Regex::new(pattern).unwrap();
            assert!(
                re.is_match(line),
                "Line {} didn't match; got {}, want {}",
                i + 1,
                line,
                pattern
            )
        }
    });
}

#[test]
fn test_list() {
    utils::with_base_dir_of_three_repos(|config| {
        let basedir = config.basedir.clone();
        let report = subcommands::list::execute(config).unwrap();
        // There are no global messages; the per-repo messages are simply a list
        // of the repo paths themselves.
        let expected = vec![
            PathBuf::from(&basedir).join("a"),
            PathBuf::from(&basedir).join("b"),
            PathBuf::from(&basedir).join("c"),
        ];
        let output = report_to_string(&report);
        for (i, line) in output.lines().enumerate() {
            assert_eq!(expected[i].to_str().unwrap(), line);
        }
    });
}

#[test]
fn test_scan() {
    utils::with_base_dir_of_three_repos(|config| {
        let report = subcommands::scan::execute(config).unwrap();
        // There is one global message about the three repos we found.
        assert_eq!(
            report_to_string(&report),
            "Found 3 repos. Use `git global list` to show them.\n"
        );
    });
}

#[test]
fn test_status() {
    utils::with_base_dir_of_three_repos(|config| {
        let report = subcommands::status::execute(config).unwrap();
        // There are no global messages.
        assert_eq!(report_to_string(&report), "");
    });
}
<|endoftext|>


<commit_before>use hir::def_id::DefId;
use hir::HirId;
use hir::ItemKind;
use rustc_ast::Mutability;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_middle::ty::subst::InternalSubsts;
use rustc_middle::ty::{Adt, Ref, Ty};
use rustc_session::lint::builtin::RUST_2021_PRELUDE_COLLISIONS;
use rustc_span::symbol::kw::Underscore;
use rustc_span::symbol::{sym, Ident};
use rustc_span::Span;
use rustc_trait_selection::infer::InferCtxtExt;

use crate::check::{
    method::probe::{self, Pick},
    FnCtxt,
};

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
    pub(super) fn lint_dot_call_from_2018(
        &self,
        self_ty: Ty<'tcx>,
        segment: &hir::PathSegment<'_>,
        span: Span,
        call_expr: &'tcx hir::Expr<'tcx>,
        self_expr: &'tcx hir::Expr<'tcx>,
        pick: &Pick<'tcx>,
        args: &'tcx [hir::Expr<'tcx>],
    ) {
        debug!(
            "lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})",
            segment.ident, self_ty, call_expr, self_expr
        );

        // Rust 2021 and later is already using the new prelude
        if span.rust_2021() {
            return;
        }

        // These are the method names that were added to prelude in Rust 2021
        if !matches!(segment.ident.name, sym::try_into) {
            return;
        }

        // No need to lint if method came from std/core, as that will now be in the prelude
        if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
            return;
        }

        if matches!(pick.kind, probe::PickKind::InherentImplPick | probe::PickKind::ObjectPick) {
            // avoid repeatedly adding unneeded `&*`s
            if pick.autoderefs == 1
                && matches!(
                    pick.autoref_or_ptr_adjustment,
                    Some(probe::AutorefOrPtrAdjustment::Autoref { .. })
                )
                && matches!(self_ty.kind(), Ref(..))
            {
                return;
            }

            // if it's an inherent `self` method (not `&self` or `&mut self`), it will take
            // precedence over the `TryInto` impl, and thus won't break in 2021 edition
            if pick.autoderefs == 0 && pick.autoref_or_ptr_adjustment.is_none() {
                return;
            }

            // Inherent impls only require not relying on autoref and autoderef in order to
            // ensure that the trait implementation won't be used
            self.tcx.struct_span_lint_hir(
                RUST_2021_PRELUDE_COLLISIONS,
                self_expr.hir_id,
                self_expr.span,
                |lint| {
                    let sp = self_expr.span;

                    let mut lint = lint.build(&format!(
                        "trait method `{}` will become ambiguous in Rust 2021",
                        segment.ident.name
                    ));

                    let derefs = "*".repeat(pick.autoderefs);

                    let autoref = match pick.autoref_or_ptr_adjustment {
                        Some(probe::AutorefOrPtrAdjustment::Autoref {
                            mutbl: Mutability::Mut,
                            ..
                        }) => "&mut ",
                        Some(probe::AutorefOrPtrAdjustment::Autoref {
                            mutbl: Mutability::Not,
                            ..
                        }) => "&",
                        Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
                    };
                    if let Ok(self_expr) = self.sess().source_map().span_to_snippet(self_expr.span)
                    {
                        let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
                            pick.autoref_or_ptr_adjustment
                        {
                            format!("{}{} as *const _", derefs, self_expr)
                        } else {
                            format!("{}{}{}", autoref, derefs, self_expr)
                        };

                        lint.span_suggestion(
                            sp,
                            "disambiguate the method call",
                            format!("({})", self_adjusted),
                            Applicability::MachineApplicable,
                        );
                    } else {
                        let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
                            pick.autoref_or_ptr_adjustment
                        {
                            format!("{}(...) as *const _", derefs)
                        } else {
                            format!("{}{}...", autoref, derefs)
                        };
                        lint.span_help(
                            sp,
                            &format!("disambiguate the method call with `({})`", self_adjusted,),
                        );
                    }

                    lint.emit();
                },
            );
        } else {
            // trait implementations require full disambiguation to not clash with the new prelude
            // additions (i.e. convert from dot-call to fully-qualified call)
            self.tcx.struct_span_lint_hir(
                RUST_2021_PRELUDE_COLLISIONS,
                call_expr.hir_id,
                call_expr.span,
                |lint| {
                    let sp = call_expr.span;
                    let trait_name = self.trait_path_or_bare_name(
                        span,
                        call_expr.hir_id,
                        pick.item.container.id(),
                    );

                    let mut lint = lint.build(&format!(
                        "trait method `{}` will become ambiguous in Rust 2021",
                        segment.ident.name
                    ));

                    let (self_adjusted, precise) = self.adjust_expr(pick, self_expr);
                    if precise {
                        let args = args
                            .iter()
                            .skip(1)
                            .map(|arg| {
                                format!(
                                    ", {}",
                                    self.sess().source_map().span_to_snippet(arg.span).unwrap()
                                )
                            })
                            .collect::<String>();

                        lint.span_suggestion(
                            sp,
                            "disambiguate the associated function",
                            format!(
                                "{}::{}({}{})",
                                trait_name, segment.ident.name, self_adjusted, args
                            ),
                            Applicability::MachineApplicable,
                        );
                    } else {
                        lint.span_help(
                            sp,
                            &format!(
                                "disambiguate the associated function with `{}::{}(...)`",
                                trait_name, segment.ident,
                            ),
                        );
                    }

                    lint.emit();
                },
            );
        }
    }

    pub(super) fn lint_fully_qualified_call_from_2018(
        &self,
        span: Span,
        method_name: Ident,
        self_ty: Ty<'tcx>,
        self_ty_span: Span,
        expr_id: hir::HirId,
        pick: &Pick<'tcx>,
    ) {
        // Rust 2021 and later is already using the new prelude
        if span.rust_2021() {
            return;
        }

        // These are the fully qualified methods added to prelude in Rust 2021
        if !matches!(method_name.name, sym::try_into | sym::try_from | sym::from_iter) {
            return;
        }

        // No need to lint if method came from std/core, as that will now be in the prelude
        if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
            return;
        }

        // For from_iter, check if the type actually implements FromIterator.
        // If we know it does not, we don't need to warn.
        if method_name.name == sym::from_iter {
            if let Some(trait_def_id) = self.tcx.get_diagnostic_item(sym::FromIterator) {
                if !self
                    .infcx
                    .type_implements_trait(
                        trait_def_id,
                        self_ty,
                        InternalSubsts::empty(),
                        self.param_env,
                    )
                    .may_apply()
                {
                    return;
                }
            }
        }

        // No need to lint if this is an inherent method called on a specific type, like `Vec::foo(...)`,
        // since such methods take precedence over trait methods.
        if matches!(pick.kind, probe::PickKind::InherentImplPick) {
            return;
        }

        self.tcx.struct_span_lint_hir(RUST_2021_PRELUDE_COLLISIONS, expr_id, span, |lint| {
            // "type" refers to either a type or, more likely, a trait from which
            // the associated function or method is from.
            let trait_path = self.trait_path_or_bare_name(span, expr_id, pick.item.container.id());
            let trait_generics = self.tcx.generics_of(pick.item.container.id());

            let parameter_count = trait_generics.count() - (trait_generics.has_self as usize);
            let trait_name = if parameter_count == 0 {
                trait_path
            } else {
                format!(
                    "{}<{}>",
                    trait_path,
                    std::iter::repeat("_").take(parameter_count).collect::<Vec<_>>().join(", ")
                )
            };

            let mut lint = lint.build(&format!(
                "trait-associated function `{}` will become ambiguous in Rust 2021",
                method_name.name
            ));

            let self_ty_name = self
                .sess()
                .source_map()
                .span_to_snippet(self_ty_span)
                .unwrap_or_else(|_| self_ty.to_string());

            let self_ty_generics_count = match self_ty.kind() {
                // Get the number of generics the self type has (if an Adt) unless we can determine that
                // the user has written the self type with generics already which we (naively) do by looking
                // for a "<" in `self_ty_name`.
                Adt(def, _) if !self_ty_name.contains('<') => self.tcx.generics_of(def.did).count(),
                _ => 0,
            };
            let self_ty_generics = if self_ty_generics_count > 0 {
                format!("<{}>", vec!["_"; self_ty_generics_count].join(", "))
            } else {
                String::new()
            };
            lint.span_suggestion(
                span,
                "disambiguate the associated function",
                format!(
                    "<{}{} as {}>::{}",
                    self_ty_name, self_ty_generics, trait_name, method_name.name,
                ),
                Applicability::MachineApplicable,
            );

            lint.emit();
        });
    }

    fn trait_path_or_bare_name(
        &self,
        span: Span,
        expr_hir_id: HirId,
        trait_def_id: DefId,
    ) -> String {
        self.trait_path(span, expr_hir_id, trait_def_id).unwrap_or_else(|| {
            let key = self.tcx.def_key(trait_def_id);
            format!("{}", key.disambiguated_data.data)
        })
    }

    fn trait_path(&self, span: Span, expr_hir_id: HirId, trait_def_id: DefId) -> Option<String> {
        let applicable_traits = self.tcx.in_scope_traits(expr_hir_id)?;
        let applicable_trait = applicable_traits.iter().find(|t| t.def_id == trait_def_id)?;
        if applicable_trait.import_ids.is_empty() {
            // The trait was declared within the module, we only need to use its name.
            return None;
        }

        let import_items: Vec<_> = applicable_trait
            .import_ids
            .iter()
            .map(|&import_id| {
                let hir_id = self.tcx.hir().local_def_id_to_hir_id(import_id);
                self.tcx.hir().expect_item(hir_id)
            })
            .collect();

        // Find an identifier with which this trait was imported (note that `_` doesn't count).
        let any_id = import_items
            .iter()
            .filter_map(|item| if item.ident.name != Underscore { Some(item.ident) } else { None })
            .next();
        if let Some(any_id) = any_id {
            return Some(format!("{}", any_id));
        }

        // All that is left is `_`! We need to use the full path. It doesn't matter which one we pick,
        // so just take the first one.
        match import_items[0].kind {
            ItemKind::Use(path, _) => Some(
                path.segments
                    .iter()
                    .map(|segment| segment.ident.to_string())
                    .collect::<Vec<_>>()
                    .join("::"),
            ),
            _ => {
                span_bug!(span, "unexpected item kind, expected a use: {:?}", import_items[0].kind);
            }
        }
    }

    /// Creates a string version of the `expr` that includes explicit adjustments.
    /// Returns the string and also a bool indicating whther this is a *precise*
    /// suggestion.
    fn adjust_expr(&self, pick: &Pick<'tcx>, expr: &hir::Expr<'tcx>) -> (String, bool) {
        let derefs = "*".repeat(pick.autoderefs);

        let autoref = match pick.autoref_or_ptr_adjustment {
            Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Mut, .. }) => "&mut ",
            Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Not, .. }) => "&",
            Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
        };

        let (expr_text, precise) =
            if let Ok(expr_text) = self.sess().source_map().span_to_snippet(expr.span) {
                (expr_text, true)
            } else {
                ("(..)".to_string(), false)
            };

        let adjusted_text = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
            pick.autoref_or_ptr_adjustment
        {
            format!("{}{} as *const _", derefs, expr_text)
        } else {
            format!("{}{}{}", autoref, derefs, expr_text)
        };

        (adjusted_text, precise)
    }
}
<commit_msg>Fix prelude collision suggestions for glob imported traits.<commit_after>use hir::def_id::DefId;
use hir::HirId;
use hir::ItemKind;
use rustc_ast::Mutability;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_middle::ty::subst::InternalSubsts;
use rustc_middle::ty::{Adt, Ref, Ty};
use rustc_session::lint::builtin::RUST_2021_PRELUDE_COLLISIONS;
use rustc_span::symbol::kw::{Empty, Underscore};
use rustc_span::symbol::{sym, Ident};
use rustc_span::Span;
use rustc_trait_selection::infer::InferCtxtExt;

use crate::check::{
    method::probe::{self, Pick},
    FnCtxt,
};

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
    pub(super) fn lint_dot_call_from_2018(
        &self,
        self_ty: Ty<'tcx>,
        segment: &hir::PathSegment<'_>,
        span: Span,
        call_expr: &'tcx hir::Expr<'tcx>,
        self_expr: &'tcx hir::Expr<'tcx>,
        pick: &Pick<'tcx>,
        args: &'tcx [hir::Expr<'tcx>],
    ) {
        debug!(
            "lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})",
            segment.ident, self_ty, call_expr, self_expr
        );

        // Rust 2021 and later is already using the new prelude
        if span.rust_2021() {
            return;
        }

        // These are the method names that were added to prelude in Rust 2021
        if !matches!(segment.ident.name, sym::try_into) {
            return;
        }

        // No need to lint if method came from std/core, as that will now be in the prelude
        if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
            return;
        }

        if matches!(pick.kind, probe::PickKind::InherentImplPick | probe::PickKind::ObjectPick) {
            // avoid repeatedly adding unneeded `&*`s
            if pick.autoderefs == 1
                && matches!(
                    pick.autoref_or_ptr_adjustment,
                    Some(probe::AutorefOrPtrAdjustment::Autoref { .. })
                )
                && matches!(self_ty.kind(), Ref(..))
            {
                return;
            }

            // if it's an inherent `self` method (not `&self` or `&mut self`), it will take
            // precedence over the `TryInto` impl, and thus won't break in 2021 edition
            if pick.autoderefs == 0 && pick.autoref_or_ptr_adjustment.is_none() {
                return;
            }

            // Inherent impls only require not relying on autoref and autoderef in order to
            // ensure that the trait implementation won't be used
            self.tcx.struct_span_lint_hir(
                RUST_2021_PRELUDE_COLLISIONS,
                self_expr.hir_id,
                self_expr.span,
                |lint| {
                    let sp = self_expr.span;

                    let mut lint = lint.build(&format!(
                        "trait method `{}` will become ambiguous in Rust 2021",
                        segment.ident.name
                    ));

                    let derefs = "*".repeat(pick.autoderefs);

                    let autoref = match pick.autoref_or_ptr_adjustment {
                        Some(probe::AutorefOrPtrAdjustment::Autoref {
                            mutbl: Mutability::Mut,
                            ..
                        }) => "&mut ",
                        Some(probe::AutorefOrPtrAdjustment::Autoref {
                            mutbl: Mutability::Not,
                            ..
                        }) => "&",
                        Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
                    };
                    if let Ok(self_expr) = self.sess().source_map().span_to_snippet(self_expr.span)
                    {
                        let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
                            pick.autoref_or_ptr_adjustment
                        {
                            format!("{}{} as *const _", derefs, self_expr)
                        } else {
                            format!("{}{}{}", autoref, derefs, self_expr)
                        };

                        lint.span_suggestion(
                            sp,
                            "disambiguate the method call",
                            format!("({})", self_adjusted),
                            Applicability::MachineApplicable,
                        );
                    } else {
                        let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
                            pick.autoref_or_ptr_adjustment
                        {
                            format!("{}(...) as *const _", derefs)
                        } else {
                            format!("{}{}...", autoref, derefs)
                        };
                        lint.span_help(
                            sp,
                            &format!("disambiguate the method call with `({})`", self_adjusted,),
                        );
                    }

                    lint.emit();
                },
            );
        } else {
            // trait implementations require full disambiguation to not clash with the new prelude
            // additions (i.e. convert from dot-call to fully-qualified call)
            self.tcx.struct_span_lint_hir(
                RUST_2021_PRELUDE_COLLISIONS,
                call_expr.hir_id,
                call_expr.span,
                |lint| {
                    let sp = call_expr.span;
                    let trait_name = self.trait_path_or_bare_name(
                        span,
                        call_expr.hir_id,
                        pick.item.container.id(),
                    );

                    let mut lint = lint.build(&format!(
                        "trait method `{}` will become ambiguous in Rust 2021",
                        segment.ident.name
                    ));

                    let (self_adjusted, precise) = self.adjust_expr(pick, self_expr);
                    if precise {
                        let args = args
                            .iter()
                            .skip(1)
                            .map(|arg| {
                                format!(
                                    ", {}",
                                    self.sess().source_map().span_to_snippet(arg.span).unwrap()
                                )
                            })
                            .collect::<String>();

                        lint.span_suggestion(
                            sp,
                            "disambiguate the associated function",
                            format!(
                                "{}::{}({}{})",
                                trait_name, segment.ident.name, self_adjusted, args
                            ),
                            Applicability::MachineApplicable,
                        );
                    } else {
                        lint.span_help(
                            sp,
                            &format!(
                                "disambiguate the associated function with `{}::{}(...)`",
                                trait_name, segment.ident,
                            ),
                        );
                    }

                    lint.emit();
                },
            );
        }
    }

    pub(super) fn lint_fully_qualified_call_from_2018(
        &self,
        span: Span,
        method_name: Ident,
        self_ty: Ty<'tcx>,
        self_ty_span: Span,
        expr_id: hir::HirId,
        pick: &Pick<'tcx>,
    ) {
        // Rust 2021 and later is already using the new prelude
        if span.rust_2021() {
            return;
        }

        // These are the fully qualified methods added to prelude in Rust 2021
        if !matches!(method_name.name, sym::try_into | sym::try_from | sym::from_iter) {
            return;
        }

        // No need to lint if method came from std/core, as that will now be in the prelude
        if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
            return;
        }

        // For from_iter, check if the type actually implements FromIterator.
        // If we know it does not, we don't need to warn.
        if method_name.name == sym::from_iter {
            if let Some(trait_def_id) = self.tcx.get_diagnostic_item(sym::FromIterator) {
                if !self
                    .infcx
                    .type_implements_trait(
                        trait_def_id,
                        self_ty,
                        InternalSubsts::empty(),
                        self.param_env,
                    )
                    .may_apply()
                {
                    return;
                }
            }
        }

        // No need to lint if this is an inherent method called on a specific type, like `Vec::foo(...)`,
        // since such methods take precedence over trait methods.
        if matches!(pick.kind, probe::PickKind::InherentImplPick) {
            return;
        }

        self.tcx.struct_span_lint_hir(RUST_2021_PRELUDE_COLLISIONS, expr_id, span, |lint| {
            // "type" refers to either a type or, more likely, a trait from which
            // the associated function or method is from.
            let trait_path = self.trait_path_or_bare_name(span, expr_id, pick.item.container.id());
            let trait_generics = self.tcx.generics_of(pick.item.container.id());

            let parameter_count = trait_generics.count() - (trait_generics.has_self as usize);
            let trait_name = if parameter_count == 0 {
                trait_path
            } else {
                format!(
                    "{}<{}>",
                    trait_path,
                    std::iter::repeat("_").take(parameter_count).collect::<Vec<_>>().join(", ")
                )
            };

            let mut lint = lint.build(&format!(
                "trait-associated function `{}` will become ambiguous in Rust 2021",
                method_name.name
            ));

            let self_ty_name = self
                .sess()
                .source_map()
                .span_to_snippet(self_ty_span)
                .unwrap_or_else(|_| self_ty.to_string());

            let self_ty_generics_count = match self_ty.kind() {
                // Get the number of generics the self type has (if an Adt) unless we can determine that
                // the user has written the self type with generics already which we (naively) do by looking
                // for a "<" in `self_ty_name`.
                Adt(def, _) if !self_ty_name.contains('<') => self.tcx.generics_of(def.did).count(),
                _ => 0,
            };
            let self_ty_generics = if self_ty_generics_count > 0 {
                format!("<{}>", vec!["_"; self_ty_generics_count].join(", "))
            } else {
                String::new()
            };
            lint.span_suggestion(
                span,
                "disambiguate the associated function",
                format!(
                    "<{}{} as {}>::{}",
                    self_ty_name, self_ty_generics, trait_name, method_name.name,
                ),
                Applicability::MachineApplicable,
            );

            lint.emit();
        });
    }

    fn trait_path_or_bare_name(
        &self,
        span: Span,
        expr_hir_id: HirId,
        trait_def_id: DefId,
    ) -> String {
        self.trait_path(span, expr_hir_id, trait_def_id).unwrap_or_else(|| {
            let key = self.tcx.def_key(trait_def_id);
            format!("{}", key.disambiguated_data.data)
        })
    }

    fn trait_path(&self, span: Span, expr_hir_id: HirId, trait_def_id: DefId) -> Option<String> {
        let applicable_traits = self.tcx.in_scope_traits(expr_hir_id)?;
        let applicable_trait = applicable_traits.iter().find(|t| t.def_id == trait_def_id)?;
        if applicable_trait.import_ids.is_empty() {
            // The trait was declared within the module, we only need to use its name.
            return None;
        }

        let import_items: Vec<_> = applicable_trait
            .import_ids
            .iter()
            .map(|&import_id| {
                let hir_id = self.tcx.hir().local_def_id_to_hir_id(import_id);
                self.tcx.hir().expect_item(hir_id)
            })
            .collect();

        // Find an identifier with which this trait was imported (note that `_` doesn't count).
        let any_id = import_items
            .iter()
            .filter_map(|item| if item.ident.name != Underscore { Some(item.ident) } else { None })
            .next();
        if let Some(any_id) = any_id {
            if any_id.name == Empty {
                // Glob import, so just use its name.
                return None;
            } else {
                return Some(format!("{}", any_id));
            }
        }

        // All that is left is `_`! We need to use the full path. It doesn't matter which one we pick,
        // so just take the first one.
        match import_items[0].kind {
            ItemKind::Use(path, _) => Some(
                path.segments
                    .iter()
                    .map(|segment| segment.ident.to_string())
                    .collect::<Vec<_>>()
                    .join("::"),
            ),
            _ => {
                span_bug!(span, "unexpected item kind, expected a use: {:?}", import_items[0].kind);
            }
        }
    }

    /// Creates a string version of the `expr` that includes explicit adjustments.
    /// Returns the string and also a bool indicating whther this is a *precise*
    /// suggestion.
    fn adjust_expr(&self, pick: &Pick<'tcx>, expr: &hir::Expr<'tcx>) -> (String, bool) {
        let derefs = "*".repeat(pick.autoderefs);

        let autoref = match pick.autoref_or_ptr_adjustment {
            Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Mut, .. }) => "&mut ",
            Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Not, .. }) => "&",
            Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
        };

        let (expr_text, precise) =
            if let Ok(expr_text) = self.sess().source_map().span_to_snippet(expr.span) {
                (expr_text, true)
            } else {
                ("(..)".to_string(), false)
            };

        let adjusted_text = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
            pick.autoref_or_ptr_adjustment
        {
            format!("{}{} as *const _", derefs, expr_text)
        } else {
            format!("{}{}{}", autoref, derefs, expr_text)
        };

        (adjusted_text, precise)
    }
}
<|endoftext|>


<commit_before>use std::thread;
use std::sync::mpsc;
use websocket::{Server, Message, Sender, Receiver};
use websocket::server::sender;
use websocket::stream::WebSocketStream;
use std::collections::{HashMap, BitSet};
use std::io::prelude::*;
use std::num::ToPrimitive;
use rustc_serialize::json::{Json, ToJson};

use value::{Value, Tuple};
use index;
use flow::{Changes, FlowState, Flow};
use compiler::{compile, World};

trait FromJson {
    fn from_json(json: &Json) -> Self;
}

impl ToJson for Value {
    fn to_json(&self) -> Json {
        match *self {
            Value::String(ref string) => Json::String(string.clone()),
            Value::Float(float) => Json::F64(float),
            Value::Tuple(ref tuple) => tuple.to_json(),
            Value::Relation(ref relation) => Json::Object(vec![
                ("relation".to_string(), relation.iter().map(ToJson::to_json).collect::<Vec<_>>().to_json())
                ].into_iter().collect()),
        }
    }
}

impl FromJson for Value {
    fn from_json(json: &Json) -> Self {
        match *json {
            Json::String(ref string) => Value::String(string.clone()),
            Json::F64(float) => Value::Float(float),
            Json::I64(int) => Value::Float(int.to_f64().unwrap()),
            Json::U64(uint) => Value::Float(uint.to_f64().unwrap()),
            Json::Array(ref array) => Value::Tuple(array.iter().map(|j| Value::from_json(j)).collect()),
            Json::Object(ref object) => {
                assert!(object.len() == 1);
                let relation: Vec<Tuple> = FromJson::from_json(&object["relation"]);
                Value::Relation(relation.into_iter().collect())
            },
            _ => panic!("Cannot decode {:?} as Value", json),
        }
    }
}

impl<T: FromJson> FromJson for Vec<T> {
    fn from_json(json: &Json) -> Self {
        json.as_array().unwrap().iter().map(FromJson::from_json).collect()
    }
}

pub struct Event {
    changes: Changes,
}

impl ToJson for Event {
    fn to_json(&self) -> Json {
        Json::Object(vec![
            ("changes".to_string(), Json::Object(
                self.changes.iter().map(|&(ref view_id, ref view_changes)| {
                    (view_id.to_string(), Json::Object(vec![
                        ("inserted".to_string(), view_changes.inserted.to_json()),
                        ("removed".to_string(), view_changes.removed.to_json()),
                        ].into_iter().collect()))
                }).collect()))].into_iter().collect())
    }
}

impl FromJson for Event {
    fn from_json(json: &Json) -> Self {
        Event{
            changes: json.as_object().unwrap()["changes"]
            .as_object().unwrap().iter().map(|(view_id, view_changes)| {
                (view_id.to_string(), index::Changes{
                    inserted: FromJson::from_json(&view_changes.as_object().unwrap()["inserted"]),
                    removed: FromJson::from_json(&view_changes.as_object().unwrap()["removed"]),
                })
            }).collect()
        }
    }
}

struct Instance {
    input: World,
    flow: Flow,
    output: FlowState,
}

impl Instance {
    pub fn change(&mut self, changes: Changes) -> Changes {
        self.input.change(changes);
        let mut input_clone = self.input.clone();
        let (flow, mut output) = compile(&mut input_clone);
        flow.run(&mut output);
        let changes = flow.changes_since(&output, &self.flow, &self.output);
        self.flow = flow;
        self.output = output;
        changes
    }
}

// TODO holy crap why is everything blocking? this is a mess
pub fn serve() -> (mpsc::Receiver<Event>, mpsc::Receiver<sender::Sender<WebSocketStream>>) {
    let (input_sender, input_receiver) = mpsc::channel();
    let (sender_sender, sender_receiver) = mpsc::channel();
    thread::spawn(move || {
        let server = Server::bind("127.0.0.1:2794").unwrap();
        for connection in server {
            let input_sender = input_sender.clone();
            let sender_sender = sender_sender.clone();
            thread::spawn(move || {
                // accept request
                let request = connection.unwrap().read_request().unwrap();
                request.validate().unwrap();
                let response = request.accept();
                let (mut sender, mut receiver) = response.send().unwrap().split();

                let ip = sender.get_mut().peer_addr().unwrap();
                println!("Connection from {}", ip);
                ::std::io::stdout().flush().unwrap(); // TODO is this actually necessary?

                // hand over sender
                sender_sender.send(sender).unwrap();

                // handle messages
                for message in receiver.incoming_messages() {
                    let message = message.unwrap();
                    match message {
                        Message::Text(text) => {
                            let json = Json::from_str(&text).unwrap();
                            let event = FromJson::from_json(&json);
                            input_sender.send(event).unwrap();
                        }
                        _ => panic!("Unknown message: {:?}", message)
                    }
                }
            });
        }
    });
    (input_receiver, sender_receiver)
}

pub fn run() {
    let empty_world = World{views: HashMap::new()};
    let empty_flow = Flow{nodes: Vec::new()};
    let empty_output = FlowState{outputs: Vec::new(), dirty: BitSet::new()};
    let mut instance = Instance{
        input: empty_world,
        flow: empty_flow.clone(),
        output: empty_output.clone(),
    };
    let mut senders: Vec<sender::Sender<_>> = Vec::new();
    let (input_receiver, sender_receiver) = serve();
    loop {
        select!(
            input = input_receiver.recv() => {
                time!("sending initial state", {
                    let input = input.unwrap();
                    let changes = instance.change(input.changes);
                    let text = format!("{}", Event{changes: changes}.to_json());
                    for sender in senders.iter_mut() {
                        sender.send_message(Message::Text(text.clone())).unwrap();
                    }
                })
            },
            sender = sender_receiver.recv() => {
                time!("sending update", {
                    let mut sender = sender.unwrap();
                    let changes = instance.flow.changes_since(&instance.output, &empty_flow, &empty_output);
                    let text = format!("{}", Event{changes: changes}.to_json());
                    sender.send_message(Message::Text(text)).unwrap();
                    senders.push(sender)
                })
            }
            )
    }
}<commit_msg>Fix potential race condition<commit_after>use std::thread;
use std::sync::mpsc;
use websocket::{Server, Message, Sender, Receiver};
use websocket::server::sender;
use websocket::stream::WebSocketStream;
use std::collections::{HashMap, BitSet};
use std::io::prelude::*;
use std::num::ToPrimitive;
use rustc_serialize::json::{Json, ToJson};

use value::{Value, Tuple};
use index;
use flow::{Changes, FlowState, Flow};
use compiler::{compile, World};

trait FromJson {
    fn from_json(json: &Json) -> Self;
}

impl ToJson for Value {
    fn to_json(&self) -> Json {
        match *self {
            Value::String(ref string) => Json::String(string.clone()),
            Value::Float(float) => Json::F64(float),
            Value::Tuple(ref tuple) => tuple.to_json(),
            Value::Relation(ref relation) => Json::Object(vec![
                ("relation".to_string(), relation.iter().map(ToJson::to_json).collect::<Vec<_>>().to_json())
                ].into_iter().collect()),
        }
    }
}

impl FromJson for Value {
    fn from_json(json: &Json) -> Self {
        match *json {
            Json::String(ref string) => Value::String(string.clone()),
            Json::F64(float) => Value::Float(float),
            Json::I64(int) => Value::Float(int.to_f64().unwrap()),
            Json::U64(uint) => Value::Float(uint.to_f64().unwrap()),
            Json::Array(ref array) => Value::Tuple(array.iter().map(|j| Value::from_json(j)).collect()),
            Json::Object(ref object) => {
                assert!(object.len() == 1);
                let relation: Vec<Tuple> = FromJson::from_json(&object["relation"]);
                Value::Relation(relation.into_iter().collect())
            },
            _ => panic!("Cannot decode {:?} as Value", json),
        }
    }
}

impl<T: FromJson> FromJson for Vec<T> {
    fn from_json(json: &Json) -> Self {
        json.as_array().unwrap().iter().map(FromJson::from_json).collect()
    }
}

pub struct Event {
    changes: Changes,
}

impl ToJson for Event {
    fn to_json(&self) -> Json {
        Json::Object(vec![
            ("changes".to_string(), Json::Object(
                self.changes.iter().map(|&(ref view_id, ref view_changes)| {
                    (view_id.to_string(), Json::Object(vec![
                        ("inserted".to_string(), view_changes.inserted.to_json()),
                        ("removed".to_string(), view_changes.removed.to_json()),
                        ].into_iter().collect()))
                }).collect()))].into_iter().collect())
    }
}

impl FromJson for Event {
    fn from_json(json: &Json) -> Self {
        Event{
            changes: json.as_object().unwrap()["changes"]
            .as_object().unwrap().iter().map(|(view_id, view_changes)| {
                (view_id.to_string(), index::Changes{
                    inserted: FromJson::from_json(&view_changes.as_object().unwrap()["inserted"]),
                    removed: FromJson::from_json(&view_changes.as_object().unwrap()["removed"]),
                })
            }).collect()
        }
    }
}

struct Instance {
    input: World,
    flow: Flow,
    output: FlowState,
}

impl Instance {
    pub fn change(&mut self, changes: Changes) -> Changes {
        self.input.change(changes);
        let mut input_clone = self.input.clone();
        let (flow, mut output) = compile(&mut input_clone);
        flow.run(&mut output);
        let changes = flow.changes_since(&output, &self.flow, &self.output);
        self.flow = flow;
        self.output = output;
        changes
    }
}

pub enum ServerEvent {
    Event(Event),
    NewClient(sender::Sender<WebSocketStream>),
}

// TODO holy crap why is everything blocking? this is a mess
pub fn serve() -> mpsc::Receiver<ServerEvent> {
    let (event_sender, event_receiver) = mpsc::channel();
    thread::spawn(move || {
        let server = Server::bind("127.0.0.1:2794").unwrap();
        for connection in server {
            let event_sender = event_sender.clone();
            thread::spawn(move || {
                // accept request
                let request = connection.unwrap().read_request().unwrap();
                request.validate().unwrap();
                let response = request.accept();
                let (mut sender, mut receiver) = response.send().unwrap().split();

                let ip = sender.get_mut().peer_addr().unwrap();
                println!("Connection from {}", ip);
                ::std::io::stdout().flush().unwrap(); // TODO is this actually necessary?

                // hand over sender
                event_sender.send(ServerEvent::NewClient(sender)).unwrap();

                // handle messages
                for message in receiver.incoming_messages() {
                    let message = message.unwrap();
                    match message {
                        Message::Text(text) => {
                            let json = Json::from_str(&text).unwrap();
                            let event = FromJson::from_json(&json);
                            event_sender.send(ServerEvent::Event(event)).unwrap();
                        }
                        _ => panic!("Unknown message: {:?}", message)
                    }
                }
            });
        }
    });
    (input_receiver, sender_receiver)
}

pub fn run() {
    let empty_world = World{views: HashMap::new()};
    let empty_flow = Flow{nodes: Vec::new()};
    let empty_output = FlowState{outputs: Vec::new(), dirty: BitSet::new()};
    let mut instance = Instance{
        input: empty_world,
        flow: empty_flow.clone(),
        output: empty_output.clone(),
    };
    let mut senders: Vec<sender::Sender<_>> = Vec::new();
    let event_receiver = serve();
    for event in event_receiver.iter() {
        match event {
            ServerEvent::NewClient(mut sender) => {
                time!("sending initial state", {
                    let changes = instance.flow.changes_since(&instance.output, &empty_flow, &empty_output);
                    let text = format!("{}", Event{changes: changes}.to_json());
                    sender.send_message(Message::Text(text)).unwrap();
                    senders.push(sender)
                })
            }
            ServerEvent::Event(event) => {
                time!("sending update", {
                    let changes = instance.change(event.changes);
                    let text = format!("{}", Event{changes: changes}.to_json());
                    for sender in senders.iter_mut() {
                        sender.send_message(Message::Text(text.clone())).unwrap();
                    }
                })
            }
        }
    }
}<|endoftext|>


<commit_before>extern crate glib;
extern crate cairo;
extern crate libc;

use self::glib::translate::*;

use std::cell::Cell;
use std::ptr;

use aspect_ratio::*;
use drawing_ctx::RsvgDrawingCtx;
use drawing_ctx;
use handle::RsvgHandle;
use length::*;
use node::*;
use property_bag;
use property_bag::*;
use util::*;
use viewbox::*;

use self::cairo::MatrixTrait;

/***** NodeGroup *****/

struct NodeGroup ();

impl NodeGroup {
    fn new () -> NodeGroup {
        NodeGroup ()
    }
}

impl NodeTrait for NodeGroup {
    fn set_atts (&self, _: &RsvgNode, _: *const RsvgHandle, _: *const RsvgPropertyBag) -> NodeResult {
        Ok (())
    }

    fn draw (&self, node: &RsvgNode, draw_ctx: *const RsvgDrawingCtx, dominate: i32) {
        node.draw_children (draw_ctx, dominate);
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

/***** NodeDefs *****/

struct NodeDefs ();

impl NodeDefs {
    fn new () -> NodeDefs {
        NodeDefs ()
    }
}

impl NodeTrait for NodeDefs {
    fn set_atts (&self, _: &RsvgNode, _: *const RsvgHandle, _: *const RsvgPropertyBag) -> NodeResult {
        Ok (())
    }

    fn draw (&self, _: &RsvgNode, _: *const RsvgDrawingCtx, _: i32) {
        // nothing
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

/***** NodeSwitch *****/

struct NodeSwitch ();

impl NodeSwitch {
    fn new () -> NodeSwitch {
        NodeSwitch ()
    }
}

impl NodeTrait for NodeSwitch {
    fn set_atts (&self, _: &RsvgNode, _: *const RsvgHandle, _: *const RsvgPropertyBag) -> NodeResult {
        Ok (())
    }

    fn draw (&self, node: &RsvgNode, draw_ctx: *const RsvgDrawingCtx, dominate: i32) {
        drawing_ctx::state_reinherit_top (draw_ctx, node.get_state (), dominate);

        drawing_ctx::push_discrete_layer (draw_ctx);

        for child in &*node.children.borrow () {
            if drawing_ctx::state_get_cond_true (child.get_state ()) {
                let boxed_child = box_node (child.clone ());

                drawing_ctx::draw_node_from_stack (draw_ctx, boxed_child, 0);

                rsvg_node_unref (boxed_child);

                break;
            }
        }

        drawing_ctx::pop_discrete_layer (draw_ctx);
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

/***** NodeSvg *****/

struct NodeSvg {
    preserve_aspect_ratio: Cell<AspectRatio>,
    x:                     Cell<RsvgLength>,
    y:                     Cell<RsvgLength>,
    w:                     Cell<RsvgLength>,
    h:                     Cell<RsvgLength>,
    vbox:                  Cell<RsvgViewBox>,
    atts:                  Cell<*mut RsvgPropertyBag>
}

impl NodeSvg {
    fn new () -> NodeSvg {
        NodeSvg {
            preserve_aspect_ratio: Cell::new (AspectRatio::default ()),
            x:                     Cell::new (RsvgLength::parse ("0", LengthDir::Horizontal).unwrap ()),
            y:                     Cell::new (RsvgLength::parse ("0", LengthDir::Vertical).unwrap ()),
            w:                     Cell::new (RsvgLength::parse ("100%", LengthDir::Horizontal).unwrap ()),
            h:                     Cell::new (RsvgLength::parse ("100%", LengthDir::Vertical).unwrap ()),
            vbox:                  Cell::new (RsvgViewBox::default ()),
            atts:                  Cell::new (ptr::null_mut ())
        }
    }
}

impl NodeTrait for NodeSvg {
    fn set_atts (&self, node: &RsvgNode, _: *const RsvgHandle, pbag: *const RsvgPropertyBag) -> NodeResult {
        self.preserve_aspect_ratio.set (property_bag::parse_or_default (pbag, "preserveAspectRatio")?);

        // x & y attributes have no effect on outermost svg
        // http://www.w3.org/TR/SVG/struct.html#SVGElement
        if node.get_parent ().is_some () {
            self.x.set (property_bag::length_or_default (pbag, "x", LengthDir::Horizontal)?);
            self.y.set (property_bag::length_or_default (pbag, "y", LengthDir::Vertical)?);
        }

        self.w.set (property_bag::length_or_value (pbag, "width", LengthDir::Horizontal, "100%")?);
        self.h.set (property_bag::length_or_value (pbag, "height", LengthDir::Vertical, "100%")?);

        self.vbox.set (property_bag::parse_or_default (pbag, "viewBox")?);

        // The "style" sub-element is not loaded yet here, so we need
        // to store other attributes to be applied later.
        self.atts.set (property_bag::dup (pbag));

        Ok (())
    }

    fn draw (&self, node: &RsvgNode, draw_ctx: *const RsvgDrawingCtx, dominate: i32) {
        let nx = self.x.get ().normalize (draw_ctx);
        let ny = self.y.get ().normalize (draw_ctx);
        let nw = self.w.get ().normalize (draw_ctx);
        let nh = self.h.get ().normalize (draw_ctx);

        drawing_ctx::state_reinherit_top (draw_ctx, node.get_state (), dominate);

        let state = drawing_ctx::get_current_state (draw_ctx);

        let affine_old = drawing_ctx::get_current_state_affine (draw_ctx);

        let vbox = self.vbox.get ();

        if vbox.active {
            // viewBox width==0 or height==0 disables rendering of the element
            // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
            if double_equals (vbox.rect.width, 0.0) || double_equals (vbox.rect.height, 0.0) {
                return;
            }

            let (x, y, w, h) = self.preserve_aspect_ratio.get ().compute (vbox.rect.width, vbox.rect.height,
                                                                          nx, ny, nw, nh);

            let affine = cairo::Matrix::new (w / vbox.rect.width,
                                             0.0,
                                             0.0,
                                             h / vbox.rect.height,
                                             x - vbox.rect.x * w / vbox.rect.width,
                                             y - vbox.rect.y * h / vbox.rect.height);

            drawing_ctx::set_current_state_affine (draw_ctx, cairo::Matrix::multiply (&affine, &affine_old));

            drawing_ctx::push_view_box (draw_ctx, vbox.rect.width, vbox.rect.height);
        } else {
            let mut affine = affine_old;
            affine.translate (nx, ny);

            drawing_ctx::set_current_state_affine (draw_ctx, affine);
            drawing_ctx::push_view_box (draw_ctx, nw, nh);
        }

        let affine_new = drawing_ctx::get_current_state_affine (draw_ctx);

        drawing_ctx::push_discrete_layer (draw_ctx);

        // Bounding box addition must be AFTER the discrete layer
        // push, which must be AFTER the transformation happens.

        if !drawing_ctx::state_is_overflow (state) && node.get_parent ().is_some () {
            drawing_ctx::set_current_state_affine (draw_ctx, affine_old);
            drawing_ctx::add_clipping_rect (draw_ctx, nx, ny, nw, nh);
            drawing_ctx::set_current_state_affine (draw_ctx, affine_new);
        }

        node.draw_children (draw_ctx, -1); // dominate==-1 so it won't reinherit or push a layer

        drawing_ctx::pop_discrete_layer (draw_ctx);
        drawing_ctx::pop_view_box (draw_ctx);
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

impl Drop for NodeSvg {
    fn drop (&mut self) {
        property_bag::free (self.atts.get ());
        self.atts.set (ptr::null_mut ());
    }
}

/***** C Prototypes *****/

#[no_mangle]
pub extern fn rsvg_node_group_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Group,
                    raw_parent,
                    Box::new (NodeGroup::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_defs_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Defs,
                    raw_parent,
                    Box::new (NodeDefs::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_switch_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Switch,
                    raw_parent,
                    Box::new (NodeSwitch::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_svg_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Svg,
                    raw_parent,
                    Box::new (NodeSvg::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_svg_get_size (raw_node: *const RsvgNode, out_width: *mut RsvgLength, out_height: *mut RsvgLength) {
    assert! (!raw_node.is_null ());
    let node: &RsvgNode = unsafe { & *raw_node };

    assert! (!out_width.is_null ());
    assert! (!out_height.is_null ());

    node.with_impl (|svg: &NodeSvg| {
        unsafe {
            *out_width  = svg.w.get ();
            *out_height = svg.h.get ();
        }
    });
}

#[no_mangle]
pub extern fn rsvg_node_svg_get_view_box (raw_node: *const RsvgNode) -> RsvgViewBox {
    assert! (!raw_node.is_null ());
    let node: &RsvgNode = unsafe { & *raw_node };

    let mut vbox = RsvgViewBox::default ();

    node.with_impl (|svg: &NodeSvg| {
        vbox = svg.vbox.get ();
    });

    vbox
}

extern "C" {
    fn rsvg_parse_style_attrs (handle: *const RsvgHandle,
                               node:   *const RsvgNode,
                               tag:    *const libc::c_char,
                               class:  *const libc::c_char,
                               id:     *const libc::c_char,
                               pbag:   *const RsvgPropertyBag);
}

#[no_mangle]
pub extern fn rsvg_node_svg_apply_atts (raw_node: *const RsvgNode, handle: *const RsvgHandle) {
    assert! (!raw_node.is_null ());
    let node: &RsvgNode = unsafe { & *raw_node };

    node.with_impl (|svg: &NodeSvg| {
        let pbag = svg.atts.get ();
        let class = property_bag::lookup (pbag, "class");
        let id = property_bag::lookup (pbag, "id");

        unsafe { rsvg_parse_style_attrs (handle,
                                         raw_node,
                                         str::to_glib_none ("svg").0,
                                         class.map_or (ptr::null (), |s| String::to_glib_none (&s).0),
                                         id.map_or (ptr::null (), |s| String::to_glib_none (&s).0),
                                         pbag); }
    });
}
<commit_msg>NodeSvg: Only access self.atts if it is non-null<commit_after>extern crate glib;
extern crate cairo;
extern crate libc;

use self::glib::translate::*;

use std::cell::Cell;
use std::ptr;

use aspect_ratio::*;
use drawing_ctx::RsvgDrawingCtx;
use drawing_ctx;
use handle::RsvgHandle;
use length::*;
use node::*;
use property_bag;
use property_bag::*;
use util::*;
use viewbox::*;

use self::cairo::MatrixTrait;

/***** NodeGroup *****/

struct NodeGroup ();

impl NodeGroup {
    fn new () -> NodeGroup {
        NodeGroup ()
    }
}

impl NodeTrait for NodeGroup {
    fn set_atts (&self, _: &RsvgNode, _: *const RsvgHandle, _: *const RsvgPropertyBag) -> NodeResult {
        Ok (())
    }

    fn draw (&self, node: &RsvgNode, draw_ctx: *const RsvgDrawingCtx, dominate: i32) {
        node.draw_children (draw_ctx, dominate);
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

/***** NodeDefs *****/

struct NodeDefs ();

impl NodeDefs {
    fn new () -> NodeDefs {
        NodeDefs ()
    }
}

impl NodeTrait for NodeDefs {
    fn set_atts (&self, _: &RsvgNode, _: *const RsvgHandle, _: *const RsvgPropertyBag) -> NodeResult {
        Ok (())
    }

    fn draw (&self, _: &RsvgNode, _: *const RsvgDrawingCtx, _: i32) {
        // nothing
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

/***** NodeSwitch *****/

struct NodeSwitch ();

impl NodeSwitch {
    fn new () -> NodeSwitch {
        NodeSwitch ()
    }
}

impl NodeTrait for NodeSwitch {
    fn set_atts (&self, _: &RsvgNode, _: *const RsvgHandle, _: *const RsvgPropertyBag) -> NodeResult {
        Ok (())
    }

    fn draw (&self, node: &RsvgNode, draw_ctx: *const RsvgDrawingCtx, dominate: i32) {
        drawing_ctx::state_reinherit_top (draw_ctx, node.get_state (), dominate);

        drawing_ctx::push_discrete_layer (draw_ctx);

        for child in &*node.children.borrow () {
            if drawing_ctx::state_get_cond_true (child.get_state ()) {
                let boxed_child = box_node (child.clone ());

                drawing_ctx::draw_node_from_stack (draw_ctx, boxed_child, 0);

                rsvg_node_unref (boxed_child);

                break;
            }
        }

        drawing_ctx::pop_discrete_layer (draw_ctx);
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

/***** NodeSvg *****/

struct NodeSvg {
    preserve_aspect_ratio: Cell<AspectRatio>,
    x:                     Cell<RsvgLength>,
    y:                     Cell<RsvgLength>,
    w:                     Cell<RsvgLength>,
    h:                     Cell<RsvgLength>,
    vbox:                  Cell<RsvgViewBox>,
    atts:                  Cell<*mut RsvgPropertyBag>
}

impl NodeSvg {
    fn new () -> NodeSvg {
        NodeSvg {
            preserve_aspect_ratio: Cell::new (AspectRatio::default ()),
            x:                     Cell::new (RsvgLength::parse ("0", LengthDir::Horizontal).unwrap ()),
            y:                     Cell::new (RsvgLength::parse ("0", LengthDir::Vertical).unwrap ()),
            w:                     Cell::new (RsvgLength::parse ("100%", LengthDir::Horizontal).unwrap ()),
            h:                     Cell::new (RsvgLength::parse ("100%", LengthDir::Vertical).unwrap ()),
            vbox:                  Cell::new (RsvgViewBox::default ()),
            atts:                  Cell::new (ptr::null_mut ())
        }
    }
}

impl NodeTrait for NodeSvg {
    fn set_atts (&self, node: &RsvgNode, _: *const RsvgHandle, pbag: *const RsvgPropertyBag) -> NodeResult {
        self.preserve_aspect_ratio.set (property_bag::parse_or_default (pbag, "preserveAspectRatio")?);

        // x & y attributes have no effect on outermost svg
        // http://www.w3.org/TR/SVG/struct.html#SVGElement
        if node.get_parent ().is_some () {
            self.x.set (property_bag::length_or_default (pbag, "x", LengthDir::Horizontal)?);
            self.y.set (property_bag::length_or_default (pbag, "y", LengthDir::Vertical)?);
        }

        self.w.set (property_bag::length_or_value (pbag, "width", LengthDir::Horizontal, "100%")?);
        self.h.set (property_bag::length_or_value (pbag, "height", LengthDir::Vertical, "100%")?);

        self.vbox.set (property_bag::parse_or_default (pbag, "viewBox")?);

        // The "style" sub-element is not loaded yet here, so we need
        // to store other attributes to be applied later.
        self.atts.set (property_bag::dup (pbag));

        Ok (())
    }

    fn draw (&self, node: &RsvgNode, draw_ctx: *const RsvgDrawingCtx, dominate: i32) {
        let nx = self.x.get ().normalize (draw_ctx);
        let ny = self.y.get ().normalize (draw_ctx);
        let nw = self.w.get ().normalize (draw_ctx);
        let nh = self.h.get ().normalize (draw_ctx);

        drawing_ctx::state_reinherit_top (draw_ctx, node.get_state (), dominate);

        let state = drawing_ctx::get_current_state (draw_ctx);

        let affine_old = drawing_ctx::get_current_state_affine (draw_ctx);

        let vbox = self.vbox.get ();

        if vbox.active {
            // viewBox width==0 or height==0 disables rendering of the element
            // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
            if double_equals (vbox.rect.width, 0.0) || double_equals (vbox.rect.height, 0.0) {
                return;
            }

            let (x, y, w, h) = self.preserve_aspect_ratio.get ().compute (vbox.rect.width, vbox.rect.height,
                                                                          nx, ny, nw, nh);

            let affine = cairo::Matrix::new (w / vbox.rect.width,
                                             0.0,
                                             0.0,
                                             h / vbox.rect.height,
                                             x - vbox.rect.x * w / vbox.rect.width,
                                             y - vbox.rect.y * h / vbox.rect.height);

            drawing_ctx::set_current_state_affine (draw_ctx, cairo::Matrix::multiply (&affine, &affine_old));

            drawing_ctx::push_view_box (draw_ctx, vbox.rect.width, vbox.rect.height);
        } else {
            let mut affine = affine_old;
            affine.translate (nx, ny);

            drawing_ctx::set_current_state_affine (draw_ctx, affine);
            drawing_ctx::push_view_box (draw_ctx, nw, nh);
        }

        let affine_new = drawing_ctx::get_current_state_affine (draw_ctx);

        drawing_ctx::push_discrete_layer (draw_ctx);

        // Bounding box addition must be AFTER the discrete layer
        // push, which must be AFTER the transformation happens.

        if !drawing_ctx::state_is_overflow (state) && node.get_parent ().is_some () {
            drawing_ctx::set_current_state_affine (draw_ctx, affine_old);
            drawing_ctx::add_clipping_rect (draw_ctx, nx, ny, nw, nh);
            drawing_ctx::set_current_state_affine (draw_ctx, affine_new);
        }

        node.draw_children (draw_ctx, -1); // dominate==-1 so it won't reinherit or push a layer

        drawing_ctx::pop_discrete_layer (draw_ctx);
        drawing_ctx::pop_view_box (draw_ctx);
    }

    fn get_c_impl (&self) -> *const RsvgCNodeImpl {
        unreachable! ();
    }
}

impl Drop for NodeSvg {
    fn drop (&mut self) {
        let pbag = self.atts.get ();

        if !pbag.is_null () {
            property_bag::free (pbag);
        }
    }
}

/***** C Prototypes *****/

#[no_mangle]
pub extern fn rsvg_node_group_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Group,
                    raw_parent,
                    Box::new (NodeGroup::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_defs_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Defs,
                    raw_parent,
                    Box::new (NodeDefs::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_switch_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Switch,
                    raw_parent,
                    Box::new (NodeSwitch::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_svg_new (_: *const libc::c_char, raw_parent: *const RsvgNode) -> *const RsvgNode {
    boxed_node_new (NodeType::Svg,
                    raw_parent,
                    Box::new (NodeSvg::new ()))
}

#[no_mangle]
pub extern fn rsvg_node_svg_get_size (raw_node: *const RsvgNode, out_width: *mut RsvgLength, out_height: *mut RsvgLength) {
    assert! (!raw_node.is_null ());
    let node: &RsvgNode = unsafe { & *raw_node };

    assert! (!out_width.is_null ());
    assert! (!out_height.is_null ());

    node.with_impl (|svg: &NodeSvg| {
        unsafe {
            *out_width  = svg.w.get ();
            *out_height = svg.h.get ();
        }
    });
}

#[no_mangle]
pub extern fn rsvg_node_svg_get_view_box (raw_node: *const RsvgNode) -> RsvgViewBox {
    assert! (!raw_node.is_null ());
    let node: &RsvgNode = unsafe { & *raw_node };

    let mut vbox = RsvgViewBox::default ();

    node.with_impl (|svg: &NodeSvg| {
        vbox = svg.vbox.get ();
    });

    vbox
}

extern "C" {
    fn rsvg_parse_style_attrs (handle: *const RsvgHandle,
                               node:   *const RsvgNode,
                               tag:    *const libc::c_char,
                               class:  *const libc::c_char,
                               id:     *const libc::c_char,
                               pbag:   *const RsvgPropertyBag);
}

#[no_mangle]
pub extern fn rsvg_node_svg_apply_atts (raw_node: *const RsvgNode, handle: *const RsvgHandle) {
    assert! (!raw_node.is_null ());
    let node: &RsvgNode = unsafe { & *raw_node };

    node.with_impl (|svg: &NodeSvg| {
        let pbag = svg.atts.get ();

        if !pbag.is_null () {
            let class = property_bag::lookup (pbag, "class");
            let id = property_bag::lookup (pbag, "id");

            unsafe { rsvg_parse_style_attrs (handle,
                                             raw_node,
                                             str::to_glib_none ("svg").0,
                                             class.map_or (ptr::null (), |s| String::to_glib_none (&s).0),
                                             id.map_or (ptr::null (), |s| String::to_glib_none (&s).0),
                                             pbag); }
        }
    });
}
<|endoftext|>