1use 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 if method.sig.decl.output != ReturnType::Default {
41 Err(err_msg)?
42 }
43
44 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 fluent_method.attrs.retain(|a| a.path != parse_quote!{ doc });
86 fluent_method.attrs.push(parse_quote! { #[doc = #doc] });
87
88 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
124fn 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}