mago_analyzer/plugin/libraries/psl/str/
str_functions.rs1use 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#[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}