1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4use std::sync::LazyLock;
5
6use mago_atom::Atom;
7use mago_atom::AtomMap;
8use mago_atom::ascii_lowercase_constant_name_atom;
9use mago_atom::atom;
10use mago_atom::concat_atom;
11use mago_names::ResolvedNames;
12use mago_names::scope::NamespaceScope;
13use mago_span::HasPosition;
14use mago_span::HasSpan;
15use mago_syntax::ast::Access;
16use mago_syntax::ast::Array;
17use mago_syntax::ast::ArrayElement;
18use mago_syntax::ast::Binary;
19use mago_syntax::ast::BinaryOperator;
20use mago_syntax::ast::ClassConstantAccess;
21use mago_syntax::ast::ClassLikeConstantSelector;
22use mago_syntax::ast::Construct;
23use mago_syntax::ast::Expression;
24use mago_syntax::ast::Identifier;
25use mago_syntax::ast::LegacyArray;
26use mago_syntax::ast::Literal;
27use mago_syntax::ast::MagicConstant;
28use mago_syntax::ast::StringPart;
29use mago_syntax::ast::UnaryPrefix;
30use mago_syntax::ast::UnaryPrefixOperator;
31
32use crate::flags::attribute::AttributeFlags;
33use crate::identifier::function_like::FunctionLikeIdentifier;
34use crate::metadata::constant::ConstantMetadata;
35use crate::scanner::Context;
36use crate::ttype::atomic::TAtomic;
37use crate::ttype::atomic::array::TArray;
38use crate::ttype::atomic::array::keyed::TKeyedArray;
39use crate::ttype::atomic::array::list::TList;
40use crate::ttype::atomic::callable::TCallable;
41use crate::ttype::atomic::reference::TReference;
42use crate::ttype::atomic::reference::TReferenceMemberSelector;
43use crate::ttype::atomic::scalar::TScalar;
44use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
45use crate::ttype::atomic::scalar::float::TFloat;
46use crate::ttype::atomic::scalar::int::TInteger;
47use crate::ttype::atomic::scalar::string::TString;
48use crate::ttype::atomic::scalar::string::TStringCasing;
49use crate::ttype::atomic::scalar::string::TStringLiteral;
50use crate::ttype::get_arraykey;
51use crate::ttype::get_bool;
52use crate::ttype::get_empty_string;
53use crate::ttype::get_false;
54use crate::ttype::get_float;
55use crate::ttype::get_int;
56use crate::ttype::get_int_or_float;
57use crate::ttype::get_literal_int;
58use crate::ttype::get_literal_string;
59use crate::ttype::get_mixed;
60use crate::ttype::get_mixed_keyed_array;
61use crate::ttype::get_never;
62use crate::ttype::get_non_empty_string;
63use crate::ttype::get_non_negative_int;
64use crate::ttype::get_null;
65use crate::ttype::get_object;
66use crate::ttype::get_open_resource;
67use crate::ttype::get_positive_int;
68use crate::ttype::get_string;
69use crate::ttype::get_true;
70use crate::ttype::get_void;
71use crate::ttype::union::TUnion;
72use crate::ttype::wrap_atomic;
73use crate::utils::str_is_numeric;
74
75#[inline]
80pub fn get_literal_constant_type(name: &str) -> Option<TUnion> {
81 let name = name.strip_prefix('\\').unwrap_or(name);
82
83 if name.eq_ignore_ascii_case("true") {
84 Some(get_true())
85 } else if name.eq_ignore_ascii_case("false") {
86 Some(get_false())
87 } else if name.eq_ignore_ascii_case("null") {
88 Some(get_null())
89 } else {
90 None
91 }
92}
93
94#[inline]
99pub fn get_platform_constant_type(name: &str) -> Option<TUnion> {
100 static DIR_SEPARATOR_SLICE: LazyLock<[TAtomic; 2]> = LazyLock::new(|| {
101 [
102 TAtomic::Scalar(TScalar::String(TString {
103 literal: Some(TStringLiteral::Value(atom("/"))),
104 is_numeric: false,
105 is_truthy: true,
106 is_non_empty: true,
107 is_callable: false,
108 casing: TStringCasing::Lowercase,
109 })),
110 TAtomic::Scalar(TScalar::String(TString {
111 literal: Some(TStringLiteral::Value(atom("\\"))),
112 is_numeric: false,
113 is_truthy: true,
114 is_non_empty: true,
115 is_callable: false,
116 casing: TStringCasing::Lowercase,
117 })),
118 ]
119 });
120
121 const PHP_INT_MAX_SLICE: &[TAtomic] = &[
122 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(9_223_372_036_854_775_807))),
123 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(2_147_483_647))),
124 ];
125
126 const PHP_INT_MIN_SLICE: &[TAtomic] = &[
127 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(-9_223_372_036_854_775_808))),
128 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(-2_147_483_648))),
129 ];
130
131 const PHP_MAJOR_VERSION_ATOMIC: &TAtomic = &TAtomic::Scalar(TScalar::Integer(TInteger::Range(8, 9)));
132 const PHP_ZTS_ATOMIC: &TAtomic = &TAtomic::Scalar(TScalar::Integer(TInteger::Range(0, 1)));
133 const PHP_DEBUG_ATOMIC: &TAtomic = &TAtomic::Scalar(TScalar::Integer(TInteger::Range(0, 1)));
134 const PHP_INT_SIZE_ATOMIC: &TAtomic = &TAtomic::Scalar(TScalar::Integer(TInteger::Range(4, 8)));
135 const PHP_WINDOWS_VERSION_MAJOR_ATOMIC: &TAtomic = &TAtomic::Scalar(TScalar::Integer(TInteger::Range(4, 6)));
136 const PHP_WINDOWS_VERSION_MINOR_SLICE: &[TAtomic] = &[
137 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(0))),
138 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(1))),
139 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(2))),
140 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(10))),
141 TAtomic::Scalar(TScalar::Integer(TInteger::Literal(90))),
142 ];
143
144 let name = name.strip_prefix('\\').unwrap_or(name);
145
146 match name {
147 "PHP_MAXPATHLEN"
148 | "PHP_WINDOWS_VERSION_BUILD"
149 | "LIBXML_VERSION"
150 | "OPENSSL_VERSION_NUMBER"
151 | "PHP_FLOAT_DIG" => Some(get_int()),
152 "PHP_EXTRA_VERSION" => Some(get_string()),
153 "PHP_BUILD_DATE"
154 | "PEAR_EXTENSION_DIR"
155 | "PEAR_INSTALL_DIR"
156 | "PHP_BINARY"
157 | "PHP_BINDIR"
158 | "PHP_CONFIG_FILE_PATH"
159 | "PHP_CONFIG_FILE_SCAN_DIR"
160 | "PHP_DATADIR"
161 | "PHP_EXTENSION_DIR"
162 | "PHP_LIBDIR"
163 | "PHP_LOCALSTATEDIR"
164 | "PHP_MANDIR"
165 | "PHP_OS"
166 | "PHP_OS_FAMILY"
167 | "PHP_PREFIX"
168 | "PHP_EOL"
169 | "PATH_SEPARATOR"
170 | "PHP_VERSION"
171 | "PHP_SAPI"
172 | "PHP_SYSCONFDIR"
173 | "ICONV_IMPL"
174 | "LIBXML_DOTTED_VERSION"
175 | "PCRE_VERSION" => Some(get_non_empty_string()),
176 "STDIN" | "STDOUT" | "STDERR" => Some(get_open_resource()),
177 "NAN" | "PHP_FLOAT_EPSILON" | "INF" => Some(get_float()),
178 "PHP_VERSION_ID" => Some(get_positive_int()),
179 "PHP_RELEASE_VERSION" | "PHP_MINOR_VERSION" => Some(get_non_negative_int()),
180 "PHP_MAJOR_VERSION" => Some(TUnion::from_single(Cow::Borrowed(PHP_MAJOR_VERSION_ATOMIC))),
181 "PHP_ZTS" => Some(TUnion::from_single(Cow::Borrowed(PHP_ZTS_ATOMIC))),
182 "PHP_DEBUG" => Some(TUnion::from_single(Cow::Borrowed(PHP_DEBUG_ATOMIC))),
183 "PHP_INT_SIZE" => Some(TUnion::from_single(Cow::Borrowed(PHP_INT_SIZE_ATOMIC))),
184 "PHP_WINDOWS_VERSION_MAJOR" => Some(TUnion::from_single(Cow::Borrowed(PHP_WINDOWS_VERSION_MAJOR_ATOMIC))),
185 "DIRECTORY_SEPARATOR" => Some(TUnion::new(Cow::Borrowed(DIR_SEPARATOR_SLICE.as_slice()))),
186 "PHP_INT_MAX" => Some(TUnion::new(Cow::Borrowed(PHP_INT_MAX_SLICE))),
187 "PHP_INT_MIN" => Some(TUnion::new(Cow::Borrowed(PHP_INT_MIN_SLICE))),
188 "PHP_WINDOWS_VERSION_MINOR" => Some(TUnion::new(Cow::Borrowed(PHP_WINDOWS_VERSION_MINOR_SLICE))),
189 _ => None,
190 }
191}
192
193#[inline]
194pub(super) fn infer<'arena>(
195 context: &Context<'_, 'arena>,
196 scope: &NamespaceScope,
197 expression: &'arena Expression<'arena>,
198 enclosing_class: Option<Atom>,
199) -> Option<TUnion> {
200 infer_with_constants(context, scope, expression, enclosing_class, None)
201}
202
203#[inline]
204pub(super) fn infer_with_constants<'arena>(
205 context: &Context<'_, 'arena>,
206 scope: &NamespaceScope,
207 expression: &'arena Expression<'arena>,
208 enclosing_class: Option<Atom>,
209 constants: Option<&AtomMap<ConstantMetadata>>,
210) -> Option<TUnion> {
211 match expression {
212 Expression::MagicConstant(magic_constant) => Some(match magic_constant {
213 MagicConstant::Line(_) => {
214 get_literal_int(i64::from(context.file.line_number(magic_constant.start_position().offset())) + 1)
215 }
216 MagicConstant::File(_) => {
217 if let Some(path) = context.file.path.as_deref().and_then(|p| p.to_str()) {
218 get_literal_string(atom(path))
219 } else {
220 get_non_empty_string()
221 }
222 }
223 MagicConstant::Directory(_) => {
224 if let Some(path) = context.file.path.as_deref().and_then(|p| p.parent()).and_then(|p| p.to_str()) {
225 get_literal_string(atom(path))
226 } else {
227 get_non_empty_string()
228 }
229 }
230 MagicConstant::Namespace(_) => {
231 if let Some(namespace_name) = scope.namespace_name() {
232 get_literal_string(atom(namespace_name))
233 } else {
234 get_empty_string()
235 }
236 }
237 MagicConstant::Trait(_) => get_string(),
238 MagicConstant::Class(_) => get_string(),
239 MagicConstant::Function(_) | MagicConstant::Method(_) => get_string(),
240 MagicConstant::Property(_) => get_string(),
241 }),
242 Expression::Literal(literal) => match literal {
243 Literal::String(literal_string) => {
244 Some(match literal_string.value {
245 Some(value) => {
246 if value.is_empty() {
247 get_empty_string()
248 } else if value.len() < 1000 {
249 wrap_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal(atom(value)))))
250 } else {
251 wrap_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(
252 str_is_numeric(value),
253 true, true, false, if value.chars().all(char::is_lowercase) {
257 TStringCasing::Lowercase
258 } else if value.chars().all(char::is_uppercase) {
259 TStringCasing::Uppercase
260 } else {
261 TStringCasing::Unspecified
262 },
263 ))))
264 }
265 }
266 None => get_string(),
267 })
268 }
269 Literal::Integer(literal_integer) => Some(match literal_integer.value {
270 Some(value) => get_literal_int(value as i64),
271 None => get_int_or_float(),
272 }),
273 Literal::Float(_) => Some(get_float()),
274 Literal::True(_) => Some(get_true()),
275 Literal::False(_) => Some(get_false()),
276 Literal::Null(_) => Some(get_null()),
277 },
278 Expression::CompositeString(composite_string) => {
279 let mut contains_content = false;
280 for part in composite_string.parts() {
281 if let StringPart::Literal(literal_string_part) = part
282 && !literal_string_part.value.is_empty()
283 {
284 contains_content = true;
285 break;
286 }
287 }
288
289 if contains_content { Some(get_non_empty_string()) } else { Some(get_string()) }
290 }
291 Expression::UnaryPrefix(UnaryPrefix { operator, operand }) => {
292 let operand_type = infer_with_constants(context, scope, operand, enclosing_class, constants)?;
293
294 match operator {
295 UnaryPrefixOperator::Plus(_) => {
296 Some(if let Some(operand_value) = operand_type.get_single_literal_int_value() {
297 get_literal_int(operand_value)
298 } else if let Some(operand_value) = operand_type.get_single_literal_float_value() {
299 TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::Float(TFloat::literal(operand_value)))))
300 } else {
301 operand_type
302 })
303 }
304 UnaryPrefixOperator::Negation(_) => {
305 Some(if let Some(operand_value) = operand_type.get_single_literal_int_value() {
306 get_literal_int(operand_value.wrapping_neg())
307 } else if let Some(operand_value) = operand_type.get_single_literal_float_value() {
308 TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::Float(TFloat::literal(
309 -operand_value,
310 )))))
311 } else {
312 operand_type
313 })
314 }
315 UnaryPrefixOperator::ArrayCast(_, _) => Some(get_mixed_keyed_array()),
316 UnaryPrefixOperator::BoolCast(_, _) => Some(get_bool()),
317 UnaryPrefixOperator::BooleanCast(_, _) => Some(get_bool()),
318 UnaryPrefixOperator::DoubleCast(_, _) => Some(get_float()),
319 UnaryPrefixOperator::RealCast(_, _) => Some(get_float()),
320 UnaryPrefixOperator::FloatCast(_, _) => Some(get_float()),
321 UnaryPrefixOperator::IntCast(_, _) => Some(get_int()),
322 UnaryPrefixOperator::IntegerCast(_, _) => Some(get_int()),
323 UnaryPrefixOperator::ObjectCast(_, _) => Some(get_object()),
324 UnaryPrefixOperator::UnsetCast(_, _) => Some(get_null()),
325 UnaryPrefixOperator::StringCast(_, _) => Some(get_string()),
326 UnaryPrefixOperator::BinaryCast(_, _) => Some(get_string()),
327 UnaryPrefixOperator::VoidCast(_, _) => Some(get_void()),
328 UnaryPrefixOperator::Not(_) => Some(get_bool()),
329 _ => None,
330 }
331 }
332 Expression::Binary(Binary { operator: BinaryOperator::StringConcat(_), lhs, rhs }) => {
333 let Some(lhs_type) = infer_with_constants(context, scope, lhs, enclosing_class, constants) else {
334 return Some(get_string());
335 };
336 let Some(rhs_type) = infer_with_constants(context, scope, rhs, enclosing_class, constants) else {
337 return Some(get_string());
338 };
339
340 let lhs_string = match lhs_type.get_single_owned() {
341 TAtomic::Scalar(TScalar::String(s)) => s,
342 _ => return Some(get_string()),
343 };
344
345 let rhs_string = match rhs_type.get_single_owned() {
346 TAtomic::Scalar(TScalar::String(s)) => s,
347 _ => return Some(get_string()),
348 };
349
350 if let (Some(left_val), Some(right_val)) =
351 (lhs_string.get_known_literal_value(), rhs_string.get_known_literal_value())
352 {
353 return Some(wrap_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal(concat_atom!(
354 left_val, right_val
355 ))))));
356 }
357
358 let is_non_empty = lhs_string.is_non_empty() || rhs_string.is_non_empty();
359 let is_truthy = lhs_string.is_truthy() || rhs_string.is_truthy();
360 let is_literal_origin = lhs_string.is_literal_origin() && rhs_string.is_literal_origin();
361 let casing = match (lhs_string.casing, rhs_string.casing) {
362 (TStringCasing::Lowercase, TStringCasing::Lowercase) => TStringCasing::Lowercase,
363 (TStringCasing::Uppercase, TStringCasing::Uppercase) => TStringCasing::Uppercase,
364 _ => TStringCasing::Unspecified,
365 };
366
367 let final_string_type = if is_literal_origin {
368 TString::unspecified_literal_with_props(false, is_truthy, is_non_empty, false, casing)
369 } else {
370 TString::general_with_props(false, is_truthy, is_non_empty, false, casing)
371 };
372
373 Some(wrap_atomic(TAtomic::Scalar(TScalar::String(final_string_type))))
374 }
375 Expression::Binary(Binary { operator, lhs, rhs }) if operator.is_bitwise() => {
376 let lhs = infer_with_constants(context, scope, lhs, enclosing_class, constants);
377 let rhs = infer_with_constants(context, scope, rhs, enclosing_class, constants);
378
379 Some(wrap_atomic(
380 match (
381 lhs.and_then(|v| v.get_single_literal_int_value()),
382 rhs.and_then(|v| v.get_single_literal_int_value()),
383 ) {
384 (Some(lhs), Some(rhs)) => {
385 let value = match operator {
386 BinaryOperator::BitwiseAnd(_) => lhs & rhs,
387 BinaryOperator::BitwiseOr(_) => lhs | rhs,
388 BinaryOperator::BitwiseXor(_) => lhs ^ rhs,
389 BinaryOperator::LeftShift(_) => {
390 if rhs < 0 {
391 return Some(get_int());
392 }
393
394 u32::try_from(rhs).ok().and_then(|s| lhs.checked_shl(s)).unwrap_or_default()
395 }
396 BinaryOperator::RightShift(_) => {
397 if rhs < 0 {
398 return Some(get_int());
399 }
400
401 match u32::try_from(rhs).ok().and_then(|s| lhs.checked_shr(s)) {
402 Some(v) => v,
403 None => {
404 if lhs >= 0 {
405 0
406 } else {
407 -1
408 }
409 }
410 }
411 }
412 _ => {
413 unreachable!("unexpected bitwise operator: {:?}", operator);
414 }
415 };
416
417 TAtomic::Scalar(TScalar::literal_int(value))
418 }
419 _ => TAtomic::Scalar(TScalar::int()),
420 },
421 ))
422 }
423 Expression::Binary(Binary { operator, lhs, rhs }) if operator.is_arithmetic() => {
424 let lhs = infer_with_constants(context, scope, lhs, enclosing_class, constants);
425 let rhs = infer_with_constants(context, scope, rhs, enclosing_class, constants);
426
427 match (
428 lhs.and_then(|v| v.get_single_literal_int_value()),
429 rhs.and_then(|v| v.get_single_literal_int_value()),
430 ) {
431 (Some(lhs_val), Some(rhs_val)) => {
432 let result = match operator {
433 BinaryOperator::Addition(_) => lhs_val.checked_add(rhs_val),
434 BinaryOperator::Subtraction(_) => lhs_val.checked_sub(rhs_val),
435 BinaryOperator::Multiplication(_) => lhs_val.checked_mul(rhs_val),
436 BinaryOperator::Modulo(_) if rhs_val != 0 => Some(lhs_val % rhs_val),
437 BinaryOperator::Exponentiation(_) if rhs_val >= 0 => lhs_val.checked_pow(rhs_val as u32),
438 BinaryOperator::Division(_) if rhs_val != 0 && lhs_val % rhs_val == 0 => {
439 Some(lhs_val / rhs_val)
440 }
441 _ => None,
442 };
443
444 match result {
445 Some(v) => Some(get_literal_int(v)),
446 None => Some(get_int_or_float()),
447 }
448 }
449 _ => Some(get_int_or_float()),
451 }
452 }
453 Expression::Construct(construct) => match construct {
454 Construct::Isset(_) => Some(get_bool()),
455 Construct::Empty(_) => Some(get_bool()),
456 Construct::Print(_) => Some(get_literal_int(1)),
457 _ => None,
458 },
459 Expression::ConstantAccess(access) => infer_constant(context.resolved_names, &access.name, constants),
460 Expression::Access(Access::ClassConstant(ClassConstantAccess {
461 class,
462 constant: ClassLikeConstantSelector::Identifier(identifier),
463 ..
464 })) => {
465 let class_name_str = if let Expression::Identifier(identifier) = class {
466 context.resolved_names.get(identifier)
467 } else if matches!(class, Expression::Self_(_) | Expression::Static(_)) {
468 enclosing_class.as_ref().map(Atom::as_str)?
469 } else {
470 return None;
471 };
472
473 Some(wrap_atomic(if identifier.value.eq_ignore_ascii_case("class") {
474 TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::literal(atom(class_name_str))))
475 } else if class_name_str.eq_ignore_ascii_case("Attribute") {
476 let bits = match identifier.value {
477 "TARGET_CLASS" => Some(AttributeFlags::TARGET_CLASS.bits()),
478 "TARGET_FUNCTION" => Some(AttributeFlags::TARGET_FUNCTION.bits()),
479 "TARGET_METHOD" => Some(AttributeFlags::TARGET_METHOD.bits()),
480 "TARGET_PROPERTY" => Some(AttributeFlags::TARGET_PROPERTY.bits()),
481 "TARGET_CLASS_CONSTANT" => Some(AttributeFlags::TARGET_CLASS_CONSTANT.bits()),
482 "TARGET_PARAMETER" => Some(AttributeFlags::TARGET_PARAMETER.bits()),
483 "TARGET_CONSTANT" => Some(AttributeFlags::TARGET_CONSTANT.bits()),
484 "TARGET_ALL" => Some(AttributeFlags::TARGET_ALL.bits()),
485 "IS_REPEATABLE" => Some(AttributeFlags::IS_REPEATABLE.bits()),
486 _ => None,
487 };
488
489 match bits {
490 Some(bits) => return Some(get_literal_int(i64::from(bits))),
491 None => TAtomic::Reference(TReference::Member {
492 class_like_name: atom(class_name_str),
493 member_selector: TReferenceMemberSelector::Identifier(atom(identifier.value)),
494 }),
495 }
496 } else {
497 TAtomic::Reference(TReference::Member {
498 class_like_name: atom(class_name_str),
499 member_selector: TReferenceMemberSelector::Identifier(atom(identifier.value)),
500 })
501 }))
502 }
503 Expression::Array(Array { elements, .. }) | Expression::LegacyArray(LegacyArray { elements, .. })
504 if is_list_array_expression(expression) =>
505 {
506 let mut entries = BTreeMap::new();
507
508 for (i, element) in elements.iter().enumerate() {
509 let ArrayElement::Value(element) = element else {
510 return None;
511 };
512
513 let value_type = infer_with_constants(context, scope, element.value, enclosing_class, constants)
514 .unwrap_or_else(get_mixed);
515
516 entries.insert(i, (false, value_type));
517 }
518
519 Some(wrap_atomic(TAtomic::Array(TArray::List(TList {
520 known_count: Some(entries.len()),
521 known_elements: Some(entries),
522 element_type: Arc::new(get_never()),
523 non_empty: !elements.is_empty(),
524 }))))
525 }
526 Expression::Array(Array { elements, .. }) | Expression::LegacyArray(LegacyArray { elements, .. })
527 if is_keyed_array_expression(expression) =>
528 {
529 let mut known_items = BTreeMap::new();
530 let mut unknown_key_values = Vec::new();
531 for element in elements {
532 let ArrayElement::KeyValue(element) = element else {
533 return None;
534 };
535
536 let value_type = infer_with_constants(context, scope, element.value, enclosing_class, constants)
537 .unwrap_or_else(get_mixed);
538
539 let Some(key_type) = infer_with_constants(context, scope, element.key, enclosing_class, constants)
540 .and_then(|v| v.get_single_array_key())
541 else {
542 unknown_key_values.push(value_type);
543 continue;
544 };
545
546 known_items.insert(key_type, (false, value_type));
547
548 if known_items.len() > 100 {
549 return None;
550 }
551 }
552
553 let mut keyed_array = TKeyedArray::new();
554 keyed_array.non_empty = !known_items.is_empty();
555 if !known_items.is_empty() {
556 keyed_array.known_items = Some(known_items);
557 }
558
559 if !unknown_key_values.is_empty() {
560 let mut value_parameter_types = vec![];
561 for value_type in unknown_key_values {
562 value_parameter_types.extend(value_type.types.into_owned());
563 }
564
565 keyed_array.parameters =
566 Some((Arc::new(get_arraykey()), Arc::new(TUnion::from_vec(value_parameter_types))))
567 }
568
569 Some(TUnion::from_single(Cow::Owned(TAtomic::Array(TArray::Keyed(keyed_array)))))
570 }
571 Expression::Closure(closure) => {
572 let span = closure.span();
573
574 Some(wrap_atomic(TAtomic::Callable(TCallable::Alias(FunctionLikeIdentifier::Closure(
575 span.file_id,
576 span.start,
577 )))))
578 }
579 Expression::ArrowFunction(arrow_func) => {
580 let span = arrow_func.span();
581
582 Some(wrap_atomic(TAtomic::Callable(TCallable::Alias(FunctionLikeIdentifier::Closure(
583 span.file_id,
584 span.start,
585 )))))
586 }
587 _ => None,
588 }
589}
590
591#[inline]
592fn infer_constant<'ctx, 'arena>(
593 names: &'ctx ResolvedNames<'arena>,
594 constant: &'ctx Identifier<'arena>,
595 constants_map: Option<&AtomMap<ConstantMetadata>>,
596) -> Option<TUnion> {
597 let (short_name, fqn) = if names.is_imported(constant) {
598 (names.get(constant), names.get(constant))
599 } else if let Some(stripped) = constant.value().strip_prefix('\\') {
600 (stripped, names.get(constant))
601 } else {
602 (constant.value(), names.get(constant))
603 };
604
605 if let Some(t) = get_literal_constant_type(short_name) {
606 return Some(t);
607 }
608
609 if let Some(t) = get_platform_constant_type(short_name) {
610 return Some(t);
611 }
612
613 if let Some(constants) = constants_map {
614 let normalized_name = ascii_lowercase_constant_name_atom(fqn);
615
616 if let Some(constant_metadata) = constants.get(&normalized_name)
617 && let Some(inferred_type) = &constant_metadata.inferred_type
618 {
619 return Some(inferred_type.clone());
620 }
621 }
622
623 None
624}
625
626#[inline]
627fn is_list_array_expression(expression: &Expression) -> bool {
628 match expression {
629 Expression::Array(Array { elements, .. }) | Expression::LegacyArray(LegacyArray { elements, .. }) => {
630 elements.iter().all(|element| matches!(element, ArrayElement::Value(_)))
631 }
632 _ => false,
633 }
634}
635
636#[inline]
637fn is_keyed_array_expression(expression: &Expression) -> bool {
638 match expression {
639 Expression::Array(Array { elements, .. }) | Expression::LegacyArray(LegacyArray { elements, .. }) => {
640 elements.iter().all(|element| matches!(element, ArrayElement::KeyValue(_)))
641 }
642 _ => false,
643 }
644}