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::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#[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}