Skip to main content

mago_analyzer/plugin/libraries/psl/str/
str_functions.rs

1//! PSL string function return type provider.
2
3use mago_codex::ttype::atomic::TAtomic;
4use mago_codex::ttype::atomic::scalar::TScalar;
5use mago_codex::ttype::atomic::scalar::string::TString;
6use mago_codex::ttype::atomic::scalar::string::TStringCasing;
7use mago_codex::ttype::union::TUnion;
8
9use crate::plugin::context::InvocationInfo;
10use crate::plugin::context::ProviderContext;
11use crate::plugin::libraries::stdlib::string::resolve_sprintf;
12use crate::plugin::provider::Provider;
13use crate::plugin::provider::ProviderMeta;
14use crate::plugin::provider::function::FunctionReturnTypeProvider;
15use crate::plugin::provider::function::FunctionTarget;
16
17static META: ProviderMeta = ProviderMeta::new("psl::str", "Psl\\Str\\*", "Returns refined string types based on input");
18
19/// Provider for PSL string functions.
20///
21/// Provides refined string types based on the input string properties.
22#[derive(Default)]
23pub struct StrProvider;
24
25impl Provider for StrProvider {
26    fn meta() -> &'static ProviderMeta {
27        &META
28    }
29}
30
31impl FunctionReturnTypeProvider for StrProvider {
32    fn targets() -> FunctionTarget {
33        FunctionTarget::Namespace(b"psl\\str")
34    }
35
36    fn get_return_type(
37        &self,
38        context: &ProviderContext<'_, '_, '_>,
39        invocation: &InvocationInfo<'_, '_, '_>,
40    ) -> Option<TUnion> {
41        let function_name = invocation.function_name().to_lowercase();
42
43        match function_name.as_str() {
44            "psl\\str\\after"
45            | "psl\\str\\after_ci"
46            | "psl\\str\\after_last"
47            | "psl\\str\\after_last_ci"
48            | "psl\\str\\before"
49            | "psl\\str\\before_ci"
50            | "psl\\str\\before_last"
51            | "psl\\str\\before_last_ci"
52            | "psl\\str\\byte\\after"
53            | "psl\\str\\byte\\after_ci"
54            | "psl\\str\\byte\\after_last"
55            | "psl\\str\\byte\\after_last_ci"
56            | "psl\\str\\byte\\before"
57            | "psl\\str\\byte\\before_ci"
58            | "psl\\str\\byte\\before_last"
59            | "psl\\str\\byte\\before_last_ci"
60            | "psl\\str\\grapheme\\after"
61            | "psl\\str\\grapheme\\after_ci"
62            | "psl\\str\\grapheme\\after_last"
63            | "psl\\str\\grapheme\\after_last_ci"
64            | "psl\\str\\grapheme\\before"
65            | "psl\\str\\grapheme\\before_ci"
66            | "psl\\str\\grapheme\\before_last"
67            | "psl\\str\\grapheme\\before_last_ci" => {
68                let haystack = invocation.get_argument(0, &[b"haystack"])?;
69                let haystack_type = context.get_expression_type(haystack)?.get_single_string()?;
70
71                Some(TUnion::from_vec(vec![
72                    TAtomic::Null,
73                    TAtomic::Scalar(TScalar::String(TString::general_with_props(
74                        false,
75                        false,
76                        false,
77                        false,
78                        haystack_type.casing,
79                    ))),
80                ]))
81            }
82            "psl\\str\\slice"
83            | "psl\\str\\strip_prefix"
84            | "psl\\str\\strip_suffix"
85            | "psl\\str\\reverse"
86            | "psl\\str\\trim"
87            | "psl\\str\\trim_left"
88            | "psl\\str\\trim_right"
89            | "psl\\str\\truncate"
90            | "psl\\str\\byte\\slice"
91            | "psl\\str\\byte\\strip_prefix"
92            | "psl\\str\\byte\\strip_suffix"
93            | "psl\\str\\byte\\reverse"
94            | "psl\\str\\byte\\trim"
95            | "psl\\str\\byte\\trim_left"
96            | "psl\\str\\byte\\trim_right"
97            | "psl\\str\\grapheme\\slice"
98            | "psl\\str\\grapheme\\strip_prefix"
99            | "psl\\str\\grapheme\\strip_suffix"
100            | "psl\\str\\grapheme\\reverse"
101            | "psl\\str\\grapheme\\trim"
102            | "psl\\str\\grapheme\\trim_left"
103            | "psl\\str\\grapheme\\trim_right" => {
104                let string = invocation.get_argument(0, &[b"string"])?;
105                let string_type = context.get_expression_type(string)?.get_single_string()?;
106
107                Some(if string_type.is_literal_origin() {
108                    TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(
109                        false,
110                        false,
111                        false,
112                        false,
113                        string_type.casing,
114                    ))))
115                } else {
116                    TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(
117                        false,
118                        false,
119                        false,
120                        false,
121                        string_type.casing,
122                    ))))
123                })
124            }
125            "psl\\str\\splice" | "psl\\str\\byte\\splice" | "psl\\str\\grapheme\\splice" => {
126                let string = invocation.get_argument(0, &[b"string"])?;
127                let replacement = invocation.get_argument(1, &[b"replacement"])?;
128
129                let string_type = context.get_expression_type(string)?.get_single_string()?;
130                let replacement_type = context.get_expression_type(replacement)?.get_single_string()?;
131
132                Some(if string_type.is_literal_origin() && replacement_type.is_literal_origin() {
133                    TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(
134                        false,
135                        string_type.is_truthy || replacement_type.is_truthy,
136                        string_type.is_non_empty || replacement_type.is_non_empty,
137                        false,
138                        match (string_type.casing, replacement_type.casing) {
139                            (TStringCasing::Lowercase, TStringCasing::Lowercase) => TStringCasing::Lowercase,
140                            (TStringCasing::Uppercase, TStringCasing::Uppercase) => TStringCasing::Uppercase,
141                            _ => TStringCasing::Unspecified,
142                        },
143                    ))))
144                } else {
145                    TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(
146                        false,
147                        string_type.is_truthy || replacement_type.is_truthy,
148                        string_type.is_non_empty || replacement_type.is_non_empty,
149                        false,
150                        match (string_type.casing, replacement_type.casing) {
151                            (TStringCasing::Lowercase, TStringCasing::Lowercase) => TStringCasing::Lowercase,
152                            (TStringCasing::Uppercase, TStringCasing::Uppercase) => TStringCasing::Uppercase,
153                            _ => TStringCasing::Unspecified,
154                        },
155                    ))))
156                })
157            }
158            "psl\\str\\lowercase" | "psl\\str\\byte\\lowercase" | "psl\\str\\grapheme\\lowercase" => {
159                let string = invocation.get_argument(0, &[b"string"])?;
160                let string_type = context.get_expression_type(string)?.get_single_string()?;
161
162                Some(match string_type.literal {
163                    Some(_) => {
164                        TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(
165                            string_type.is_numeric,
166                            string_type.is_truthy,
167                            string_type.is_non_empty,
168                            string_type.is_callable,
169                            TStringCasing::Lowercase,
170                        ))))
171                    }
172                    None => TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(
173                        string_type.is_numeric,
174                        string_type.is_truthy,
175                        string_type.is_non_empty,
176                        string_type.is_callable,
177                        TStringCasing::Lowercase,
178                    )))),
179                })
180            }
181            "psl\\str\\uppercase" | "psl\\str\\byte\\uppercase" | "psl\\str\\grapheme\\uppercase" => {
182                let string = invocation.get_argument(0, &[b"string"])?;
183                let string_type = context.get_expression_type(string)?.get_single_string()?;
184
185                Some(match string_type.literal {
186                    Some(_) => {
187                        TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(
188                            string_type.is_numeric,
189                            string_type.is_truthy,
190                            string_type.is_non_empty,
191                            string_type.is_callable,
192                            TStringCasing::Uppercase,
193                        ))))
194                    }
195                    None => TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(
196                        string_type.is_numeric,
197                        string_type.is_truthy,
198                        string_type.is_non_empty,
199                        string_type.is_callable,
200                        TStringCasing::Uppercase,
201                    )))),
202                })
203            }
204            "psl\\str\\format" => resolve_sprintf(context, invocation),
205            _ => None,
206        }
207    }
208}