libgir/codegen/
function.rs

1use std::{
2    fmt,
3    io::{Result, Write},
4    result::Result as StdResult,
5};
6
7use log::warn;
8
9use super::{
10    function_body_chunk,
11    general::{
12        allow_deprecated, cfg_condition, cfg_deprecated, doc_alias, doc_hidden,
13        not_version_condition, version_condition,
14    },
15    parameter::ToParameter,
16    return_value::{out_parameter_types, out_parameters_as_return, ToReturnValue},
17    special_functions,
18};
19use crate::{
20    analysis::{self, bounds::Bounds, try_from_glib::TryFromGlib},
21    chunk::{ffi_function_todo, Chunk},
22    env::Env,
23    library::{self, TypeId},
24    nameutil::use_glib_type,
25    version::Version,
26    writer::{primitives::tabs, ToCode},
27};
28
29// We follow the rules of the `return_self_not_must_use` clippy lint:
30//
31// If `Self` is returned (so `-> Self`) in a method (whatever the form of the
32// `self`), then the `#[must_use]` attribute must be added.
33pub fn get_must_use_if_needed(
34    parent_type_id: Option<TypeId>,
35    analysis: &analysis::functions::Info,
36    comment_prefix: &str,
37) -> Option<String> {
38    // If there is no parent, it means it's not a (trait) method so we're not
39    // interested.
40    if let Some(parent_type_id) = parent_type_id {
41        // Check it's a trait declaration or a method declaration (outside of a trait
42        // implementation).
43        if analysis.kind == library::FunctionKind::Method {
44            // We now get the list of the returned types.
45            let outs = out_parameter_types(analysis);
46            // If there is only one type returned, we check if it's the same type as `self`
47            // (stored in `parent_type_id`).
48            if [parent_type_id] == *outs.as_slice() {
49                return Some(format!("{comment_prefix}#[must_use]\n"));
50            }
51        }
52    }
53    None
54}
55
56pub fn generate(
57    w: &mut dyn Write,
58    env: &Env,
59    parent_type_id: Option<TypeId>,
60    analysis: &analysis::functions::Info,
61    special_functions: Option<&analysis::special_functions::Infos>,
62    scope_version: Option<Version>,
63    in_trait: bool,
64    only_declaration: bool,
65    indent: usize,
66) -> Result<()> {
67    if !analysis.status.need_generate() {
68        return Ok(());
69    }
70
71    if analysis.is_async_finish(env) {
72        return Ok(());
73    }
74
75    if let Some(special_functions) = special_functions {
76        if special_functions::generate(w, env, analysis, special_functions, scope_version)? {
77            return Ok(());
78        }
79    }
80
81    if analysis.hidden {
82        return Ok(());
83    }
84
85    let commented = analysis.commented;
86    let comment_prefix = if commented { "//" } else { "" };
87    let pub_prefix = if in_trait {
88        String::new()
89    } else {
90        format!("{} ", analysis.visibility)
91    };
92
93    let unsafe_ = if analysis.unsafe_ { "unsafe " } else { "" };
94    let declaration = declaration(env, analysis);
95    let suffix = if only_declaration { ";" } else { " {" };
96
97    writeln!(w)?;
98    cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?;
99    cfg_condition(w, analysis.cfg_condition.as_ref(), commented, indent)?;
100    let version = Version::if_stricter_than(analysis.version, scope_version);
101    version_condition(w, env, None, version, commented, indent)?;
102    not_version_condition(w, analysis.not_version, commented, indent)?;
103    doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?;
104    allow_deprecated(w, analysis.deprecated_version, commented, indent)?;
105    doc_alias(w, &analysis.glib_name, comment_prefix, indent)?;
106    if analysis.codegen_name() != analysis.func_name {
107        doc_alias(w, &analysis.func_name, comment_prefix, indent)?;
108    }
109    // TODO Warn the user if both get_property and set_property are set as it is
110    // an error on the gir data.
111    if let Some(get_property) = &analysis.get_property {
112        if get_property != analysis.codegen_name() {
113            doc_alias(w, get_property, comment_prefix, indent)?;
114        }
115    } else if let Some(set_property) = &analysis.set_property {
116        if set_property != analysis.codegen_name() {
117            doc_alias(w, set_property, comment_prefix, indent)?;
118        }
119    }
120    // Don't add a guard for public or copy/equal functions
121    let dead_code_cfg = if !analysis.visibility.is_public() && !analysis.is_special() {
122        "#[allow(dead_code)]"
123    } else {
124        ""
125    };
126
127    let allow_should_implement_trait = if analysis.codegen_name() == "default" {
128        format!(
129            "{}{}#[allow(clippy::should_implement_trait)]",
130            tabs(indent),
131            comment_prefix
132        )
133    } else {
134        String::new()
135    };
136
137    writeln!(
138        w,
139        "{}{}{}{}{}{}{}{}{}",
140        allow_should_implement_trait,
141        dead_code_cfg,
142        get_must_use_if_needed(parent_type_id, analysis, comment_prefix).unwrap_or_default(),
143        tabs(indent),
144        comment_prefix,
145        pub_prefix,
146        unsafe_,
147        declaration,
148        suffix,
149    )?;
150
151    if !only_declaration {
152        let body = body_chunk(env, analysis, parent_type_id).to_code(env);
153        for s in body {
154            writeln!(w, "{}{}", tabs(indent), s)?;
155        }
156    }
157
158    if analysis.async_future.is_some() {
159        let declaration = declaration_futures(env, analysis);
160        let suffix = if only_declaration { ";" } else { " {" };
161
162        writeln!(w)?;
163        cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?;
164
165        writeln!(w, "{}{}", tabs(indent), comment_prefix)?;
166        cfg_condition(w, analysis.cfg_condition.as_ref(), commented, indent)?;
167        version_condition(w, env, None, version, commented, indent)?;
168        not_version_condition(w, analysis.not_version, commented, indent)?;
169        doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?;
170        writeln!(
171            w,
172            "{}{}{}{}{}{}",
173            tabs(indent),
174            comment_prefix,
175            pub_prefix,
176            unsafe_,
177            declaration,
178            suffix
179        )?;
180
181        if !only_declaration {
182            let body = body_chunk_futures(env, analysis).unwrap();
183            for s in body.lines() {
184                if !s.is_empty() {
185                    writeln!(w, "{}{}{}", tabs(indent + 1), comment_prefix, s)?;
186                } else {
187                    writeln!(w)?;
188                }
189            }
190            writeln!(w, "{}{}}}", tabs(indent), comment_prefix)?;
191        }
192    }
193
194    Ok(())
195}
196
197pub fn declaration(env: &Env, analysis: &analysis::functions::Info) -> String {
198    let outs_as_return = !analysis.outs.is_empty();
199    let return_str = if outs_as_return {
200        out_parameters_as_return(env, analysis)
201    } else if analysis.ret.bool_return_is_error.is_some() {
202        format!(" -> Result<(), {}>", use_glib_type(env, "error::BoolError"))
203    } else if let Some(return_type) = analysis.ret.to_return_value(
204        env,
205        analysis
206            .ret
207            .parameter
208            .as_ref()
209            .map_or(&TryFromGlib::Default, |par| &par.try_from_glib),
210        false,
211    ) {
212        format!(" -> {return_type}")
213    } else {
214        String::new()
215    };
216    let mut param_str = String::with_capacity(100);
217
218    let (bounds, _) = bounds(&analysis.bounds, &[], false, false);
219
220    for par in &analysis.parameters.rust_parameters {
221        if !param_str.is_empty() {
222            param_str.push_str(", ");
223        }
224        let c_par = &analysis.parameters.c_parameters[par.ind_c];
225        let s = c_par.to_parameter(env, &analysis.bounds, false);
226        param_str.push_str(&s);
227    }
228
229    format!(
230        "fn {}{}({}){}",
231        analysis.codegen_name(),
232        bounds,
233        param_str,
234        return_str,
235    )
236}
237
238pub fn declaration_futures(env: &Env, analysis: &analysis::functions::Info) -> String {
239    let async_future = analysis.async_future.as_ref().unwrap();
240
241    let return_str = if let Some(ref error_parameters) = async_future.error_parameters {
242        format!(
243            " -> Pin<Box_<dyn std::future::Future<Output = Result<{}, {}>> + 'static>>",
244            async_future.success_parameters, error_parameters
245        )
246    } else {
247        format!(
248            " -> Pin<Box_<dyn std::future::Future<Output = {}> + 'static>>",
249            async_future.success_parameters
250        )
251    };
252
253    let mut param_str = String::with_capacity(100);
254
255    let mut skipped = 0;
256    let mut skipped_bounds = vec![];
257    for (pos, par) in analysis.parameters.rust_parameters.iter().enumerate() {
258        let c_par = &analysis.parameters.c_parameters[par.ind_c];
259
260        if c_par.name == "callback" || c_par.name == "cancellable" {
261            skipped += 1;
262            if let Some(alias) = analysis
263                .bounds
264                .get_parameter_bound(&c_par.name)
265                .and_then(|bound| bound.type_parameter_reference())
266            {
267                skipped_bounds.push(alias);
268            }
269            continue;
270        }
271
272        if pos - skipped > 0 {
273            param_str.push_str(", ");
274        }
275
276        let s = c_par.to_parameter(env, &analysis.bounds, true);
277        param_str.push_str(&s);
278    }
279
280    let (bounds, _) = bounds(&analysis.bounds, skipped_bounds.as_ref(), true, false);
281
282    format!(
283        "fn {}{}({}){}",
284        async_future.name, bounds, param_str, return_str,
285    )
286}
287
288pub fn bounds(
289    bounds: &Bounds,
290    skip: &[char],
291    r#async: bool,
292    filter_callback_modified: bool,
293) -> (String, Vec<String>) {
294    use crate::analysis::bounds::BoundType::*;
295
296    if bounds.is_empty() {
297        return (String::new(), Vec::new());
298    }
299
300    let skip_lifetimes = bounds
301        .iter()
302        // TODO: False or true?
303        .filter(|bound| bound.alias.is_some_and(|alias| skip.contains(&alias)))
304        .filter_map(|bound| match bound.bound_type {
305            IsA(lifetime) | AsRef(lifetime) => lifetime,
306            _ => None,
307        })
308        .collect::<Vec<_>>();
309
310    let lifetimes = bounds
311        .iter_lifetimes()
312        .filter(|s| !skip_lifetimes.contains(s))
313        .map(|s| format!("'{s}"))
314        .collect::<Vec<_>>();
315
316    let bounds = bounds.iter().filter(|bound| {
317        bound.alias.is_none_or(|alias| !skip.contains(&alias))
318            && (!filter_callback_modified || !bound.callback_modified)
319    });
320
321    let type_names = lifetimes
322        .iter()
323        .cloned()
324        .chain(
325            bounds
326                .clone()
327                .filter_map(|b| b.type_parameter_definition(r#async)),
328        )
329        .collect::<Vec<_>>();
330
331    let type_names = if type_names.is_empty() {
332        String::new()
333    } else {
334        format!("<{}>", type_names.join(", "))
335    };
336
337    let bounds = lifetimes
338        .into_iter()
339        // TODO: enforce that this is only used on NoWrapper!
340        // TODO: Analyze if alias is used in function, otherwise set to None!
341        .chain(bounds.filter_map(|b| b.alias).map(|a| a.to_string()))
342        .collect::<Vec<_>>();
343
344    (type_names, bounds)
345}
346
347pub fn body_chunk(
348    env: &Env,
349    analysis: &analysis::functions::Info,
350    parent_type_id: Option<TypeId>,
351) -> Chunk {
352    if analysis.commented {
353        return ffi_function_todo(env, &analysis.glib_name);
354    }
355
356    let outs_as_return = !analysis.outs.is_empty();
357
358    let mut builder = function_body_chunk::Builder::new();
359    let sys_crate_name = if let Some(ty_id) = parent_type_id {
360        env.sys_crate_import(ty_id)
361    } else {
362        env.main_sys_crate_name().to_owned()
363    };
364
365    builder
366        .glib_name(&format!("{}::{}", sys_crate_name, analysis.glib_name))
367        .assertion(analysis.assertion)
368        .ret(&analysis.ret)
369        .transformations(&analysis.parameters.transformations)
370        .in_unsafe(analysis.unsafe_)
371        .outs_mode(analysis.outs.mode);
372
373    if analysis.r#async {
374        if let Some(ref trampoline) = analysis.trampoline {
375            builder.async_trampoline(trampoline);
376        } else {
377            warn!(
378                "Async function {} has no associated _finish function",
379                analysis.codegen_name(),
380            );
381        }
382    } else {
383        for trampoline in &analysis.callbacks {
384            builder.callback(trampoline);
385        }
386        for trampoline in &analysis.destroys {
387            builder.destroy(trampoline);
388        }
389    }
390
391    for par in &analysis.parameters.c_parameters {
392        if outs_as_return && analysis.outs.iter().any(|out| out.lib_par.name == par.name) {
393            builder.out_parameter(env, par);
394        } else {
395            builder.parameter();
396        }
397    }
398
399    let (bounds, bounds_names) = bounds(&analysis.bounds, &[], false, true);
400
401    builder.generate(env, &bounds, &bounds_names.join(", "))
402}
403
404pub fn body_chunk_futures(
405    env: &Env,
406    analysis: &analysis::functions::Info,
407) -> StdResult<String, fmt::Error> {
408    use std::fmt::Write;
409
410    use crate::analysis::ref_mode::RefMode;
411
412    let async_future = analysis.async_future.as_ref().unwrap();
413
414    let mut body = String::new();
415
416    let gio_future_name = if env.config.library_name != "Gio" {
417        "gio::GioFuture"
418    } else {
419        "crate::GioFuture"
420    };
421    writeln!(body)?;
422
423    if !async_future.assertion.is_none() {
424        writeln!(body, "{}", async_future.assertion)?;
425    }
426    let skip = usize::from(async_future.is_method);
427
428    // Skip the instance parameter
429    for par in analysis.parameters.rust_parameters.iter().skip(skip) {
430        if par.name == "cancellable" || par.name == "callback" {
431            continue;
432        }
433
434        let c_par = &analysis.parameters.c_parameters[par.ind_c];
435
436        let type_ = env.type_(par.typ);
437        let is_str = matches!(type_, library::Type::Basic(library::Basic::Utf8));
438        let is_slice = matches!(type_, library::Type::CArray(_));
439
440        if is_slice {
441            writeln!(body, "let {} = {}.to_vec();", par.name, par.name)?;
442        } else if *c_par.nullable {
443            writeln!(
444                body,
445                "let {} = {}.map(ToOwned::to_owned);",
446                par.name, par.name
447            )?;
448        } else if is_str {
449            writeln!(body, "let {} = String::from({});", par.name, par.name)?;
450        } else if c_par.ref_mode != RefMode::None {
451            writeln!(body, "let {} = {}.clone();", par.name, par.name)?;
452        }
453    }
454
455    if async_future.is_method {
456        writeln!(
457            body,
458            "Box_::pin({gio_future_name}::new(self, move |obj, cancellable, send| {{"
459        )?;
460    } else {
461        writeln!(
462            body,
463            "Box_::pin({gio_future_name}::new(&(), move |_obj, cancellable, send| {{"
464        )?;
465    }
466
467    if async_future.is_method {
468        writeln!(body, "\tobj.{}(", analysis.codegen_name())?;
469    } else if analysis.type_name.is_ok() {
470        writeln!(body, "\tSelf::{}(", analysis.codegen_name())?;
471    } else {
472        writeln!(body, "\t{}(", analysis.codegen_name())?;
473    }
474
475    // Skip the instance parameter
476    for par in analysis.parameters.rust_parameters.iter().skip(skip) {
477        if par.name == "cancellable" {
478            writeln!(body, "\t\tSome(cancellable),")?;
479        } else if par.name == "callback" {
480            continue;
481        } else {
482            let c_par = &analysis.parameters.c_parameters[par.ind_c];
483
484            if *c_par.nullable {
485                writeln!(
486                    body,
487                    "\t\t{}.as_ref().map(::std::borrow::Borrow::borrow),",
488                    par.name
489                )?;
490            } else if c_par.ref_mode != RefMode::None {
491                writeln!(body, "\t\t&{},", par.name)?;
492            } else {
493                writeln!(body, "\t\t{},", par.name)?;
494            }
495        }
496    }
497
498    writeln!(body, "\t\tmove |res| {{")?;
499    writeln!(body, "\t\t\tsend.resolve(res);")?;
500    writeln!(body, "\t\t}},")?;
501    writeln!(body, "\t);")?;
502    writeln!(body, "}}))")?;
503
504    Ok(body)
505}