1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
use log::error;

use super::{
    bounds::{BoundType, Bounds},
    conversion_type::ConversionType,
    ffi_type::used_ffi_type,
    ref_mode::RefMode,
    rust_type::RustType,
    trampoline_parameters::{self, Parameters},
};
use crate::{
    config::{self, gobjects::GObject},
    env::Env,
    library,
    nameutil::signal_to_snake,
    parser::is_empty_c_type,
    traits::IntoString,
    version::Version,
};

#[derive(Debug, Clone)]
pub struct Trampoline {
    pub name: String,
    pub parameters: Parameters,
    pub ret: library::Parameter,
    // This field is used for user callbacks in `codegen::function_body_chunk` when generating
    // inner C functions. We need to have the bound name in order to create variables and also to
    // pass to the C function bounds (otherwise it won't compile because it doesn't know how to
    // infer the bounds).
    pub bound_name: String,
    pub bounds: Bounds,
    pub version: Option<Version>,
    pub inhibit: bool,
    pub concurrency: library::Concurrency,
    pub is_notify: bool,
    pub scope: library::ParameterScope,
    /// It's used to group callbacks
    pub user_data_index: usize,
    pub destroy_index: usize,
    pub nullable: library::Nullable,
    /// This field is used to give the type name when generating the "IsA<X>"
    /// part.
    pub type_name: String,
}

pub type Trampolines = Vec<Trampoline>;

pub fn analyze(
    env: &Env,
    signal: &library::Signal,
    type_tid: library::TypeId,
    in_trait: bool,
    fundamental_type: bool,
    configured_signals: &[&config::signals::Signal],
    obj: &GObject,
    used_types: &mut Vec<String>,
    version: Option<Version>,
) -> Result<Trampoline, Vec<String>> {
    let errors = closure_errors(env, signal);
    if !errors.is_empty() {
        warn_main!(
            type_tid,
            "Can't generate {} trampoline for signal '{}'",
            type_tid.full_name(&env.library),
            signal.name
        );
        return Err(errors);
    }

    let is_notify = signal.name.starts_with("notify::");

    let name = format!("{}_trampoline", signal_to_snake(&signal.name));

    // TODO: move to object.signal.return config
    let inhibit = configured_signals.iter().any(|f| f.inhibit);
    if inhibit && signal.ret.typ != library::TypeId::tid_bool() {
        error!("Wrong return type for Inhibit for signal '{}'", signal.name);
    }

    let mut bounds: Bounds = Default::default();

    if in_trait || fundamental_type {
        let type_name = RustType::builder(env, type_tid)
            .ref_mode(RefMode::ByRefFake)
            .try_build();
        if fundamental_type {
            bounds.add_parameter(
                "this",
                &type_name.into_string(),
                BoundType::AsRef(None),
                false,
            );
        } else {
            bounds.add_parameter(
                "this",
                &type_name.into_string(),
                BoundType::IsA(None),
                false,
            );
        }
    }

    let parameters = if is_notify {
        let mut parameters = trampoline_parameters::Parameters::new(1);

        let owner = env.type_(type_tid);
        let c_type = format!("{}*", owner.get_glib_name().unwrap());

        let transform = parameters.prepare_transformation(
            env,
            type_tid,
            "this".to_owned(),
            c_type,
            library::ParameterDirection::In,
            library::Transfer::None,
            library::Nullable(false),
            crate::analysis::ref_mode::RefMode::ByRef,
            ConversionType::Borrow,
        );
        parameters.transformations.push(transform);

        parameters
    } else {
        trampoline_parameters::analyze(env, &signal.parameters, type_tid, configured_signals, None)
    };

    if in_trait || fundamental_type {
        let type_name = RustType::builder(env, type_tid)
            .ref_mode(RefMode::ByRefFake)
            .try_build();
        if fundamental_type {
            bounds.add_parameter(
                "this",
                &type_name.into_string(),
                BoundType::AsRef(None),
                false,
            );
        } else {
            bounds.add_parameter(
                "this",
                &type_name.into_string(),
                BoundType::IsA(None),
                false,
            );
        }
    }

    for par in &parameters.rust_parameters {
        if let Ok(rust_type) = RustType::builder(env, par.typ)
            .direction(par.direction)
            .try_from_glib(&par.try_from_glib)
            .try_build()
        {
            used_types.extend(rust_type.into_used_types());
        }
    }
    for par in &parameters.c_parameters {
        if let Some(ffi_type) = used_ffi_type(env, par.typ, &par.c_type) {
            used_types.push(ffi_type);
        }
    }

    let mut ret_nullable = signal.ret.nullable;

    if signal.ret.typ != Default::default() {
        if let Ok(rust_type) = RustType::builder(env, signal.ret.typ)
            .direction(library::ParameterDirection::Out)
            .try_build()
        {
            // No GString
            used_types.extend(rust_type.into_used_types());
        }
        if let Some(ffi_type) = used_ffi_type(env, signal.ret.typ, &signal.ret.c_type) {
            used_types.push(ffi_type);
        }

        let nullable_override = configured_signals.iter().find_map(|f| f.ret.nullable);
        if let Some(nullable) = nullable_override {
            ret_nullable = nullable;
        }
    }

    let concurrency = configured_signals
        .iter()
        .map(|f| f.concurrency)
        .next()
        .unwrap_or(obj.concurrency);

    let ret = library::Parameter {
        nullable: ret_nullable,
        ..signal.ret.clone()
    };

    let trampoline = Trampoline {
        name,
        parameters,
        ret,
        bounds,
        version,
        inhibit,
        concurrency,
        is_notify,
        bound_name: String::new(),
        scope: library::ParameterScope::None,
        user_data_index: 0,
        destroy_index: 0,
        nullable: library::Nullable(false),
        type_name: env.library.type_(type_tid).get_name(),
    };
    Ok(trampoline)
}

fn closure_errors(env: &Env, signal: &library::Signal) -> Vec<String> {
    let mut errors: Vec<String> = Vec::new();
    for par in &signal.parameters {
        if let Some(error) = type_error(env, par) {
            errors.push(format!(
                "{} {}: {}",
                error,
                par.name,
                par.typ.full_name(&env.library)
            ));
        }
    }
    if signal.ret.typ != Default::default() {
        if let Some(error) = type_error(env, &signal.ret) {
            errors.push(format!(
                "{} return value {}",
                error,
                signal.ret.typ.full_name(&env.library)
            ));
        }
    }
    errors
}

pub fn type_error(env: &Env, par: &library::Parameter) -> Option<&'static str> {
    use super::rust_type::TypeError::*;
    if par.direction == library::ParameterDirection::Out {
        Some("Out")
    } else if par.direction == library::ParameterDirection::InOut {
        Some("InOut")
    } else if is_empty_c_type(&par.c_type) {
        Some("Empty ctype")
    } else if ConversionType::of(env, par.typ) == ConversionType::Unknown {
        Some("Unknown conversion")
    } else {
        match RustType::try_new(env, par.typ) {
            Err(Ignored(_)) => Some("Ignored"),
            Err(Mismatch(_)) => Some("Mismatch"),
            Err(Unimplemented(_)) => Some("Unimplemented"),
            Ok(_) => None,
        }
    }
}