Skip to main content

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