fluent_impl/
method.rs

1/*
2    This file is a part of term-string.
3
4    Copyright (C) 2018 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
5    https://github.com/rust-alt/term-string
6
7    This Source Code Form is subject to the terms of the Mozilla Public
8    License, v. 2.0. If a copy of the MPL was not distributed with this
9    file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
10*/
11
12use proc_macro2::Span;
13use syn::{
14    punctuated::Punctuated, token::Comma, Attribute, Expr, FnArg, GenericParam, Generics, Ident, ImplItemMethod, Pat,
15    ReturnType, Type, Visibility,
16};
17
18use config::{self, MacroConfig, MethodConfig};
19use type_utils as t;
20
21pub(crate) fn get_method_config(attrs: &[Attribute]) -> Result<MethodConfig, String> {
22    let mut method_config = MethodConfig::default();
23
24    for opts in attrs.iter().filter(|a| a.path == parse_quote! { fluent_impl_opts }) {
25        let attr_info = config::parse_config_from_attr(&opts)?;
26        method_config = config::get_method_config(attr_info, Some(method_config))?;
27    }
28
29    Ok(method_config)
30}
31
32pub(crate) fn try_fluentable(
33    method: &ImplItemMethod,
34    macro_config: &MacroConfig,
35    method_config: &MethodConfig,
36) -> Result<(), String> {
37    let err_msg = "fluent_impl only applies to `&mut self` methods and no return value";
38
39    // Check if method returns anything
40    if method.sig.decl.output != ReturnType::Default {
41        Err(err_msg)?
42    }
43
44    // Check if first arg is `&mut self`
45    if let Some(first_arg) = method.sig.decl.inputs.first() {
46        match first_arg.into_value() {
47            FnArg::SelfRef(arg) => {
48                if arg.mutability.is_none() {
49                    Err(err_msg)?
50                }
51            },
52            _ => Err(err_msg)?,
53        }
54    } else {
55        Err(err_msg)?
56    }
57
58    match method.vis {
59        Visibility::Public(_) => (),
60        _ => if !macro_config.non_public && !method_config.non_public {
61            Err("generating a chaining method from this non-public method was not enabled")?;
62        },
63    }
64
65    if method_config.skip {
66        Err("skip opt enabled")?;
67    }
68
69    Ok(())
70}
71
72pub(crate) fn fluent_from_fluentable(
73    method: ImplItemMethod,
74    macro_config: &MacroConfig,
75    ty: &Type,
76) -> Result<ImplItemMethod, String> {
77    let mut fluent_method = method;
78    let b_ident = fluent_method.sig.ident.clone();
79    let doc = fluent_doc(&fluent_method, macro_config)?;
80    let doc = doc.replace("%f%", &fluent_method.sig.ident.to_string());
81    let doc = doc.replace("%t%", &t::bare_ty_str(ty)?);
82
83    fluent_method.sig.ident = Ident::new(&fluent_ident(&fluent_method, macro_config)?, Span::call_site());
84    // Remove original doc and add ours
85    fluent_method.attrs.retain(|a| a.path != parse_quote!{ doc });
86    fluent_method.attrs.push(parse_quote! { #[doc = #doc] });
87
88    // Always Some
89    match fluent_method.sig.decl.inputs.iter_mut().nth(0) {
90        Some(first_arg) => *first_arg = parse_quote! { mut self },
91        None => unreachable!(),
92    };
93
94    fluent_method.sig.decl.output = parse_quote! { -> Self };
95    simplify_fn_args(&mut fluent_method.sig.decl.inputs);
96    let call_args = get_call_args(&fluent_method.sig.decl.inputs);
97    let generic_params = get_generic_params(&fluent_method.sig.decl.generics);
98    fluent_method.block = parse_quote! { { self.#b_ident::<#generic_params>(#call_args); self } };
99
100    Ok(fluent_method)
101}
102
103fn get_generic_params(generics: &Generics) -> Punctuated<Ident, Comma> {
104    let mut ret = Punctuated::new();
105    for param in &generics.params {
106        match param {
107            GenericParam::Lifetime(l) => {
108                ret.push_value(l.lifetime.ident.clone());
109                ret.push_punct(Token!(,)(Span::call_site()));
110            },
111            GenericParam::Const(p) => {
112                ret.push_value(p.ident.clone());
113                ret.push_punct(Token!(,)(Span::call_site()));
114            },
115            GenericParam::Type(t) => {
116                ret.push_value(t.ident.clone());
117                ret.push_punct(Token!(,)(Span::call_site()));
118            },
119        }
120    }
121    ret
122}
123
124// Replace non-ident arg params with idents.
125// Check tests/run-pass/pattern_args.rs where without this
126// we will get errors.
127fn simplify_fn_args(inputs: &mut Punctuated<FnArg, Comma>) {
128    for param in inputs.iter_mut().enumerate() {
129        if let (idx, FnArg::Captured(cap)) = param {
130            match cap.pat {
131                Pat::Ident(_) => (),
132                _ => {
133                    let ident = Ident::new(&format!("arg{}", idx), Span::call_site());
134                    cap.pat = parse_quote! { #ident };
135                },
136            }
137        }
138    }
139}
140
141fn get_call_args(inputs: &Punctuated<FnArg, Comma>) -> Punctuated<Expr, Comma> {
142    let mut ret = Punctuated::new();
143    for param in inputs {
144        match param {
145            FnArg::SelfRef(_) | FnArg::SelfValue(_) => (),
146            FnArg::Captured(cap) => {
147                let pat = &cap.pat;
148                let expr: Expr = parse_quote! { #pat };
149                ret.push_value(expr);
150                ret.push_punct(Token!(,)(Span::call_site()));
151            },
152            FnArg::Inferred(pat) => {
153                let expr: Expr = parse_quote! { #pat };
154                ret.push_value(expr);
155                ret.push_punct(Token!(,)(Span::call_site()));
156            },
157            FnArg::Ignored(ty) => {
158                let expr: Expr = parse_quote! { #ty };
159                ret.push_value(expr);
160                ret.push_punct(Token!(,)(Span::call_site()));
161            },
162        }
163    }
164    ret
165}
166
167fn fluent_doc(method: &ImplItemMethod, macro_config: &MacroConfig) -> Result<String, String> {
168    let method_config = get_method_config(&method.attrs)?;
169    let mut doc = if let Some(doc) = method_config.doc {
170        doc
171    } else {
172        macro_config.doc.clone()
173    };
174    doc += "\n\n [`%f%`]: %t%::%f%";
175    doc += "\n [`%f%()`]: %t%::%f%";
176    Ok(doc)
177}
178
179fn fluent_ident(method: &ImplItemMethod, macro_config: &MacroConfig) -> Result<String, String> {
180    let method_config = get_method_config(&method.attrs)?;
181
182    if let Some(name) = method_config.name {
183        return Ok(name);
184    }
185
186    let b_ident = if let Some(rename) = method_config.rename {
187        rename
188    } else {
189        method.sig.ident.to_string()
190    };
191
192    let prefix = if let Some(prefix) = method_config.prefix {
193        prefix
194    } else {
195        macro_config.prefix.clone()
196    };
197
198    let ident_str = prefix + &b_ident;
199    Ok(ident_str)
200}