Skip to main content

ktrs_compose/rules/
mutable_state_autoboxing.rs

1//! Port of `rules/MutableStateAutoboxing.kt`.
2
3use std::collections::HashMap;
4
5use ktrs_ast::psi::{KtCallExpression, KtConstantExpression, KtFile, KtFunction, KtReferenceExpression};
6use ktrs_ast::{Ast, NodeId};
7
8use crate::core::compose_kt_config::ComposeKtConfig;
9use crate::core::compose_kt_visitor::ComposeKtVisitor;
10use crate::core::emitter::Emitter;
11use crate::core::util::kt_constant_expressions::{is_double, is_float, is_int, is_long};
12use crate::core::util::psi_elements::find_all_children;
13
14pub struct MutableStateAutoboxing;
15
16impl ComposeKtVisitor for MutableStateAutoboxing {
17    fn visit_file(&self, ast: &mut Ast, file: KtFile, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
18        let all_mutable_state_of_with_constant_single_argument: Vec<(KtCallExpression, KtConstantExpression)> =
19            find_mutable_state_of(ast, file.node())
20                .into_iter()
21                .filter_map(|it| Some((it, KtConstantExpression::cast(ast, single_argument_expression(ast, it)?)?)))
22                .collect();
23        let checks: [(ConstantPredicate, &str); 4] = [
24            (is_int, MUTABLE_STATE_AUTOBOXING_INT),
25            (is_long, MUTABLE_STATE_AUTOBOXING_LONG),
26            (is_double, MUTABLE_STATE_AUTOBOXING_DOUBLE),
27            (is_float, MUTABLE_STATE_AUTOBOXING_FLOAT),
28        ];
29        for (predicate, message) in checks {
30            for &(item, constant_expression) in &all_mutable_state_of_with_constant_single_argument {
31                if predicate(ast, constant_expression) {
32                    emitter.report(ast, item.node(), message, false);
33                }
34            }
35        }
36    }
37
38    fn visit_function(&self, ast: &mut Ast, function: KtFunction, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
39        let parameter_names_and_types: HashMap<String, String> = function
40            .value_parameters(ast)
41            .into_iter()
42            .filter_map(|it| Some((it, it.type_reference(ast)?.text(ast))))
43            .filter(|(_, type_)| SUPPORTED_TYPES.contains(&type_.as_str()))
44            .map(|(it, type_)| (it.name_as_safe_name(ast), type_))
45            .collect();
46        let primitive_candidates: Vec<(KtCallExpression, &str)> = find_mutable_state_of(ast, function.node())
47            .into_iter()
48            .filter_map(|it| {
49                let argument = single_argument_expression(ast, it).filter(|&a| KtReferenceExpression::is(ast, a))?;
50                Some((it, parameter_names_and_types.get(&ast.text(argument))?.as_str()))
51            })
52            .collect();
53        for (message, types) in PARAMETER_TYPE_MESSAGES {
54            for &(candidate, type_) in &primitive_candidates {
55                if types.contains(&type_) {
56                    emitter.report(ast, candidate.node(), message, false);
57                }
58            }
59        }
60    }
61}
62
63type ConstantPredicate = fn(&Ast, KtConstantExpression) -> bool;
64
65fn single_argument_expression(ast: &Ast, call: KtCallExpression) -> Option<NodeId> {
66    match call.value_arguments(ast).as_slice() {
67        [single] => single.argument_expression(ast),
68        _ => None,
69    }
70}
71
72fn find_mutable_state_of(ast: &Ast, element: NodeId) -> Vec<KtCallExpression> {
73    find_all_children::<KtCallExpression>(ast, element)
74        .into_iter()
75        .filter(|it| it.callee_expression(ast).is_some_and(|c| ast.text(c) == "mutableStateOf"))
76        .filter(|it| it.value_arguments(ast).len() == 1)
77        .collect()
78}
79
80/// `emitIfParameterTypeIs(message) { it == ... }`, in upstream's order.
81const PARAMETER_TYPE_MESSAGES: &[(&str, &[&str])] = &[
82    (MUTABLE_STATE_AUTOBOXING_INT, &["Int"]),
83    (MUTABLE_STATE_AUTOBOXING_LONG, &["Long"]),
84    (MUTABLE_STATE_AUTOBOXING_DOUBLE, &["Double"]),
85    (MUTABLE_STATE_AUTOBOXING_FLOAT, &["Float"]),
86    (MUTABLE_STATE_AUTOBOXING_INT_LIST, &["List<Int>", "PersistentList<Int>", "ImmutableList<Int>"]),
87    (MUTABLE_STATE_AUTOBOXING_LONG_LIST, &["List<Long>", "PersistentList<Long>", "ImmutableList<Long>"]),
88    (MUTABLE_STATE_AUTOBOXING_FLOAT_LIST, &["List<Float>", "PersistentList<Float>", "ImmutableList<Float>"]),
89    (MUTABLE_STATE_AUTOBOXING_INT_SET, &["Set<Int>", "PersistentSet<Int>", "ImmutableSet<Int>"]),
90    (MUTABLE_STATE_AUTOBOXING_LONG_SET, &["Set<Long>", "PersistentSet<Long>", "ImmutableSet<Long>"]),
91    (MUTABLE_STATE_AUTOBOXING_FLOAT_SET, &["Set<Float>", "PersistentSet<Float>", "ImmutableSet<Float>"]),
92    (MUTABLE_STATE_AUTOBOXING_INT_INT_MAP, &["Map<Int, Int>", "PersistentMap<Int, Int>", "ImmutableMap<Int, Int>"]),
93    (MUTABLE_STATE_AUTOBOXING_INT_LONG_MAP, &["Map<Int, Long>", "PersistentMap<Int, Long>", "ImmutableMap<Int, Long>"]),
94    (MUTABLE_STATE_AUTOBOXING_INT_FLOAT_MAP, &["Map<Int, Float>", "PersistentMap<Int, Float>", "ImmutableMap<Int, Float>"]),
95    (MUTABLE_STATE_AUTOBOXING_LONG_INT_MAP, &["Map<Long, Int>", "PersistentMap<Long, Int>", "ImmutableMap<Long, Int>"]),
96    (MUTABLE_STATE_AUTOBOXING_LONG_LONG_MAP, &["Map<Long, Long>", "PersistentMap<Long, Long>", "ImmutableMap<Long, Long>"]),
97    (MUTABLE_STATE_AUTOBOXING_LONG_FLOAT_MAP, &["Map<Long, Float>", "PersistentMap<Long, Float>", "ImmutableMap<Long, Float>"]),
98    (MUTABLE_STATE_AUTOBOXING_FLOAT_INT_MAP, &["Map<Float, Int>", "PersistentMap<Float, Int>", "ImmutableMap<Float, Int>"]),
99    (MUTABLE_STATE_AUTOBOXING_FLOAT_LONG_MAP, &["Map<Float, Long>", "PersistentMap<Float, Long>", "ImmutableMap<Float, Long>"]),
100    (MUTABLE_STATE_AUTOBOXING_FLOAT_FLOAT_MAP, &["Map<Float, Float>", "PersistentMap<Float, Float>", "ImmutableMap<Float, Float>"]),
101];
102
103const SUPPORTED_TYPES: &[&str] = &[
104    "Int",
105    "Long",
106    "Float",
107    "Double",
108    "List<Int>",
109    "PersistentList<Int>",
110    "ImmutableList<Int>",
111    "List<Long>",
112    "PersistentList<Long>",
113    "ImmutableList<Long>",
114    "List<Float>",
115    "PersistentList<Float>",
116    "ImmutableList<Float>",
117    "Set<Int>",
118    "PersistentSet<Int>",
119    "ImmutableSet<Int>",
120    "Set<Long>",
121    "PersistentSet<Long>",
122    "ImmutableSet<Long>",
123    "Set<Float>",
124    "PersistentSet<Float>",
125    "ImmutableSet<Float>",
126    "Map<Int, Int>",
127    "PersistentMap<Int, Int>",
128    "ImmutableMap<Int, Int>",
129    "Map<Int, Long>",
130    "PersistentMap<Int, Long>",
131    "ImmutableMap<Int, Long>",
132    "Map<Int, Float>",
133    "PersistentMap<Int, Float>",
134    "ImmutableMap<Int, Float>",
135    "Map<Long, Int>",
136    "PersistentMap<Long, Int>",
137    "ImmutableMap<Long, Int>",
138    "Map<Long, Long>",
139    "PersistentMap<Long, Long>",
140    "ImmutableMap<Long, Long>",
141    "Map<Long, Float>",
142    "PersistentMap<Long, Float>",
143    "ImmutableMap<Long, Float>",
144    "Map<Float, Int>",
145    "PersistentMap<Float, Int>",
146    "ImmutableMap<Float, Int>",
147    "Map<Float, Long>",
148    "PersistentMap<Float, Long>",
149    "ImmutableMap<Float, Long>",
150    "Map<Float, Float>",
151    "PersistentMap<Float, Float>",
152    "ImmutableMap<Float, Float>",
153];
154
155macro_rules! message {
156    ($($name:ident = $head:literal;)*) => {$(
157        pub const $name: &str = concat!(
158            $head,
159            "\nSee https://mrmans0n.github.io/compose-rules/rules/#use-mutablestateof-type-specific-variants-when-possible for more information."
160        );
161    )*};
162}
163
164message! {
165    MUTABLE_STATE_AUTOBOXING_INT = "Using mutableIntStateOf is recommended over mutableStateOf<Int>, as it uses the Int primitive directly which is more performant.";
166    MUTABLE_STATE_AUTOBOXING_LONG = "Using mutableLongStateOf is recommended over mutableStateOf<Long>, as it uses the Long primitive directly which is more performant.";
167    MUTABLE_STATE_AUTOBOXING_DOUBLE = "Using mutableDoubleStateOf is recommended over mutableStateOf<Double>, as it uses the Double primitive directly which is more performant.";
168    MUTABLE_STATE_AUTOBOXING_FLOAT = "Using mutableFloatStateOf is recommended over mutableStateOf<Float>, as it uses the Float primitive directly which is more performant.";
169    MUTABLE_STATE_AUTOBOXING_INT_LIST = "Using mutableIntListOf is recommended over mutableStateOf Immutable/Persistent/List<Int> due to its better performance.";
170    MUTABLE_STATE_AUTOBOXING_LONG_LIST = "Using mutableLongListOf is recommended over mutableStateOf Immutable/Persistent/List<Long> due to its better performance.";
171    MUTABLE_STATE_AUTOBOXING_FLOAT_LIST = "Using mutableFloatListOf is recommended over mutableStateOf Immutable/Persistent/List<Float> due to its better performance.";
172    MUTABLE_STATE_AUTOBOXING_INT_SET = "Using mutableIntSetOf is recommended over mutableStateOf Immutable/Persistent/Set<Int> due to its better performance.";
173    MUTABLE_STATE_AUTOBOXING_LONG_SET = "Using mutableLongSetOf is recommended over mutableStateOf Immutable/Persistent/Set<Long> due to its better performance.";
174    MUTABLE_STATE_AUTOBOXING_FLOAT_SET = "Using mutableFloatSetOf is recommended over mutableStateOf Immutable/Persistent/Set<Float> due to its better performance.";
175    MUTABLE_STATE_AUTOBOXING_INT_INT_MAP = "Using mutableIntIntMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Int, Int> due to its better performance.";
176    MUTABLE_STATE_AUTOBOXING_INT_LONG_MAP = "Using mutableIntLongMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Int, Long> due to its better performance.";
177    MUTABLE_STATE_AUTOBOXING_INT_FLOAT_MAP = "Using mutableIntFloatMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Int, Float> due to its better performance.";
178    MUTABLE_STATE_AUTOBOXING_LONG_INT_MAP = "Using mutableLongIntMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Long, Int> due to its better performance.";
179    MUTABLE_STATE_AUTOBOXING_LONG_LONG_MAP = "Using mutableLongLongMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Long, Long> due to its better performance.";
180    MUTABLE_STATE_AUTOBOXING_LONG_FLOAT_MAP = "Using mutableLongFloatMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Long, Float> due to its better performance.";
181    MUTABLE_STATE_AUTOBOXING_FLOAT_INT_MAP = "Using mutableFloatIntMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Float, Int> due to its better performance.";
182    MUTABLE_STATE_AUTOBOXING_FLOAT_LONG_MAP = "Using mutableFloatLongMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Float, Long> due to its better performance.";
183    MUTABLE_STATE_AUTOBOXING_FLOAT_FLOAT_MAP = "Using mutableFloatFloatMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Float, Float> due to its better performance.";
184}