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