Blame servo/components/style_derive/cg.rs

Packit f0b94e
/* This Source Code Form is subject to the terms of the Mozilla Public
Packit f0b94e
 * License, v. 2.0. If a copy of the MPL was not distributed with this
Packit f0b94e
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Packit f0b94e
Packit f0b94e
use darling::{FromDeriveInput, FromField, FromVariant};
Packit f0b94e
use quote::{ToTokens, Tokens};
Packit f0b94e
use std::collections::HashSet;
Packit f0b94e
use syn::{self, AngleBracketedGenericArguments, Binding, DeriveInput, Field};
Packit f0b94e
use syn::{GenericArgument, GenericParam, Ident, ImplGenerics, Path};
Packit f0b94e
use syn::{PathArguments, PathSegment, QSelf, Type, TypeArray, TypeGenerics};
Packit f0b94e
use syn::{TypeParam, TypeParen, TypePath, TypeSlice, TypeTuple};
Packit f0b94e
use syn::{Variant, WherePredicate};
Packit f0b94e
use syn::visit::{self, Visit};
Packit f0b94e
use synstructure::{self, BindingInfo, BindStyle, VariantAst, VariantInfo};
Packit f0b94e
Packit f0b94e
pub struct WhereClause<'input, 'path> {
Packit f0b94e
    pub inner: Option<syn::WhereClause>,
Packit f0b94e
    pub params: Vec<&'input TypeParam>,
Packit f0b94e
    trait_path: &'path Path,
Packit f0b94e
    trait_output: Option<Ident>,
Packit f0b94e
    bounded_types: HashSet<Type>,
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
impl<'input, 'path> ToTokens for WhereClause<'input, 'path> {
Packit f0b94e
    fn to_tokens(&self, tokens: &mut Tokens) {
Packit f0b94e
        self.inner.to_tokens(tokens);
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
impl<'input, 'path> WhereClause<'input, 'path> {
Packit f0b94e
    pub fn add_trait_bound(&mut self, ty: &Type) {
Packit f0b94e
        let trait_path = self.trait_path;
Packit f0b94e
        let mut found = self.trait_output.map(|_| HashSet::new());
Packit f0b94e
        if self.bounded_types.contains(&ty) {
Packit f0b94e
            return;
Packit f0b94e
        }
Packit f0b94e
        if !is_parameterized(&ty, &self.params, found.as_mut()) {
Packit f0b94e
            return;
Packit f0b94e
        }
Packit f0b94e
        self.bounded_types.insert(ty.clone());
Packit f0b94e
Packit f0b94e
        let output = if let Some(output) = self.trait_output {
Packit f0b94e
            output
Packit f0b94e
        } else {
Packit f0b94e
            add_predicate(&mut self.inner, where_predicate(ty.clone(), trait_path, None));
Packit f0b94e
            return;
Packit f0b94e
        };
Packit f0b94e
Packit f0b94e
        if let Type::Path(syn::TypePath { ref path, .. }) = *ty {
Packit f0b94e
            if path_to_ident(path).is_some() {
Packit f0b94e
                add_predicate(&mut self.inner, where_predicate(ty.clone(), trait_path, None));
Packit f0b94e
                return;
Packit f0b94e
            }
Packit f0b94e
        }
Packit f0b94e
Packit f0b94e
        let output_type = map_type_params(ty, &self.params, &mut |ident| {
Packit f0b94e
            parse_quote!(<#ident as ::#trait_path>::#output)
Packit f0b94e
        });
Packit f0b94e
Packit f0b94e
        let pred = where_predicate(
Packit f0b94e
            ty.clone(),
Packit f0b94e
            trait_path,
Packit f0b94e
            Some((output, output_type)),
Packit f0b94e
        );
Packit f0b94e
Packit f0b94e
        add_predicate(&mut self.inner, pred);
Packit f0b94e
Packit f0b94e
        if let Some(found) = found {
Packit f0b94e
            for ident in found {
Packit f0b94e
                let ty = Type::Path(syn::TypePath { qself: None, path: ident.into() });
Packit f0b94e
                if !self.bounded_types.contains(&ty) {
Packit f0b94e
                    self.bounded_types.insert(ty.clone());
Packit f0b94e
                    add_predicate(
Packit f0b94e
                        &mut self.inner,
Packit f0b94e
                        where_predicate(ty, trait_path, None),
Packit f0b94e
                    );
Packit f0b94e
                };
Packit f0b94e
            }
Packit f0b94e
        }
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn add_predicate(
Packit f0b94e
    where_clause: &mut Option<syn::WhereClause>,
Packit f0b94e
    pred: WherePredicate,
Packit f0b94e
) {
Packit f0b94e
    where_clause.get_or_insert(parse_quote!(where)).predicates.push(pred);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn fmap_match<F>(
Packit f0b94e
    input: &DeriveInput,
Packit f0b94e
    bind_style: BindStyle,
Packit f0b94e
    mut f: F,
Packit f0b94e
) -> Tokens
Packit f0b94e
where
Packit f0b94e
    F: FnMut(BindingInfo) -> Tokens,
Packit f0b94e
{
Packit f0b94e
    let mut s = synstructure::Structure::new(input);
Packit f0b94e
    s.variants_mut().iter_mut().for_each(|v| { v.bind_with(|_| bind_style); });
Packit f0b94e
    s.each_variant(|variant| {
Packit f0b94e
        let (mapped, mapped_fields) = value(variant, "mapped");
Packit f0b94e
        let fields_pairs = variant.bindings().into_iter().zip(mapped_fields);
Packit f0b94e
        let mut computations = quote!();
Packit f0b94e
        computations.append_all(fields_pairs.map(|(field, mapped_field)| {
Packit f0b94e
            let expr = f(field.clone());
Packit f0b94e
            quote! { let #mapped_field = #expr; }
Packit f0b94e
        }));
Packit f0b94e
        computations.append_all(mapped);
Packit f0b94e
        Some(computations)
Packit f0b94e
    })
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn fmap_trait_output(
Packit f0b94e
    input: &DeriveInput,
Packit f0b94e
    trait_path: &Path,
Packit f0b94e
    trait_output: Ident,
Packit f0b94e
) -> Path {
Packit f0b94e
    let segment = PathSegment {
Packit f0b94e
        ident: input.ident.clone(),
Packit f0b94e
        arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
Packit f0b94e
            args: input.generics.params.iter().map(|arg| {
Packit f0b94e
                match arg {
Packit f0b94e
                    &GenericParam::Lifetime(ref data) => GenericArgument::Lifetime(data.lifetime.clone()),
Packit f0b94e
                    &GenericParam::Type(ref data) => {
Packit f0b94e
                        let ident = data.ident;
Packit f0b94e
                        GenericArgument::Type(
Packit f0b94e
                            parse_quote!(<#ident as ::#trait_path>::#trait_output),
Packit f0b94e
                        )
Packit f0b94e
                    },
Packit f0b94e
                    ref arg => panic!("arguments {:?} cannot be mapped yet", arg)
Packit f0b94e
                }
Packit f0b94e
            }).collect(),
Packit f0b94e
            colon2_token: Default::default(),
Packit f0b94e
            gt_token: Default::default(),
Packit f0b94e
            lt_token: Default::default(),
Packit f0b94e
Packit f0b94e
        })
Packit f0b94e
    };
Packit f0b94e
    segment.into()
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn is_parameterized(
Packit f0b94e
    ty: &Type,
Packit f0b94e
    params: &[&TypeParam],
Packit f0b94e
    found: Option<&mut HashSet<Ident>>,
Packit f0b94e
) -> bool {
Packit f0b94e
    struct IsParameterized<'a, 'b> {
Packit f0b94e
        params: &'a [&'a TypeParam],
Packit f0b94e
        has_free: bool,
Packit f0b94e
        found: Option<&'b mut HashSet<Ident>>,
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    impl<'a, 'b, 'ast> Visit<'ast> for IsParameterized<'a, 'b> {
Packit f0b94e
        fn visit_path(&mut self, path: &'ast Path) {
Packit f0b94e
            if let Some(ident) = path_to_ident(path) {
Packit f0b94e
                if self.params.iter().any(|param| param.ident == ident) {
Packit f0b94e
                    self.has_free = true;
Packit f0b94e
                    if let Some(ref mut found) = self.found {
Packit f0b94e
                        found.insert(ident.clone());
Packit f0b94e
                    }
Packit f0b94e
                }
Packit f0b94e
            }
Packit f0b94e
            visit::visit_path(self, path);
Packit f0b94e
        }
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    let mut visitor = IsParameterized { params, has_free: false, found };
Packit f0b94e
    visitor.visit_type(ty);
Packit f0b94e
    visitor.has_free
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn map_type_params<F>(ty: &Type, params: &[&TypeParam], f: &mut F) -> Type
Packit f0b94e
where
Packit f0b94e
    F: FnMut(&Ident) -> Type,
Packit f0b94e
{
Packit f0b94e
    match *ty {
Packit f0b94e
        Type::Slice(ref inner) => {
Packit f0b94e
            Type::from(TypeSlice { elem: Box::new(map_type_params(&inner.elem, params, f)), ..inner.clone() })
Packit f0b94e
        },
Packit f0b94e
        Type::Array(ref inner) => { //ref ty, ref expr) => {
Packit f0b94e
            Type::from(TypeArray { elem: Box::new(map_type_params(&inner.elem, params, f)), ..inner.clone() })
Packit f0b94e
        },
Packit f0b94e
        ref ty @ Type::Never(_) => ty.clone(),
Packit f0b94e
        Type::Tuple(ref inner) => {
Packit f0b94e
            Type::from(
Packit f0b94e
                TypeTuple {
Packit f0b94e
                    elems: inner.elems.iter().map(|ty| map_type_params(&ty, params, f)).collect(),
Packit f0b94e
                    ..inner.clone()
Packit f0b94e
                }
Packit f0b94e
            )
Packit f0b94e
        },
Packit f0b94e
        Type::Path(TypePath { qself: None, ref path }) => {
Packit f0b94e
            if let Some(ident) = path_to_ident(path) {
Packit f0b94e
                if params.iter().any(|param| param.ident == ident) {
Packit f0b94e
                    return f(ident);
Packit f0b94e
                }
Packit f0b94e
            }
Packit f0b94e
            Type::from(TypePath { qself: None, path: map_type_params_in_path(path, params, f) })
Packit f0b94e
        }
Packit f0b94e
        Type::Path(TypePath { ref qself, ref path }) => {
Packit f0b94e
            Type::from(TypePath {
Packit f0b94e
                qself: qself.as_ref().map(|qself| {
Packit f0b94e
                    QSelf {
Packit f0b94e
                        ty: Box::new(map_type_params(&qself.ty, params, f)),
Packit f0b94e
                        position: qself.position,
Packit f0b94e
                        ..qself.clone()
Packit f0b94e
                    }
Packit f0b94e
                }),
Packit f0b94e
                path: map_type_params_in_path(path, params, f),
Packit f0b94e
            })
Packit f0b94e
        },
Packit f0b94e
        Type::Paren(ref inner) => {
Packit f0b94e
            Type::from(TypeParen { elem: Box::new(map_type_params(&inner.elem, params, f)), ..inner.clone() })
Packit f0b94e
        },
Packit f0b94e
        ref ty => panic!("type {:?} cannot be mapped yet", ty),
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
fn map_type_params_in_path<F>(path: &Path, params: &[&TypeParam], f: &mut F) -> Path
Packit f0b94e
where
Packit f0b94e
    F: FnMut(&Ident) -> Type,
Packit f0b94e
{
Packit f0b94e
    Path {
Packit f0b94e
        leading_colon: path.leading_colon,
Packit f0b94e
        segments: path.segments.iter().map(|segment| {
Packit f0b94e
            PathSegment {
Packit f0b94e
                ident: segment.ident.clone(),
Packit f0b94e
                arguments: match segment.arguments {
Packit f0b94e
                    PathArguments::AngleBracketed(ref data) => {
Packit f0b94e
                        PathArguments::AngleBracketed(AngleBracketedGenericArguments {
Packit f0b94e
                            args: data.args.iter().map(|arg| {
Packit f0b94e
                                match arg {
Packit f0b94e
                                    ty @ &GenericArgument::Lifetime(_) => ty.clone(),
Packit f0b94e
                                    &GenericArgument::Type(ref data) => {
Packit f0b94e
                                        GenericArgument::Type(map_type_params(data, params, f))
Packit f0b94e
                                    },
Packit f0b94e
                                    &GenericArgument::Binding(ref data) => GenericArgument::Binding(Binding {
Packit f0b94e
                                        ty: map_type_params(&data.ty, params, f),
Packit f0b94e
                                        ..data.clone()
Packit f0b94e
                                    }),
Packit f0b94e
                                    ref arg => panic!("arguments {:?} cannot be mapped yet", arg)
Packit f0b94e
                                }
Packit f0b94e
                            }).collect(),
Packit f0b94e
                            ..data.clone()
Packit f0b94e
                        })
Packit f0b94e
                    },
Packit f0b94e
                    ref arg @ PathArguments::None => arg.clone(),
Packit f0b94e
                    ref parameters => {
Packit f0b94e
                        panic!("parameters {:?} cannot be mapped yet", parameters)
Packit f0b94e
                    }
Packit f0b94e
                },
Packit f0b94e
            }
Packit f0b94e
        }).collect(),
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
fn path_to_ident(path: &Path) -> Option<&Ident> {
Packit f0b94e
    match *path {
Packit f0b94e
        Path { leading_colon: None, ref segments } if segments.len() == 1 => {
Packit f0b94e
            if segments[0].arguments.is_empty() {
Packit f0b94e
                Some(&segments[0].ident)
Packit f0b94e
            } else {
Packit f0b94e
                None
Packit f0b94e
            }
Packit f0b94e
        },
Packit f0b94e
        _ => None,
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn parse_field_attrs(field: &Field) -> A
Packit f0b94e
where
Packit f0b94e
    A: FromField,
Packit f0b94e
{
Packit f0b94e
    match A::from_field(field) {
Packit f0b94e
        Ok(attrs) => attrs,
Packit f0b94e
        Err(e) => panic!("failed to parse field attributes: {}", e),
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn parse_input_attrs(input: &DeriveInput) -> A
Packit f0b94e
where
Packit f0b94e
    A: FromDeriveInput,
Packit f0b94e
{
Packit f0b94e
    match A::from_derive_input(input) {
Packit f0b94e
        Ok(attrs) => attrs,
Packit f0b94e
        Err(e) => panic!("failed to parse input attributes: {}", e),
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn parse_variant_attrs(variant: &VariantAst) -> A
Packit f0b94e
where
Packit f0b94e
    A: FromVariant,
Packit f0b94e
{
Packit f0b94e
    let v = Variant {
Packit f0b94e
        ident: *variant.ident,
Packit f0b94e
        attrs: variant.attrs.to_vec(),
Packit f0b94e
        fields: variant.fields.clone(),
Packit f0b94e
        discriminant: variant.discriminant.clone(),
Packit f0b94e
    };
Packit f0b94e
    match A::from_variant(&v) {
Packit f0b94e
        Ok(attrs) => attrs,
Packit f0b94e
        Err(e) => panic!("failed to parse variant attributes: {}", e),
Packit f0b94e
    }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
Packit f0b94e
pub fn ref_pattern<'a>(
Packit f0b94e
    variant: &'a VariantInfo,
Packit f0b94e
    prefix: &str,
Packit f0b94e
) -> (Tokens, Vec<BindingInfo<'a>>) {
Packit f0b94e
    let mut v = variant.clone();
Packit f0b94e
    v.bind_with(|_| BindStyle::Ref);
Packit f0b94e
    v.bindings_mut().iter_mut().for_each(|b| { b.binding = Ident::from(format!("{}_{}", b.binding, prefix)) });
Packit f0b94e
    (v.pat(), v.bindings().iter().cloned().collect())
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn trait_parts<'input, 'path>(
Packit f0b94e
    input: &'input DeriveInput,
Packit f0b94e
    trait_path: &'path Path,
Packit f0b94e
) -> (ImplGenerics<'input>, TypeGenerics<'input>, WhereClause<'input, 'path>) {
Packit f0b94e
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
Packit f0b94e
    let where_clause = WhereClause {
Packit f0b94e
        inner: where_clause.cloned(),
Packit f0b94e
        params: input.generics.type_params().into_iter().collect::<Vec<&TypeParam>>(),
Packit f0b94e
        trait_path,
Packit f0b94e
        trait_output: None,
Packit f0b94e
        bounded_types: HashSet::new()
Packit f0b94e
    };
Packit f0b94e
    (impl_generics, ty_generics, where_clause)
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
fn trait_ref(path: &Path, output: Option<(Ident, Type)>) -> Path {
Packit f0b94e
    let segments = path.segments.iter().collect::<Vec<&PathSegment>>();
Packit f0b94e
    let (name, parent) = segments.split_last().unwrap();
Packit f0b94e
Packit f0b94e
    let last_segment: PathSegment = if let Some((param, ty)) = output {
Packit f0b94e
        parse_quote!(#name<#param = #ty>)
Packit f0b94e
    } else {
Packit f0b94e
        parse_quote!(#name)
Packit f0b94e
    };
Packit f0b94e
    parse_quote!(::#(#parent::)*#last_segment)
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn value<'a>(
Packit f0b94e
    variant: &'a VariantInfo,
Packit f0b94e
    prefix: &str,
Packit f0b94e
) -> (Tokens, Vec<BindingInfo<'a>>) {
Packit f0b94e
    let mut v = variant.clone();
Packit f0b94e
    v.bindings_mut().iter_mut().for_each(|b| { b.binding = Ident::from(format!("{}_{}", b.binding, prefix)) });
Packit f0b94e
    v.bind_with(|_| BindStyle::Move);
Packit f0b94e
    (v.pat(), v.bindings().iter().cloned().collect())
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
pub fn where_predicate(
Packit f0b94e
    bounded_ty: Type,
Packit f0b94e
    trait_path: &Path,
Packit f0b94e
    trait_output: Option<(Ident, Type)>,
Packit f0b94e
) -> WherePredicate {
Packit f0b94e
    let trait_ref = trait_ref(trait_path, trait_output);
Packit f0b94e
    parse_quote!(#bounded_ty: #trait_ref)
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
/// Transforms "FooBar" to "foo-bar".
Packit f0b94e
///
Packit f0b94e
/// If the first Camel segment is "Moz", "Webkit", or "Servo", the result string
Packit f0b94e
/// is prepended with "-".
Packit f0b94e
pub fn to_css_identifier(mut camel_case: &str) -> String {
Packit f0b94e
    camel_case = camel_case.trim_right_matches('_');
Packit f0b94e
    let mut first = true;
Packit f0b94e
    let mut result = String::with_capacity(camel_case.len());
Packit f0b94e
    while let Some(segment) = split_camel_segment(&mut camel_case) {
Packit f0b94e
        if first {
Packit f0b94e
            match segment {
Packit f0b94e
                "Moz" | "Webkit" | "Servo" => first = false,
Packit f0b94e
                _ => {},
Packit f0b94e
            }
Packit f0b94e
        }
Packit f0b94e
        if !first {
Packit f0b94e
            result.push_str("-");
Packit f0b94e
        }
Packit f0b94e
        first = false;
Packit f0b94e
        result.push_str(&segment.to_lowercase());
Packit f0b94e
    }
Packit f0b94e
    result
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
Packit f0b94e
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
Packit f0b94e
    let index = match camel_case.chars().next() {
Packit f0b94e
        None => return None,
Packit f0b94e
        Some(ch) => ch.len_utf8(),
Packit f0b94e
    };
Packit f0b94e
    let end_position = camel_case[index..]
Packit f0b94e
        .find(char::is_uppercase)
Packit f0b94e
        .map_or(camel_case.len(), |pos| index + pos);
Packit f0b94e
    let result = &camel_case[..end_position];
Packit f0b94e
    *camel_case = &camel_case[end_position..];
Packit f0b94e
    Some(result)
Packit f0b94e
}