1use std::sync::Arc;
2
3use php_ast::Span;
4
5use mir_codebase::storage::{FnParam, MethodStorage, Visibility};
6use mir_issues::{IssueKind, Severity};
7use mir_types::{Atomic, Union};
8
9use crate::expr::ExpressionAnalyzer;
10
11pub struct CheckArgsParams<'a> {
16 pub fn_name: &'a str,
17 pub params: &'a [FnParam],
18 pub arg_types: &'a [Union],
19 pub arg_spans: &'a [Span],
20 pub arg_names: &'a [Option<String>],
21 pub call_span: Span,
22 pub has_spread: bool,
23}
24
25pub fn check_constructor_args(
26 ea: &mut ExpressionAnalyzer<'_>,
27 class_name: &str,
28 p: CheckArgsParams<'_>,
29) {
30 let ctor_name = format!("{}::__construct", class_name);
31 check_args(
32 ea,
33 CheckArgsParams {
34 fn_name: &ctor_name,
35 ..p
36 },
37 );
38}
39
40pub fn spread_element_type(arr_ty: &Union) -> Union {
43 let mut result = Union::empty();
44 for atomic in arr_ty.types.iter() {
45 match atomic {
46 Atomic::TArray { value, .. }
47 | Atomic::TNonEmptyArray { value, .. }
48 | Atomic::TList { value }
49 | Atomic::TNonEmptyList { value } => {
50 for t in value.types.iter() {
51 result.add_type(t.clone());
52 }
53 }
54 Atomic::TKeyedArray { properties, .. } => {
55 for (_key, prop) in properties.iter() {
56 for t in prop.ty.types.iter() {
57 result.add_type(t.clone());
58 }
59 }
60 }
61 _ => return Union::mixed(),
62 }
63 }
64 if result.types.is_empty() {
65 Union::mixed()
66 } else {
67 result
68 }
69}
70
71pub(crate) fn substitute_static_in_return(ret: Union, receiver_fqcn: &Arc<str>) -> Union {
73 let from_docblock = ret.from_docblock;
74 let types: Vec<Atomic> = ret
75 .types
76 .into_iter()
77 .map(|a| match a {
78 Atomic::TStaticObject { .. } | Atomic::TSelf { .. } => Atomic::TNamedObject {
79 fqcn: receiver_fqcn.clone(),
80 type_params: vec![],
81 },
82 other => other,
83 })
84 .collect();
85 let mut result = Union::from_vec(types);
86 result.from_docblock = from_docblock;
87 result
88}
89
90pub(crate) fn check_method_visibility(
91 ea: &mut ExpressionAnalyzer<'_>,
92 method: &MethodStorage,
93 ctx: &crate::context::Context,
94 span: Span,
95) {
96 match method.visibility {
97 Visibility::Private => {
98 let caller_fqcn = ctx.self_fqcn.as_deref().unwrap_or("");
99 let from_trait = ea.codebase.traits.contains_key(method.fqcn.as_ref());
100 let allowed = caller_fqcn == method.fqcn.as_ref()
101 || (from_trait
102 && ea
103 .codebase
104 .extends_or_implements(caller_fqcn, method.fqcn.as_ref()));
105 if !allowed {
106 ea.emit(
107 IssueKind::UndefinedMethod {
108 class: method.fqcn.to_string(),
109 method: method.name.to_string(),
110 },
111 Severity::Error,
112 span,
113 );
114 }
115 }
116 Visibility::Protected => {
117 let caller_fqcn = ctx.self_fqcn.as_deref().unwrap_or("");
118 if caller_fqcn.is_empty() {
119 ea.emit(
120 IssueKind::UndefinedMethod {
121 class: method.fqcn.to_string(),
122 method: method.name.to_string(),
123 },
124 Severity::Error,
125 span,
126 );
127 } else {
128 let allowed = caller_fqcn == method.fqcn.as_ref()
129 || ea
130 .codebase
131 .extends_or_implements(caller_fqcn, method.fqcn.as_ref());
132 if !allowed {
133 ea.emit(
134 IssueKind::UndefinedMethod {
135 class: method.fqcn.to_string(),
136 method: method.name.to_string(),
137 },
138 Severity::Error,
139 span,
140 );
141 }
142 }
143 }
144 Visibility::Public => {}
145 }
146}
147
148pub(crate) fn check_args(ea: &mut ExpressionAnalyzer<'_>, p: CheckArgsParams<'_>) {
153 let CheckArgsParams {
154 fn_name,
155 params,
156 arg_types,
157 arg_spans,
158 arg_names,
159 call_span,
160 has_spread,
161 } = p;
162
163 let has_named = arg_names.iter().any(|n| n.is_some());
164 let mut param_to_arg: Vec<Option<(Union, Span)>> = vec![None; params.len()];
165
166 if has_named {
167 let mut positional = 0usize;
168 for (i, (ty, span)) in arg_types.iter().zip(arg_spans.iter()).enumerate() {
169 if let Some(Some(name)) = arg_names.get(i) {
170 if let Some(pi) = params.iter().position(|p| p.name.as_ref() == name.as_str()) {
171 param_to_arg[pi] = Some((ty.clone(), *span));
172 }
173 } else {
174 while positional < params.len() && param_to_arg[positional].is_some() {
175 positional += 1;
176 }
177 if positional < params.len() {
178 param_to_arg[positional] = Some((ty.clone(), *span));
179 positional += 1;
180 }
181 }
182 }
183 } else {
184 for (i, (ty, span)) in arg_types.iter().zip(arg_spans.iter()).enumerate() {
185 if i < params.len() {
186 param_to_arg[i] = Some((ty.clone(), *span));
187 }
188 }
189 }
190
191 let required_count = params
192 .iter()
193 .filter(|p| !p.is_optional && !p.is_variadic)
194 .count();
195 let provided_count = if params.iter().any(|p| p.is_variadic) {
196 arg_types.len()
197 } else {
198 arg_types.len().min(params.len())
199 };
200
201 if provided_count < required_count && !has_spread {
202 ea.emit(
203 IssueKind::InvalidArgument {
204 param: format!("#{}", provided_count + 1),
205 fn_name: fn_name.to_string(),
206 expected: format!("{} argument(s)", required_count),
207 actual: format!("{} provided", provided_count),
208 },
209 Severity::Error,
210 call_span,
211 );
212 return;
213 }
214
215 for (param, slot) in params.iter().zip(param_to_arg.iter()) {
216 let (arg_ty, arg_span) = match slot {
217 Some(pair) => pair,
218 None => continue,
219 };
220 let arg_span = *arg_span;
221
222 if let Some(raw_param_ty) = ¶m.ty {
223 let param_ty_owned;
224 let param_ty: &Union = if param.is_variadic {
225 if let Some(elem_ty) = raw_param_ty.types.iter().find_map(|a| match a {
226 Atomic::TList { value } | Atomic::TNonEmptyList { value } => {
227 Some(*value.clone())
228 }
229 _ => None,
230 }) {
231 param_ty_owned = elem_ty;
232 ¶m_ty_owned
233 } else {
234 raw_param_ty
235 }
236 } else {
237 raw_param_ty
238 };
239
240 if !param_ty.is_nullable()
241 && !param_ty.is_mixed()
242 && arg_ty.is_single()
243 && arg_ty.contains(|t| matches!(t, Atomic::TNull))
244 {
245 ea.emit(
246 IssueKind::InvalidArgument {
247 param: param.name.to_string(),
248 fn_name: fn_name.to_string(),
249 expected: format!("{}", param_ty),
250 actual: format!("{}", arg_ty),
251 },
252 Severity::Error,
253 arg_span,
254 );
255 } else if !param_ty.is_nullable() && !param_ty.is_mixed() && arg_ty.is_nullable() {
256 ea.emit(
257 IssueKind::PossiblyNullArgument {
258 param: param.name.to_string(),
259 fn_name: fn_name.to_string(),
260 },
261 Severity::Info,
262 arg_span,
263 );
264 }
265
266 if !arg_ty.is_subtype_of_simple(param_ty)
267 && !param_ty.is_mixed()
268 && !arg_ty.is_mixed()
269 && !named_object_subtype(arg_ty, param_ty, ea)
270 && !param_contains_template_or_unknown(param_ty, ea)
271 && !param_contains_template_or_unknown(arg_ty, ea)
272 && !array_list_compatible(arg_ty, param_ty, ea)
273 && !(arg_ty.is_single() && param_ty.is_subtype_of_simple(arg_ty))
274 && !(arg_ty.is_single() && param_ty.remove_null().is_subtype_of_simple(arg_ty))
275 && !(arg_ty.is_single()
276 && param_ty
277 .types
278 .iter()
279 .any(|p| Union::single(p.clone()).is_subtype_of_simple(arg_ty)))
280 && !arg_ty.remove_null().is_subtype_of_simple(param_ty)
281 && !arg_ty.remove_false().is_subtype_of_simple(param_ty)
282 && !named_object_subtype(&arg_ty.remove_null(), param_ty, ea)
283 && !named_object_subtype(&arg_ty.remove_false(), param_ty, ea)
284 {
285 ea.emit(
286 IssueKind::InvalidArgument {
287 param: param.name.to_string(),
288 fn_name: fn_name.to_string(),
289 expected: format!("{}", param_ty),
290 actual: invalid_argument_actual_type(arg_ty, param_ty, ea),
291 },
292 Severity::Error,
293 arg_span,
294 );
295 }
296 }
297 }
298}
299
300fn invalid_argument_actual_type(
305 arg_ty: &Union,
306 param_ty: &Union,
307 ea: &ExpressionAnalyzer<'_>,
308) -> String {
309 if let Some(projected) = project_generic_ancestor_type(arg_ty, param_ty, ea) {
310 return format!("{}", projected);
311 }
312 format!("{}", arg_ty)
313}
314
315fn project_generic_ancestor_type(
316 arg_ty: &Union,
317 param_ty: &Union,
318 ea: &ExpressionAnalyzer<'_>,
319) -> Option<Union> {
320 if !arg_ty.is_single() {
321 return None;
322 }
323 let arg_fqcn = match arg_ty.types.first()? {
324 Atomic::TNamedObject { fqcn, type_params } => {
325 if !type_params.is_empty() {
326 return None;
327 }
328 fqcn
329 }
330 Atomic::TSelf { fqcn } | Atomic::TStaticObject { fqcn } | Atomic::TParent { fqcn } => fqcn,
331 _ => return None,
332 };
333 let resolved_arg = ea.codebase.resolve_class_name(&ea.file, arg_fqcn.as_ref());
334
335 for param_atomic in ¶m_ty.types {
336 let (param_fqcn, param_type_params) = match param_atomic {
337 Atomic::TNamedObject { fqcn, type_params } => (fqcn, type_params),
338 _ => continue,
339 };
340 if param_type_params.is_empty() {
341 continue;
342 }
343
344 let resolved_param = ea
345 .codebase
346 .resolve_class_name(&ea.file, param_fqcn.as_ref());
347 let ancestor_args = generic_ancestor_type_args(arg_fqcn.as_ref(), &resolved_param, ea)
348 .or_else(|| generic_ancestor_type_args(&resolved_arg, &resolved_param, ea))
349 .or_else(|| generic_ancestor_type_args(arg_fqcn.as_ref(), param_fqcn.as_ref(), ea))
350 .or_else(|| generic_ancestor_type_args(&resolved_arg, param_fqcn.as_ref(), ea))?;
351 if ancestor_args.is_empty() {
352 continue;
353 }
354
355 return Some(Union::single(Atomic::TNamedObject {
356 fqcn: param_fqcn.clone(),
357 type_params: ancestor_args,
358 }));
359 }
360
361 None
362}
363
364fn named_object_subtype(arg: &Union, param: &Union, ea: &ExpressionAnalyzer<'_>) -> bool {
367 arg.types.iter().all(|a_atomic| {
368 let arg_fqcn: &Arc<str> = match a_atomic {
369 Atomic::TNamedObject { fqcn, .. } => fqcn,
370 Atomic::TSelf { fqcn } | Atomic::TStaticObject { fqcn } => {
371 if ea.codebase.traits.contains_key(fqcn.as_ref()) {
372 return true;
373 }
374 fqcn
375 }
376 Atomic::TParent { fqcn } => fqcn,
377 Atomic::TNever => return true,
378 Atomic::TClosure { .. } => {
379 return param.types.iter().any(|p| match p {
380 Atomic::TClosure { .. } | Atomic::TCallable { .. } => true,
381 Atomic::TNamedObject { fqcn, .. } => fqcn.as_ref() == "Closure",
382 _ => false,
383 });
384 }
385 Atomic::TCallable { .. } => {
386 return param.types.iter().any(|p| match p {
387 Atomic::TCallable { .. } | Atomic::TClosure { .. } => true,
388 Atomic::TNamedObject { fqcn, .. } => fqcn.as_ref() == "Closure",
389 _ => false,
390 });
391 }
392 Atomic::TClassString(Some(arg_cls)) => {
393 return param.types.iter().any(|p| match p {
394 Atomic::TClassString(None) | Atomic::TString => true,
395 Atomic::TClassString(Some(param_cls)) => {
396 arg_cls == param_cls
397 || ea
398 .codebase
399 .extends_or_implements(arg_cls.as_ref(), param_cls.as_ref())
400 }
401 _ => false,
402 });
403 }
404 Atomic::TNull => {
405 return param.types.iter().any(|p| matches!(p, Atomic::TNull));
406 }
407 Atomic::TFalse => {
408 return param
409 .types
410 .iter()
411 .any(|p| matches!(p, Atomic::TFalse | Atomic::TBool));
412 }
413 _ => return false,
414 };
415
416 if param
417 .types
418 .iter()
419 .any(|p| matches!(p, Atomic::TCallable { .. }))
420 {
421 let resolved_arg = ea.codebase.resolve_class_name(&ea.file, arg_fqcn.as_ref());
422 if ea.codebase.get_method(&resolved_arg, "__invoke").is_some()
423 || ea
424 .codebase
425 .get_method(arg_fqcn.as_ref(), "__invoke")
426 .is_some()
427 {
428 return true;
429 }
430 }
431
432 param.types.iter().any(|p_atomic| {
433 let param_fqcn: &Arc<str> = match p_atomic {
434 Atomic::TNamedObject { fqcn, .. } => fqcn,
435 Atomic::TSelf { fqcn } => fqcn,
436 Atomic::TStaticObject { fqcn } => fqcn,
437 Atomic::TParent { fqcn } => fqcn,
438 _ => return false,
439 };
440 let resolved_param = ea
441 .codebase
442 .resolve_class_name(&ea.file, param_fqcn.as_ref());
443 let resolved_arg = ea.codebase.resolve_class_name(&ea.file, arg_fqcn.as_ref());
444
445 let is_same_class = resolved_param == resolved_arg
446 || arg_fqcn.as_ref() == resolved_param.as_str()
447 || resolved_arg == param_fqcn.as_ref();
448
449 if is_same_class {
450 let arg_type_params = match a_atomic {
451 Atomic::TNamedObject { type_params, .. } => type_params.as_slice(),
452 _ => &[],
453 };
454 let param_type_params = match p_atomic {
455 Atomic::TNamedObject { type_params, .. } => type_params.as_slice(),
456 _ => &[],
457 };
458 if !arg_type_params.is_empty() || !param_type_params.is_empty() {
459 let class_tps = ea.codebase.get_class_template_params(&resolved_param);
460 return generic_type_params_compatible(
461 arg_type_params,
462 param_type_params,
463 &class_tps,
464 ea,
465 );
466 }
467 return true;
468 }
469
470 let arg_extends_param = ea
471 .codebase
472 .extends_or_implements(arg_fqcn.as_ref(), &resolved_param)
473 || ea
474 .codebase
475 .extends_or_implements(arg_fqcn.as_ref(), param_fqcn.as_ref())
476 || ea
477 .codebase
478 .extends_or_implements(&resolved_arg, &resolved_param);
479
480 if arg_extends_param {
481 let param_type_params = match p_atomic {
482 Atomic::TNamedObject { type_params, .. } => type_params.as_slice(),
483 _ => &[],
484 };
485 if !param_type_params.is_empty() {
486 let ancestor_args =
487 generic_ancestor_type_args(arg_fqcn.as_ref(), &resolved_param, ea)
488 .or_else(|| {
489 generic_ancestor_type_args(&resolved_arg, &resolved_param, ea)
490 })
491 .or_else(|| {
492 generic_ancestor_type_args(
493 arg_fqcn.as_ref(),
494 param_fqcn.as_ref(),
495 ea,
496 )
497 })
498 .or_else(|| {
499 generic_ancestor_type_args(&resolved_arg, param_fqcn.as_ref(), ea)
500 });
501 if let Some(arg_as_param_params) = ancestor_args {
502 let class_tps = ea.codebase.get_class_template_params(&resolved_param);
503 return generic_type_params_compatible(
504 &arg_as_param_params,
505 param_type_params,
506 &class_tps,
507 ea,
508 );
509 }
510 }
511 return true;
512 }
513
514 if ea
515 .codebase
516 .extends_or_implements(param_fqcn.as_ref(), &resolved_arg)
517 || ea
518 .codebase
519 .extends_or_implements(param_fqcn.as_ref(), arg_fqcn.as_ref())
520 || ea
521 .codebase
522 .extends_or_implements(&resolved_param, &resolved_arg)
523 {
524 let param_type_params = match p_atomic {
525 Atomic::TNamedObject { type_params, .. } => type_params.as_slice(),
526 _ => &[],
527 };
528 if param_type_params.is_empty() {
529 return true;
530 }
531 }
532
533 if !arg_fqcn.contains('\\') && !ea.codebase.type_exists(&resolved_arg) {
534 for entry in ea.codebase.classes.iter() {
535 if entry.value().short_name.as_ref() == arg_fqcn.as_ref() {
536 let actual_fqcn = entry.key().clone();
537 if ea
538 .codebase
539 .extends_or_implements(actual_fqcn.as_ref(), &resolved_param)
540 || ea
541 .codebase
542 .extends_or_implements(actual_fqcn.as_ref(), param_fqcn.as_ref())
543 {
544 return true;
545 }
546 }
547 }
548 }
549
550 let iface_key = if ea.codebase.interfaces.contains_key(arg_fqcn.as_ref()) {
551 Some(arg_fqcn.as_ref())
552 } else if ea.codebase.interfaces.contains_key(resolved_arg.as_str()) {
553 Some(resolved_arg.as_str())
554 } else {
555 None
556 };
557 if let Some(iface_fqcn) = iface_key {
558 let compatible = ea.codebase.classes.iter().any(|entry| {
559 let cls = entry.value();
560 cls.all_parents.iter().any(|p| p.as_ref() == iface_fqcn)
561 && (ea
562 .codebase
563 .extends_or_implements(entry.key().as_ref(), param_fqcn.as_ref())
564 || ea
565 .codebase
566 .extends_or_implements(entry.key().as_ref(), &resolved_param))
567 });
568 if compatible {
569 return true;
570 }
571 }
572
573 if arg_fqcn.contains('\\')
574 && !ea.codebase.type_exists(arg_fqcn.as_ref())
575 && !ea.codebase.type_exists(&resolved_arg)
576 {
577 return true;
578 }
579
580 if param_fqcn.contains('\\')
581 && !ea.codebase.type_exists(param_fqcn.as_ref())
582 && !ea.codebase.type_exists(&resolved_param)
583 {
584 return true;
585 }
586
587 false
588 })
589 })
590}
591
592fn strict_named_object_subtype(arg: &Union, param: &Union, ea: &ExpressionAnalyzer<'_>) -> bool {
594 arg.types.iter().all(|a_atomic| {
595 let arg_fqcn: &Arc<str> = match a_atomic {
596 Atomic::TNamedObject { fqcn, .. } => fqcn,
597 Atomic::TNever => return true,
598 _ => return false,
599 };
600 param.types.iter().any(|p_atomic| {
601 let param_fqcn: &Arc<str> = match p_atomic {
602 Atomic::TNamedObject { fqcn, .. } => fqcn,
603 _ => return false,
604 };
605 let resolved_param = ea
606 .codebase
607 .resolve_class_name(&ea.file, param_fqcn.as_ref());
608 let resolved_arg = ea.codebase.resolve_class_name(&ea.file, arg_fqcn.as_ref());
609 resolved_param == resolved_arg
610 || arg_fqcn.as_ref() == resolved_param.as_str()
611 || resolved_arg == param_fqcn.as_ref()
612 || ea
613 .codebase
614 .extends_or_implements(arg_fqcn.as_ref(), &resolved_param)
615 || ea
616 .codebase
617 .extends_or_implements(arg_fqcn.as_ref(), param_fqcn.as_ref())
618 || ea
619 .codebase
620 .extends_or_implements(&resolved_arg, &resolved_param)
621 })
622 })
623}
624
625fn generic_type_params_compatible(
627 arg_params: &[Union],
628 param_params: &[Union],
629 template_params: &[mir_codebase::storage::TemplateParam],
630 ea: &ExpressionAnalyzer<'_>,
631) -> bool {
632 if arg_params.len() != param_params.len() {
633 return true;
634 }
635 if arg_params.is_empty() {
636 return true;
637 }
638
639 for (i, (arg_p, param_p)) in arg_params.iter().zip(param_params.iter()).enumerate() {
640 let variance = template_params
641 .get(i)
642 .map(|tp| tp.variance)
643 .unwrap_or(mir_types::Variance::Invariant);
644
645 let compatible = match variance {
646 mir_types::Variance::Covariant => {
647 arg_p.is_subtype_of_simple(param_p)
648 || param_p.is_mixed()
649 || arg_p.is_mixed()
650 || strict_named_object_subtype(arg_p, param_p, ea)
651 }
652 mir_types::Variance::Contravariant => {
653 param_p.is_subtype_of_simple(arg_p)
654 || arg_p.is_mixed()
655 || param_p.is_mixed()
656 || strict_named_object_subtype(param_p, arg_p, ea)
657 }
658 mir_types::Variance::Invariant => {
659 arg_p == param_p
660 || arg_p.is_mixed()
661 || param_p.is_mixed()
662 || (arg_p.is_subtype_of_simple(param_p) && param_p.is_subtype_of_simple(arg_p))
663 }
664 };
665
666 if !compatible {
667 return false;
668 }
669 }
670
671 true
672}
673
674fn generic_ancestor_type_args(
675 child: &str,
676 ancestor: &str,
677 ea: &ExpressionAnalyzer<'_>,
678) -> Option<Vec<Union>> {
679 let mut seen = std::collections::HashSet::new();
680 generic_ancestor_type_args_inner(child, ancestor, ea, &mut seen)
681}
682
683fn generic_ancestor_type_args_inner(
684 child: &str,
685 ancestor: &str,
686 ea: &ExpressionAnalyzer<'_>,
687 seen: &mut std::collections::HashSet<String>,
688) -> Option<Vec<Union>> {
689 if child == ancestor {
690 return Some(vec![]);
691 }
692 if !seen.insert(child.to_string()) {
693 return None;
694 }
695
696 let cls = ea.codebase.classes.get(child)?;
697 let parent = cls.parent.clone();
698 let extends_type_args = cls.extends_type_args.clone();
699 let implements_type_args = cls.implements_type_args.clone();
700 drop(cls);
701
702 for (iface, args) in implements_type_args {
703 if iface.as_ref() == ancestor {
704 return Some(args);
705 }
706 }
707
708 let parent = parent?;
709 if parent.as_ref() == ancestor {
710 return Some(extends_type_args);
711 }
712
713 let parent_args = generic_ancestor_type_args_inner(parent.as_ref(), ancestor, ea, seen)?;
714 if parent_args.is_empty() {
715 return Some(parent_args);
716 }
717
718 let parent_template_params = ea.codebase.get_class_template_params(parent.as_ref());
719 let bindings: std::collections::HashMap<Arc<str>, Union> = parent_template_params
720 .iter()
721 .zip(extends_type_args.iter())
722 .map(|(tp, ty)| (tp.name.clone(), ty.clone()))
723 .collect();
724
725 Some(
726 parent_args
727 .into_iter()
728 .map(|ty| ty.substitute_templates(&bindings))
729 .collect(),
730 )
731}
732
733fn param_contains_template_or_unknown(param_ty: &Union, ea: &ExpressionAnalyzer<'_>) -> bool {
734 param_ty.types.iter().any(|atomic| match atomic {
735 Atomic::TTemplateParam { .. } => true,
736 Atomic::TNamedObject { fqcn, .. } => {
737 !fqcn.contains('\\') && !ea.codebase.type_exists(fqcn.as_ref())
738 }
739 Atomic::TClassString(Some(inner)) => {
740 !inner.contains('\\') && !ea.codebase.type_exists(inner.as_ref())
741 }
742 Atomic::TArray { key: _, value }
743 | Atomic::TList { value }
744 | Atomic::TNonEmptyArray { key: _, value }
745 | Atomic::TNonEmptyList { value } => value.types.iter().any(|v| match v {
746 Atomic::TTemplateParam { .. } => true,
747 Atomic::TNamedObject { fqcn, .. } => {
748 !fqcn.contains('\\') && !ea.codebase.type_exists(fqcn.as_ref())
749 }
750 _ => false,
751 }),
752 _ => false,
753 })
754}
755
756fn union_compatible(arg_ty: &Union, param_ty: &Union, ea: &ExpressionAnalyzer<'_>) -> bool {
757 arg_ty.types.iter().all(|av| {
758 let av_fqcn: &Arc<str> = match av {
759 Atomic::TNamedObject { fqcn, .. } => fqcn,
760 Atomic::TSelf { fqcn } | Atomic::TStaticObject { fqcn } | Atomic::TParent { fqcn } => {
761 fqcn
762 }
763 Atomic::TArray { value, .. }
764 | Atomic::TNonEmptyArray { value, .. }
765 | Atomic::TList { value }
766 | Atomic::TNonEmptyList { value } => {
767 return param_ty.types.iter().any(|pv| {
768 let pv_val: &Union = match pv {
769 Atomic::TArray { value, .. }
770 | Atomic::TNonEmptyArray { value, .. }
771 | Atomic::TList { value }
772 | Atomic::TNonEmptyList { value } => value,
773 _ => return false,
774 };
775 union_compatible(value, pv_val, ea)
776 });
777 }
778 Atomic::TKeyedArray { .. } => return true,
779 _ => return Union::single(av.clone()).is_subtype_of_simple(param_ty),
780 };
781
782 param_ty.types.iter().any(|pv| {
783 let pv_fqcn: &Arc<str> = match pv {
784 Atomic::TNamedObject { fqcn, .. } => fqcn,
785 Atomic::TSelf { fqcn }
786 | Atomic::TStaticObject { fqcn }
787 | Atomic::TParent { fqcn } => fqcn,
788 _ => return false,
789 };
790 if !pv_fqcn.contains('\\') && !ea.codebase.type_exists(pv_fqcn.as_ref()) {
791 return true;
792 }
793 let resolved_param = ea.codebase.resolve_class_name(&ea.file, pv_fqcn.as_ref());
794 let resolved_arg = ea.codebase.resolve_class_name(&ea.file, av_fqcn.as_ref());
795 resolved_param == resolved_arg
796 || ea
797 .codebase
798 .extends_or_implements(av_fqcn.as_ref(), &resolved_param)
799 || ea
800 .codebase
801 .extends_or_implements(&resolved_arg, &resolved_param)
802 || ea
803 .codebase
804 .extends_or_implements(pv_fqcn.as_ref(), &resolved_arg)
805 || ea
806 .codebase
807 .extends_or_implements(&resolved_param, &resolved_arg)
808 })
809 })
810}
811
812fn array_list_compatible(arg_ty: &Union, param_ty: &Union, ea: &ExpressionAnalyzer<'_>) -> bool {
813 arg_ty.types.iter().all(|a_atomic| {
814 let arg_value: &Union = match a_atomic {
815 Atomic::TArray { value, .. }
816 | Atomic::TNonEmptyArray { value, .. }
817 | Atomic::TList { value }
818 | Atomic::TNonEmptyList { value } => value,
819 Atomic::TKeyedArray { .. } => return true,
820 _ => return false,
821 };
822
823 param_ty.types.iter().any(|p_atomic| {
824 let param_value: &Union = match p_atomic {
825 Atomic::TArray { value, .. }
826 | Atomic::TNonEmptyArray { value, .. }
827 | Atomic::TList { value }
828 | Atomic::TNonEmptyList { value } => value,
829 _ => return false,
830 };
831
832 union_compatible(arg_value, param_value, ea)
833 })
834 })
835}