libgir/codegen/
trampoline.rs

1use std::{
2    fmt::Write as FWrite,
3    io::{Result, Write},
4};
5
6use log::error;
7
8use super::{
9    return_value::ToReturnValue, trampoline_from_glib::TrampolineFromGlib,
10    trampoline_to_glib::TrampolineToGlib,
11};
12use crate::{
13    analysis::{
14        bounds::Bounds, ffi_type::ffi_type, ref_mode::RefMode, rust_type::RustType,
15        trampoline_parameters::*, trampolines::Trampoline, try_from_glib::TryFromGlib,
16    },
17    consts::TYPE_PARAMETERS_START,
18    env::Env,
19    library,
20    nameutil::{use_glib_if_needed, use_glib_type, use_gtk_type},
21    traits::IntoString,
22    writer::primitives::tabs,
23};
24
25pub fn generate(
26    w: &mut dyn Write,
27    env: &Env,
28    analysis: &Trampoline,
29    in_trait: bool,
30    indent: usize,
31) -> Result<()> {
32    let (self_bound, fn_self_bound) = in_trait
33        .then(|| {
34            (
35                format!("{}: IsA<{}>, ", TYPE_PARAMETERS_START, analysis.type_name),
36                Some(TYPE_PARAMETERS_START.to_string()),
37            )
38        })
39        .unwrap_or_default();
40
41    let prepend = tabs(indent);
42    let params_str = trampoline_parameters(env, analysis);
43    let func_str = func_string(env, analysis, fn_self_bound, true);
44    let ret_str = trampoline_returns(env, analysis);
45
46    writeln!(
47        w,
48        "{}unsafe extern \"C\" fn {}<{}F: {}>({}, f: {}){} {{",
49        prepend,
50        analysis.name,
51        self_bound,
52        func_str,
53        params_str,
54        use_glib_if_needed(env, "ffi::gpointer"),
55        ret_str,
56    )?;
57    writeln!(w, "{prepend}\tlet f: &F = &*(f as *const F);")?;
58    transformation_vars(w, env, analysis, &prepend)?;
59    let call = trampoline_call_func(env, analysis, in_trait);
60    writeln!(w, "{prepend}\t{call}")?;
61    writeln!(w, "{prepend}}}")?;
62
63    Ok(())
64}
65
66pub fn func_string(
67    env: &Env,
68    analysis: &Trampoline,
69    replace_self_bound: Option<impl AsRef<str>>,
70    closure: bool,
71) -> String {
72    let param_str = func_parameters(env, analysis, replace_self_bound, closure);
73    let return_str = func_returns(env, analysis);
74
75    if closure {
76        let concurrency_str = match analysis.concurrency {
77            // If an object can be Send to other threads, this means that
78            // our callback will be called from whatever thread the object
79            // is sent to. But it will only be ever owned by a single thread
80            // at a time, so signals can only be emitted from one thread at
81            // a time and Sync is not needed
82            library::Concurrency::Send => " + Send",
83            // If an object is Sync, it can be shared between threads, and as
84            // such our callback can be called from arbitrary threads and needs
85            // to be Send *AND* Sync
86            library::Concurrency::SendSync => " + Send + Sync",
87            library::Concurrency::None => "",
88        };
89
90        format!("Fn({param_str}){return_str}{concurrency_str} + 'static")
91    } else {
92        format!("({param_str}){return_str}",)
93    }
94}
95
96fn func_parameters(
97    env: &Env,
98    analysis: &Trampoline,
99    replace_self_bound: Option<impl AsRef<str>>,
100    closure: bool,
101) -> String {
102    let mut param_str = String::with_capacity(100);
103
104    for (pos, par) in analysis.parameters.rust_parameters.iter().enumerate() {
105        if pos == 0 {
106            if let Some(replace_self_bound) = &replace_self_bound {
107                param_str.push_str(par.ref_mode.for_rust_type());
108                param_str.push_str(replace_self_bound.as_ref());
109                continue;
110            }
111        } else {
112            param_str.push_str(", ");
113            if !closure {
114                write!(param_str, "{}: ", par.name).unwrap();
115            }
116        }
117
118        let s = func_parameter(env, par, &analysis.bounds);
119        param_str.push_str(&s);
120    }
121
122    param_str
123}
124
125fn func_parameter(env: &Env, par: &RustParameter, bounds: &Bounds) -> String {
126    // TODO: restore mutable support
127    let ref_mode = if par.ref_mode == RefMode::ByRefMut {
128        RefMode::ByRef
129    } else {
130        par.ref_mode
131    };
132
133    match bounds.get_parameter_bound(&par.name) {
134        // TODO: ASYNC??
135        Some(bound) => bound.full_type_parameter_reference(ref_mode, par.nullable, false),
136        // TODO
137        // Some((None, _)) => panic!("Trampoline expects type name"),
138        None => RustType::builder(env, par.typ)
139            .direction(par.direction)
140            .nullable(par.nullable)
141            .ref_mode(ref_mode)
142            .try_build_param()
143            .into_string(),
144    }
145}
146
147fn func_returns(env: &Env, analysis: &Trampoline) -> String {
148    if analysis.ret.typ == Default::default() {
149        String::new()
150    } else if analysis.inhibit {
151        format!(" -> {inhibit}", inhibit = use_glib_type(env, "Propagation"))
152    } else if let Some(return_type) =
153        analysis
154            .ret
155            .to_return_value(env, &TryFromGlib::default(), true)
156    {
157        format!(" -> {return_type}")
158    } else {
159        String::new()
160    }
161}
162
163fn trampoline_parameters(env: &Env, analysis: &Trampoline) -> String {
164    if analysis.is_notify {
165        return format!(
166            "{}, _param_spec: {}",
167            trampoline_parameter(env, &analysis.parameters.c_parameters[0]),
168            use_glib_if_needed(env, "ffi::gpointer"),
169        );
170    }
171
172    let mut parameter_strs: Vec<String> = Vec::new();
173    for par in &analysis.parameters.c_parameters {
174        let par_str = trampoline_parameter(env, par);
175        parameter_strs.push(par_str);
176    }
177
178    parameter_strs.join(", ")
179}
180
181fn trampoline_parameter(env: &Env, par: &CParameter) -> String {
182    let ffi_type = ffi_type(env, par.typ, &par.c_type);
183    format!("{}: {}", par.name, ffi_type.into_string())
184}
185
186fn trampoline_returns(env: &Env, analysis: &Trampoline) -> String {
187    if analysis.ret.typ == Default::default() {
188        String::new()
189    } else {
190        let ffi_type = ffi_type(env, analysis.ret.typ, &analysis.ret.c_type);
191        format!(" -> {}", ffi_type.into_string())
192    }
193}
194
195fn transformation_vars(
196    w: &mut dyn Write,
197    env: &Env,
198    analysis: &Trampoline,
199    prepend: &str,
200) -> Result<()> {
201    use crate::analysis::trampoline_parameters::TransformationType::*;
202    for transform in &analysis.parameters.transformations {
203        match transform.transformation {
204            None => (),
205            Borrow => (),
206            TreePath => {
207                let c_par = &analysis.parameters.c_parameters[transform.ind_c];
208                writeln!(
209                    w,
210                    "{}\tlet {} = from_glib_full({}({}));",
211                    prepend,
212                    transform.name,
213                    use_gtk_type(env, "ffi::gtk_tree_path_new_from_string"),
214                    c_par.name
215                )?;
216            }
217        }
218    }
219    Ok(())
220}
221
222fn trampoline_call_func(env: &Env, analysis: &Trampoline, in_trait: bool) -> String {
223    let params = trampoline_call_parameters(env, analysis, in_trait);
224    let ret = if analysis.ret.typ == Default::default() {
225        String::new()
226    } else {
227        analysis.ret.trampoline_to_glib(env)
228    };
229    format!("f({params}){ret}")
230}
231
232fn trampoline_call_parameters(env: &Env, analysis: &Trampoline, in_trait: bool) -> String {
233    let mut need_downcast = in_trait;
234    let mut parameter_strs: Vec<String> = Vec::new();
235    for (ind, par) in analysis.parameters.rust_parameters.iter().enumerate() {
236        let transformation = match analysis.parameters.get(ind) {
237            Some(transformation) => transformation,
238            None => {
239                error!("No transformation for {}", par.name);
240                continue;
241            }
242        };
243        let par_str = transformation.trampoline_from_glib(env, need_downcast, *par.nullable);
244        parameter_strs.push(par_str);
245        need_downcast = false; // Only downcast first parameter
246    }
247
248    parameter_strs.join(", ")
249}