Skip to main content

i_slint_compiler/passes/
remove_return.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use smol_str::SmolStr;
5use std::collections::BTreeMap;
6use std::sync::Arc;
7
8use crate::expression_tree::Expression;
9use crate::langtype::{Struct, StructName, Type};
10use crate::symbol_counters::SymbolCounters;
11
12pub fn remove_return(doc: &crate::object_tree::Document, symbol_counters: &SymbolCounters) {
13    doc.visit_all_used_components(|component| {
14        crate::object_tree::visit_all_expressions(component, |e, _| {
15            let mut ret_ty = None;
16            fn visit(e: &Expression, ret_ty: &mut Option<Type>) {
17                if ret_ty.is_some() {
18                    return;
19                }
20                match e {
21                    Expression::ReturnStatement(x) => {
22                        *ret_ty = Some(x.as_ref().map_or(Type::Void, |x| x.ty()));
23                    }
24                    _ => e.visit(|e| visit(e, ret_ty)),
25                };
26            }
27            visit(e, &mut ret_ty);
28            let Some(ret_ty) = ret_ty else { return };
29            let ctx = RemoveReturnContext { ret_ty };
30            *e = process_expression(std::mem::take(e), true, &ctx, &ctx.ret_ty, symbol_counters)
31                .into_expression(&ctx.ret_ty, symbol_counters);
32        })
33    });
34}
35
36fn process_expression(
37    e: Expression,
38    toplevel: bool,
39    ctx: &RemoveReturnContext,
40    ty: &Type,
41    symbol_counters: &SymbolCounters,
42) -> ExpressionResult {
43    match e {
44        Expression::DebugHook { expression, .. } => {
45            process_expression(*expression, toplevel, ctx, ty, symbol_counters)
46        }
47        Expression::ReturnStatement(expr) => ExpressionResult::Return(expr.map(|e| *e)),
48        Expression::CodeBlock(expr) => {
49            process_codeblock(expr.into_iter().peekable(), toplevel, ty, ctx, symbol_counters)
50        }
51        Expression::Condition { condition, true_expr, false_expr, .. } => {
52            process_condition(condition, *true_expr, *false_expr, ctx, ty, symbol_counters)
53        }
54        Expression::Cast { from, to } => {
55            let ty = if !has_value(ty) { ty.clone() } else { from.ty() };
56            process_expression(*from, toplevel, ctx, &ty, symbol_counters)
57                .map_value(symbol_counters, |e| Expression::Cast { from: e.into(), to })
58        }
59        Expression::StoreLocalVariable { name, value } => {
60            process_store_local_variable(name, *value, ctx, symbol_counters)
61        }
62        e => {
63            // Normally there shouldn't be any 'return' statements in there since return are not allowed in arbitrary expressions
64            #[cfg(debug_assertions)]
65            {
66                e.visit_recursive(&mut |e| assert!(!matches!(e, Expression::ReturnStatement(_))));
67            }
68            ExpressionResult::Just(e)
69        }
70    }
71}
72
73fn process_condition(
74    condition: Box<Expression>,
75    true_expr: Expression,
76    false_expr: Expression,
77    ctx: &RemoveReturnContext,
78    ty: &Type,
79    symbol_counters: &SymbolCounters,
80) -> ExpressionResult {
81    let te = process_expression(true_expr, false, ctx, ty, symbol_counters);
82    let fe = process_expression(false_expr, false, ctx, ty, symbol_counters);
83    merge_condition_branches(condition, te, fe, ctx, ty, symbol_counters)
84}
85
86fn merge_condition_branches(
87    condition: Box<Expression>,
88    te: ExpressionResult,
89    fe: ExpressionResult,
90    ctx: &RemoveReturnContext,
91    ty: &Type,
92    symbol_counters: &SymbolCounters,
93) -> ExpressionResult {
94    match (te, fe) {
95        (ExpressionResult::Just(te), ExpressionResult::Just(fe)) => Expression::Condition {
96            condition,
97            true_expr: te.into(),
98            false_expr: fe.into(),
99            source_location: None,
100        }
101        .into(),
102        (ExpressionResult::Just(te), ExpressionResult::Return(fe)) => {
103            ExpressionResult::MaybeReturn {
104                pre_statements: Vec::new(),
105                condition: *condition,
106                returned_value: fe,
107                actual_value: cleanup_empty_block(te),
108            }
109        }
110        (ExpressionResult::Return(te), ExpressionResult::Just(fe)) => {
111            ExpressionResult::MaybeReturn {
112                pre_statements: Vec::new(),
113                condition: Expression::UnaryOp { sub: condition, op: '!' },
114                returned_value: te,
115                actual_value: cleanup_empty_block(fe),
116            }
117        }
118        (ExpressionResult::Return(te), ExpressionResult::Return(fe)) => {
119            ExpressionResult::Return(Some(Expression::Condition {
120                condition,
121                true_expr: te.unwrap_or(Expression::CodeBlock(Vec::new())).into(),
122                false_expr: fe.unwrap_or(Expression::CodeBlock(Vec::new())).into(),
123                source_location: None,
124            }))
125        }
126        (te, fe) => {
127            let has_value = has_value(ty) && (te.has_value() || fe.has_value());
128            let ty = if has_value { ty } else { &Type::Void };
129            let te = te.into_return_object(ty, &ctx.ret_ty, symbol_counters);
130            let fe = fe.into_return_object(ty, &ctx.ret_ty, symbol_counters);
131            ExpressionResult::ReturnObject {
132                has_value,
133                has_return_value: self::has_value(&ctx.ret_ty),
134                value: Expression::Condition {
135                    condition,
136                    true_expr: te.into(),
137                    false_expr: fe.into(),
138                    source_location: None,
139                },
140            }
141        }
142    }
143}
144
145fn process_store_local_variable(
146    name: SmolStr,
147    value: Expression,
148    ctx: &RemoveReturnContext,
149    symbol_counters: &SymbolCounters,
150) -> ExpressionResult {
151    let inner_ty = value.ty();
152    match process_expression(value, false, ctx, &inner_ty, symbol_counters) {
153        ExpressionResult::Just(e) => {
154            ExpressionResult::Just(Expression::StoreLocalVariable { name, value: Box::new(e) })
155        }
156        ExpressionResult::Return(r) => ExpressionResult::Return(r),
157        ExpressionResult::MaybeReturn {
158            pre_statements,
159            condition,
160            returned_value,
161            actual_value,
162        } => ExpressionResult::MaybeReturn {
163            pre_statements,
164            condition,
165            returned_value,
166            actual_value: Some(Expression::StoreLocalVariable {
167                name,
168                value: Box::new(
169                    actual_value.unwrap_or(Expression::default_value_for_type(&inner_ty)),
170                ),
171            }),
172        },
173        ExpressionResult::ReturnObject { value, has_return_value, .. } => {
174            let tmp_name: SmolStr = symbol_counters.generate_name("return_check_store");
175            let value_ty = value.ty();
176            let load = |field: &str| Expression::StructFieldAccess {
177                base: Box::new(Expression::ReadLocalVariable {
178                    name: tmp_name.clone(),
179                    ty: value_ty.clone(),
180                }),
181                name: field.into(),
182            };
183            let condition = load(FIELD_CONDITION);
184            let returned_value = has_return_value.then(|| load(FIELD_RETURNED));
185            let actual_value =
186                Some(Expression::StoreLocalVariable { name, value: Box::new(load(FIELD_ACTUAL)) });
187            ExpressionResult::MaybeReturn {
188                pre_statements: vec![Expression::StoreLocalVariable {
189                    name: tmp_name,
190                    value: Box::new(value),
191                }],
192                condition,
193                returned_value,
194                actual_value,
195            }
196        }
197    }
198}
199
200/// Return the expression, unless it is an empty codeblock, then return None
201fn cleanup_empty_block(te: Expression) -> Option<Expression> {
202    if matches!(&te, Expression::CodeBlock(stmts) if stmts.is_empty()) { None } else { Some(te) }
203}
204
205fn process_codeblock(
206    mut iter: std::iter::Peekable<impl Iterator<Item = Expression>>,
207    toplevel: bool,
208    ty: &Type,
209    ctx: &RemoveReturnContext,
210    symbol_counters: &SymbolCounters,
211) -> ExpressionResult {
212    let mut stmts = Vec::new();
213    while let Some(e) = iter.next() {
214        let is_last = iter.peek().is_none();
215        match process_expression(
216            e,
217            toplevel,
218            ctx,
219            if is_last { ty } else { &Type::Void },
220            symbol_counters,
221        ) {
222            ExpressionResult::Just(x) => stmts.push(x),
223            ExpressionResult::Return(x) => {
224                stmts.extend(x);
225                return ExpressionResult::Return(
226                    (!stmts.is_empty()).then_some(Expression::CodeBlock(stmts)),
227                );
228            }
229            ExpressionResult::MaybeReturn {
230                mut pre_statements,
231                condition,
232                returned_value,
233                actual_value,
234            } => {
235                stmts.append(&mut pre_statements);
236                if is_last {
237                    return ExpressionResult::MaybeReturn {
238                        pre_statements: stmts,
239                        condition,
240                        returned_value,
241                        actual_value,
242                    };
243                } else if toplevel {
244                    let rest = process_codeblock(iter, true, ty, ctx, symbol_counters)
245                        .into_expression(&ctx.ret_ty, symbol_counters);
246                    let mut rest_ex = Expression::CodeBlock(
247                        actual_value.into_iter().chain(core::iter::once(rest)).collect(),
248                    );
249                    if rest_ex.ty() != ctx.ret_ty {
250                        rest_ex =
251                            Expression::Cast { from: Box::new(rest_ex), to: ctx.ret_ty.clone() }
252                    }
253                    return ExpressionResult::MaybeReturn {
254                        pre_statements: stmts,
255                        condition,
256                        returned_value,
257                        actual_value: Some(rest_ex),
258                    };
259                } else {
260                    return continue_codeblock(
261                        iter,
262                        ty,
263                        ctx,
264                        ExpressionResult::MaybeReturn {
265                            pre_statements: Vec::new(),
266                            condition,
267                            returned_value,
268                            actual_value,
269                        }
270                        .into_return_object(
271                            ty,
272                            &ctx.ret_ty,
273                            symbol_counters,
274                        ),
275                        stmts,
276                        has_value(&ctx.ret_ty),
277                        symbol_counters,
278                    );
279                }
280            }
281            ExpressionResult::ReturnObject { value, has_value, has_return_value } => {
282                if is_last {
283                    return ExpressionResult::ReturnObject {
284                        value: codeblock_with_expr(stmts, value),
285                        has_value,
286                        has_return_value,
287                    };
288                } else {
289                    return continue_codeblock(
290                        iter,
291                        ty,
292                        ctx,
293                        value,
294                        stmts,
295                        has_return_value,
296                        symbol_counters,
297                    );
298                }
299            }
300        }
301    }
302    ExpressionResult::Just(Expression::CodeBlock(stmts))
303}
304
305fn continue_codeblock(
306    iter: std::iter::Peekable<impl Iterator<Item = Expression>>,
307    ty: &Type,
308    ctx: &RemoveReturnContext,
309    return_object: Expression,
310    mut stmts: Vec<Expression>,
311    has_return_value: bool,
312    symbol_counters: &SymbolCounters,
313) -> ExpressionResult {
314    let rest = process_codeblock(iter, false, ty, ctx, symbol_counters).into_return_object(
315        ty,
316        &ctx.ret_ty,
317        symbol_counters,
318    );
319    let unique_name = symbol_counters.generate_name("return_check_merge");
320    let load = Box::new(Expression::ReadLocalVariable {
321        name: unique_name.clone(),
322        ty: return_object.ty(),
323    });
324    stmts.push(Expression::StoreLocalVariable { name: unique_name, value: return_object.into() });
325    stmts.push(Expression::Condition {
326        condition: Expression::StructFieldAccess {
327            base: load.clone(),
328            name: FIELD_CONDITION.into(),
329        }
330        .into(),
331        true_expr: rest.into(),
332        false_expr: ExpressionResult::Return(has_return_value.then(|| {
333            Expression::StructFieldAccess { base: load.clone(), name: FIELD_RETURNED.into() }
334        }))
335        .into_return_object(ty, &ctx.ret_ty, symbol_counters)
336        .into(),
337        source_location: None,
338    });
339    ExpressionResult::ReturnObject {
340        value: Expression::CodeBlock(stmts),
341        has_value: has_value(ty),
342        has_return_value,
343    }
344}
345
346struct RemoveReturnContext {
347    ret_ty: Type,
348}
349
350#[derive(Debug)]
351#[allow(clippy::large_enum_variant)]
352enum ExpressionResult {
353    /// The expression maps directly to a LLR expression
354    Just(Expression),
355    /// The expression used `return` so we need to check for the return slot
356    MaybeReturn {
357        /// Some statements that initializes some temporary variable (eg arguments to something called later)
358        pre_statements: Vec<Expression>,
359        /// Boolean expression: false means return
360        condition: Expression,
361        /// Value being returned if condition is false
362        returned_value: Option<Expression>,
363        /// The value when we don't return
364        actual_value: Option<Expression>,
365    },
366    /// The expression returns unconditionally
367    Return(Option<Expression>),
368    /// The expression is of type `{ condition: bool, actual: ty, returned: ret_ty}`
369    /// which is the equivalent of `if condition { actual } else { return R }`
370    ReturnObject { value: Expression, has_value: bool, has_return_value: bool },
371}
372
373impl From<Expression> for ExpressionResult {
374    fn from(v: Expression) -> Self {
375        Self::Just(v)
376    }
377}
378
379const FIELD_CONDITION: &str = "condition";
380const FIELD_ACTUAL: &str = "actual";
381const FIELD_RETURNED: &str = "returned";
382
383impl ExpressionResult {
384    fn into_expression(self, ty: &Type, symbol_counters: &SymbolCounters) -> Expression {
385        match self {
386            ExpressionResult::Just(e) => e,
387            ExpressionResult::Return(e) => e.unwrap_or(Expression::CodeBlock(Vec::new())),
388            ExpressionResult::MaybeReturn {
389                mut pre_statements,
390                condition,
391                returned_value,
392                actual_value,
393            } => {
394                pre_statements.push(Expression::Condition {
395                    condition: condition.into(),
396                    true_expr: actual_value.unwrap_or(Expression::CodeBlock(Vec::new())).into(),
397                    false_expr: returned_value.unwrap_or(Expression::CodeBlock(Vec::new())).into(),
398                    source_location: None,
399                });
400                Expression::CodeBlock(pre_statements)
401            }
402            ExpressionResult::ReturnObject { value, has_value, has_return_value } => {
403                let name = symbol_counters.generate_name("returned_expression");
404                let load =
405                    Box::new(Expression::ReadLocalVariable { name: name.clone(), ty: value.ty() });
406                Expression::CodeBlock(vec![
407                    Expression::StoreLocalVariable { name, value: value.into() },
408                    Expression::Condition {
409                        condition: Expression::StructFieldAccess {
410                            base: load.clone(),
411                            name: FIELD_CONDITION.into(),
412                        }
413                        .into(),
414                        true_expr: if has_value {
415                            Expression::StructFieldAccess {
416                                base: load.clone(),
417                                name: FIELD_ACTUAL.into(),
418                            }
419                        } else {
420                            Expression::default_value_for_type(ty)
421                        }
422                        .into(),
423                        false_expr: if has_return_value {
424                            Expression::StructFieldAccess {
425                                base: load.clone(),
426                                name: FIELD_RETURNED.into(),
427                            }
428                        } else {
429                            Expression::default_value_for_type(ty)
430                        }
431                        .into(),
432                        source_location: None,
433                    },
434                ])
435            }
436        }
437    }
438
439    fn into_return_object(
440        self,
441        ty: &Type,
442        ret_ty: &Type,
443        symbol_counters: &SymbolCounters,
444    ) -> Expression {
445        match self {
446            ExpressionResult::Just(e) => {
447                let ret_value = Expression::default_value_for_type(ret_ty);
448                if has_value(ty) {
449                    make_struct(
450                        [
451                            (FIELD_CONDITION, Type::Bool, Expression::BoolLiteral(true)),
452                            (FIELD_RETURNED, ret_ty.clone(), ret_value),
453                            (FIELD_ACTUAL, e.ty(), e),
454                        ]
455                        .into_iter(),
456                    )
457                } else {
458                    let object = make_struct(
459                        [
460                            (FIELD_CONDITION, Type::Bool, Expression::BoolLiteral(true)),
461                            (FIELD_RETURNED, ret_ty.clone(), ret_value),
462                        ]
463                        .into_iter(),
464                    );
465                    if e.is_constant(None) {
466                        object
467                    } else {
468                        Expression::CodeBlock(vec![e, object])
469                    }
470                }
471            }
472            ExpressionResult::MaybeReturn {
473                pre_statements,
474                condition,
475                returned_value,
476                actual_value,
477            } => {
478                let mut true_expr = match actual_value {
479                    Some(e) => {
480                        ExpressionResult::Just(e).into_return_object(ty, ret_ty, symbol_counters)
481                    }
482                    None => make_struct(
483                        [(FIELD_CONDITION, Type::Bool, Expression::BoolLiteral(true))].into_iter(),
484                    ),
485                };
486                let mut false_expr = ExpressionResult::Return(returned_value).into_return_object(
487                    ty,
488                    ret_ty,
489                    symbol_counters,
490                );
491                let true_ty = true_expr.ty();
492                let false_ty = false_expr.ty();
493                if true_ty != false_ty {
494                    let common_ty = Expression::common_target_type_for_type_list(
495                        [&true_ty, &false_ty].into_iter().cloned(),
496                    );
497                    if common_ty != true_ty {
498                        true_expr = convert_struct(
499                            std::mem::take(&mut true_expr),
500                            common_ty.clone(),
501                            symbol_counters,
502                        )
503                    }
504                    if common_ty != false_ty {
505                        false_expr = convert_struct(
506                            std::mem::take(&mut false_expr),
507                            common_ty,
508                            symbol_counters,
509                        )
510                    }
511                }
512                let o = Expression::Condition {
513                    condition: condition.into(),
514                    true_expr: true_expr.into(),
515                    false_expr: false_expr.into(),
516                    source_location: None,
517                };
518                codeblock_with_expr(pre_statements, o)
519            }
520            ExpressionResult::Return(r) => make_struct(
521                [(FIELD_CONDITION, Type::Bool, Expression::BoolLiteral(false))]
522                    .into_iter()
523                    .chain(r.map(|r| (FIELD_RETURNED, ret_ty.clone(), r)))
524                    .chain(has_value(ty).then(|| {
525                        (FIELD_ACTUAL, ty.clone(), Expression::default_value_for_type(ty))
526                    })),
527            ),
528            ExpressionResult::ReturnObject { value, .. } => value,
529        }
530    }
531
532    fn map_value(
533        self,
534        symbol_counters: &SymbolCounters,
535        f: impl FnOnce(Expression) -> Expression,
536    ) -> Self {
537        match self {
538            ExpressionResult::Just(e) => ExpressionResult::Just(f(e)),
539            ExpressionResult::Return(e) => ExpressionResult::Return(e),
540            ExpressionResult::MaybeReturn {
541                pre_statements,
542                condition,
543                returned_value,
544                actual_value,
545            } => ExpressionResult::MaybeReturn {
546                pre_statements,
547                condition,
548                returned_value,
549                actual_value: actual_value.map(f),
550            },
551            ExpressionResult::ReturnObject { value, has_value, has_return_value } => {
552                if !has_value {
553                    return ExpressionResult::ReturnObject { value, has_value, has_return_value };
554                }
555                let name = symbol_counters.generate_name("mapped_expression");
556                let value_ty = value.ty();
557                let load = |field: &str| Expression::StructFieldAccess {
558                    base: Box::new(Expression::ReadLocalVariable {
559                        name: name.clone(),
560                        ty: value_ty.clone(),
561                    }),
562                    name: field.into(),
563                };
564                let condition = (FIELD_CONDITION, Type::Bool, load(FIELD_CONDITION));
565                let actual = f(load(FIELD_ACTUAL));
566                let actual = (FIELD_ACTUAL, actual.ty(), actual);
567                let ret = has_return_value.then(|| {
568                    let r = load(FIELD_RETURNED);
569                    (FIELD_RETURNED, r.ty(), r)
570                });
571                ExpressionResult::ReturnObject {
572                    value: Expression::CodeBlock(vec![
573                        Expression::StoreLocalVariable { name, value: value.into() },
574                        make_struct([condition, actual].into_iter().chain(ret.into_iter())),
575                    ]),
576                    has_value,
577                    has_return_value,
578                }
579            }
580        }
581    }
582
583    fn has_value(&self) -> bool {
584        match self {
585            ExpressionResult::Just(expression) => has_value(&expression.ty()),
586            ExpressionResult::MaybeReturn { actual_value, .. } => {
587                actual_value.as_ref().is_some_and(|x| has_value(&x.ty()))
588            }
589            ExpressionResult::Return(..) => false,
590            ExpressionResult::ReturnObject { has_value, .. } => *has_value,
591        }
592    }
593}
594
595fn codeblock_with_expr(mut pre_statements: Vec<Expression>, expr: Expression) -> Expression {
596    if pre_statements.is_empty() {
597        expr
598    } else {
599        pre_statements.push(expr);
600        Expression::CodeBlock(pre_statements)
601    }
602}
603
604fn make_struct(it: impl Iterator<Item = (&'static str, Type, Expression)>) -> Expression {
605    let mut fields = BTreeMap::<SmolStr, Type>::new();
606    let mut values = BTreeMap::<SmolStr, Expression>::new();
607    let mut voids = Vec::new();
608    for (name, ty, expr) in it {
609        if !has_value(&ty) {
610            if ty != Type::Invalid {
611                voids.push(expr);
612            }
613            continue;
614        }
615        fields.insert(name.into(), ty);
616        values.insert(name.into(), expr);
617    }
618    codeblock_with_expr(
619        voids,
620        Expression::Struct { ty: Arc::new(Struct::new(fields, StructName::None)), values },
621    )
622}
623
624/// Given an expression `from` of type Struct, convert to another type struct with more fields
625/// Add missing members in `from`
626fn convert_struct(from: Expression, to: Type, symbol_counters: &SymbolCounters) -> Expression {
627    let Type::Struct(to) = to else {
628        assert_eq!(to, Type::Invalid);
629        return Expression::Invalid;
630    };
631    if let Expression::Struct { mut values, .. } = from {
632        let mut new_values = BTreeMap::new();
633        for key in to.fields.keys() {
634            let (key, expression) = values
635                .remove_entry(key)
636                .unwrap_or_else(|| (key.clone(), to.default_value_for_field(key)));
637            new_values.insert(key, expression);
638        }
639        return Expression::Struct { values: new_values, ty: to };
640    }
641    let var_name = symbol_counters.generate_name("tmpobj_ret_conv_");
642    let from_ty = from.ty();
643    let mut new_values = BTreeMap::new();
644    let Type::Struct(from_s) = &from_ty else {
645        assert_eq!(from_ty, Type::Invalid);
646        return Expression::Invalid;
647    };
648    for key in to.fields.keys() {
649        let expression = if from_s.fields.contains_key(key) {
650            Expression::StructFieldAccess {
651                base: Box::new(Expression::ReadLocalVariable {
652                    name: var_name.clone(),
653                    ty: from_ty.clone(),
654                }),
655                name: key.clone(),
656            }
657        } else {
658            to.default_value_for_field(key)
659        };
660        new_values.insert(key.clone(), expression);
661    }
662    Expression::CodeBlock(vec![
663        Expression::StoreLocalVariable { name: var_name, value: Box::new(from) },
664        Expression::Struct { values: new_values, ty: to },
665    ])
666}
667
668fn has_value(ty: &Type) -> bool {
669    !matches!(ty, Type::Void | Type::Invalid)
670}