libgir/analysis/
out_parameters.rs

1use std::slice::Iter;
2
3use log::error;
4
5use crate::{
6    analysis::{
7        self, conversion_type::ConversionType, function_parameters::CParameter,
8        functions::is_carray_with_direct_elements, return_value, rust_type::RustType,
9    },
10    config::{self, parameter_matchable::ParameterMatchable},
11    env::Env,
12    library::{
13        self, Basic, Function, Nullable, ParameterDirection, Type, TypeId, INTERNAL_NAMESPACE,
14    },
15    nameutil,
16};
17
18#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
19pub enum ThrowFunctionReturnStrategy {
20    #[default]
21    ReturnResult,
22    CheckError,
23    Void,
24}
25
26#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
27pub enum Mode {
28    #[default]
29    None,
30    Normal,
31    Optional,
32    Combined,
33    Throws(ThrowFunctionReturnStrategy),
34}
35
36#[derive(Debug, Default)]
37pub struct Info {
38    pub mode: Mode,
39    pub params: Vec<analysis::Parameter>,
40}
41
42impl Info {
43    pub fn is_empty(&self) -> bool {
44        self.mode == Mode::None
45    }
46
47    pub fn iter(&self) -> Iter<'_, analysis::Parameter> {
48        self.params.iter()
49    }
50}
51
52pub fn analyze(
53    env: &Env,
54    func: &Function,
55    func_c_params: &[CParameter],
56    func_ret: &return_value::Info,
57    configured_functions: &[&config::functions::Function],
58) -> (Info, bool) {
59    let mut info: Info = Default::default();
60    let mut unsupported_outs = false;
61
62    let nullable_override = configured_functions.iter().find_map(|f| f.ret.nullable);
63    if func.throws {
64        let return_strategy =
65            decide_throw_function_return_strategy(env, func_ret, &func.name, configured_functions);
66        info.mode = Mode::Throws(return_strategy);
67    } else if func.ret.typ == TypeId::tid_none() {
68        info.mode = Mode::Normal;
69    } else if func.ret.typ == TypeId::tid_bool() || func.ret.typ == TypeId::tid_c_bool() {
70        if nullable_override == Some(Nullable(false)) {
71            info.mode = Mode::Combined;
72        } else {
73            info.mode = Mode::Optional;
74        }
75    } else {
76        info.mode = Mode::Combined;
77    }
78
79    for lib_par in &func.parameters {
80        if lib_par.direction != ParameterDirection::Out {
81            continue;
82        }
83        if can_as_return(env, lib_par) {
84            let mut lib_par = lib_par.clone();
85            lib_par.name = nameutil::mangle_keywords(&lib_par.name).into_owned();
86            let configured_parameters = configured_functions.matched_parameters(&lib_par.name);
87            let mut out =
88                analysis::Parameter::from_parameter(env, &lib_par, &configured_parameters);
89
90            // FIXME: temporary solution for string_type, nullable override. This should
91            // completely work based on the analyzed parameters instead of the
92            // library parameters.
93            if let Some(c_par) = func_c_params
94                .iter()
95                .find(|c_par| c_par.name == lib_par.name)
96            {
97                out.lib_par.typ = c_par.typ;
98                out.lib_par.nullable = c_par.nullable;
99            }
100
101            info.params.push(out);
102        } else {
103            unsupported_outs = true;
104        }
105    }
106
107    if info.params.is_empty() {
108        info.mode = Mode::None;
109    }
110    if info.mode == Mode::Combined
111        || info.mode == Mode::Throws(ThrowFunctionReturnStrategy::ReturnResult)
112    {
113        let mut ret = analysis::Parameter::from_return_value(env, &func.ret, configured_functions);
114
115        // TODO: fully switch to use analyzed returns (it add too many Return<Option<>>)
116        if let Some(ref par) = func_ret.parameter {
117            ret.lib_par.typ = par.lib_par.typ;
118        }
119        if let Some(val) = nullable_override {
120            ret.lib_par.nullable = val;
121        }
122        info.params.insert(0, ret);
123    }
124
125    (info, unsupported_outs)
126}
127
128pub fn can_as_return(env: &Env, par: &library::Parameter) -> bool {
129    use super::conversion_type::ConversionType::*;
130    match ConversionType::of(env, par.typ) {
131        Direct | Scalar | Option | Result { .. } => true,
132        Pointer => {
133            // Disallow Basic arrays without length
134            if is_carray_with_direct_elements(env, par.typ) && par.array_length.is_none() {
135                return false;
136            }
137
138            RustType::builder(env, par.typ)
139                .direction(ParameterDirection::Out)
140                .scope(par.scope)
141                .try_build_param()
142                .is_ok()
143        }
144        Borrow => false,
145        Unknown => false,
146    }
147}
148
149fn decide_throw_function_return_strategy(
150    env: &Env,
151    ret: &return_value::Info,
152    func_name: &str,
153    configured_functions: &[&config::functions::Function],
154) -> ThrowFunctionReturnStrategy {
155    let typ = ret
156        .parameter
157        .as_ref()
158        .map(|par| par.lib_par.typ)
159        .unwrap_or_default();
160    if env.type_(typ).eq(&Type::Basic(Basic::None)) {
161        ThrowFunctionReturnStrategy::Void
162    } else if use_function_return_for_result(env, typ, func_name, configured_functions) {
163        ThrowFunctionReturnStrategy::ReturnResult
164    } else {
165        ThrowFunctionReturnStrategy::CheckError
166    }
167}
168
169pub fn use_function_return_for_result(
170    env: &Env,
171    typ: TypeId,
172    func_name: &str,
173    configured_functions: &[&config::functions::Function],
174) -> bool {
175    // Configuration takes precedence over everything.
176    let use_return_for_result = configured_functions
177        .iter()
178        .find_map(|f| f.ret.use_return_for_result.as_ref());
179    if let Some(use_return_for_result) = use_return_for_result {
180        if typ == Default::default() {
181            error!("Function \"{}\": use_return_for_result set to true, but function has no return value", func_name);
182            return false;
183        }
184        return *use_return_for_result;
185    }
186
187    if typ == Default::default() {
188        return false;
189    }
190    if typ.ns_id != INTERNAL_NAMESPACE {
191        return true;
192    }
193    let type_ = env.type_(typ);
194    !matches!(&*type_.get_name(), "UInt" | "Boolean" | "Bool")
195}