mir_analyzer/call/
static_call.rs1use std::sync::Arc;
2
3use php_ast::ast::{ExprKind, StaticDynMethodCallExpr, StaticMethodCallExpr};
4use php_ast::Span;
5
6use mir_issues::{IssueKind, Severity};
7use mir_types::Union;
8
9use crate::context::Context;
10use crate::expr::ExpressionAnalyzer;
11use crate::symbol::SymbolKind;
12
13use super::args::{
14 check_args, expr_can_be_passed_by_reference, spread_element_type, substitute_static_in_return,
15 CheckArgsParams,
16};
17use super::method::resolve_method_from_db;
18use super::CallAnalyzer;
19
20impl CallAnalyzer {
21 pub fn analyze_static_method_call<'a, 'arena, 'src>(
22 ea: &mut ExpressionAnalyzer<'a>,
23 call: &StaticMethodCallExpr<'arena, 'src>,
24 ctx: &mut Context,
25 span: Span,
26 ) -> Union {
27 let method_name = match &call.method.kind {
28 ExprKind::Identifier(name) => name.as_str(),
29 _ => return Union::mixed(),
30 };
31
32 let fqcn = match &call.class.kind {
33 ExprKind::Identifier(name) => {
34 crate::db::resolve_name_via_db(ea.db, &ea.file, name.as_ref())
35 }
36 _ => return Union::mixed(),
37 };
38
39 let fqcn = resolve_static_class(&fqcn, ctx);
40
41 let arg_types: Vec<Union> = call
42 .args
43 .iter()
44 .map(|arg| {
45 let ty = ea.analyze(&arg.value, ctx);
46 if arg.unpack {
47 spread_element_type(&ty)
48 } else {
49 ty
50 }
51 })
52 .collect();
53 let arg_spans: Vec<Span> = call.args.iter().map(|a| a.span).collect();
54
55 let fqcn_arc: Arc<str> = Arc::from(fqcn.as_str());
56 let method_name_lower = method_name.to_lowercase();
57
58 let resolved = resolve_method_from_db(ea, &fqcn_arc, &method_name_lower);
59
60 if let Some(resolved) = resolved {
61 if !ea.inference_only {
62 let (line, col_start, col_end) = ea.span_to_ref_loc(call.method.span);
63 ea.db.record_reference_location(crate::db::RefLoc {
64 symbol_key: Arc::from(format!("{}::{}", &fqcn, method_name.to_lowercase())),
65 file: ea.file.clone(),
66 line,
67 col_start,
68 col_end,
69 });
70 }
71 if let Some(msg) = resolved.deprecated.clone() {
72 ea.emit(
73 IssueKind::DeprecatedMethodCall {
74 class: fqcn.clone(),
75 method: method_name.to_string(),
76 message: Some(msg).filter(|m| !m.is_empty()),
77 },
78 Severity::Info,
79 span,
80 );
81 }
82 let arg_names: Vec<Option<String>> = call
83 .args
84 .iter()
85 .map(|a| a.name.as_ref().map(|n| n.to_string_repr().into_owned()))
86 .collect();
87 let arg_can_be_byref: Vec<bool> = call
88 .args
89 .iter()
90 .map(|a| expr_can_be_passed_by_reference(&a.value))
91 .collect();
92 check_args(
93 ea,
94 CheckArgsParams {
95 fn_name: method_name,
96 params: &resolved.params,
97 arg_types: &arg_types,
98 arg_spans: &arg_spans,
99 arg_names: &arg_names,
100 arg_can_be_byref: &arg_can_be_byref,
101 call_span: span,
102 has_spread: call.args.iter().any(|a| a.unpack),
103 },
104 );
105 let ret_raw = resolved.return_ty_raw;
106 let ret = substitute_static_in_return(ret_raw, &fqcn_arc);
107 ea.record_symbol(
108 call.method.span,
109 SymbolKind::StaticCall {
110 class: fqcn_arc,
111 method: Arc::from(method_name),
112 },
113 ret.clone(),
114 );
115 ret
116 } else if crate::db::type_exists_via_db(ea.db, &fqcn)
117 && !crate::db::has_unknown_ancestor_via_db(ea.db, &fqcn)
118 {
119 let (is_interface, is_abstract) = crate::db::class_kind_via_db(ea.db, &fqcn)
120 .map(|k| (k.is_interface, k.is_abstract))
121 .unwrap_or((false, false));
122 if is_interface
123 || is_abstract
124 || crate::db::method_exists_via_db(ea.db, &fqcn, "__callStatic")
125 {
126 Union::mixed()
127 } else {
128 ea.emit(
129 IssueKind::UndefinedMethod {
130 class: fqcn,
131 method: method_name.to_string(),
132 },
133 Severity::Error,
134 span,
135 );
136 Union::mixed()
137 }
138 } else if !crate::db::type_exists_via_db(ea.db, &fqcn)
139 && !matches!(fqcn.as_str(), "self" | "static" | "parent")
140 {
141 ea.emit(
142 IssueKind::UndefinedClass { name: fqcn },
143 Severity::Error,
144 call.class.span,
145 );
146 Union::mixed()
147 } else {
148 Union::mixed()
149 }
150 }
151
152 pub fn analyze_static_dyn_method_call<'a, 'arena, 'src>(
153 ea: &mut ExpressionAnalyzer<'a>,
154 call: &StaticDynMethodCallExpr<'arena, 'src>,
155 ctx: &mut Context,
156 ) -> Union {
157 for arg in call.args.iter() {
158 ea.analyze(&arg.value, ctx);
159 }
160 Union::mixed()
161 }
162}
163
164fn resolve_static_class(name: &str, ctx: &Context) -> String {
165 match name.to_lowercase().as_str() {
166 "self" => ctx.self_fqcn.as_deref().unwrap_or("self").to_string(),
167 "parent" => ctx.parent_fqcn.as_deref().unwrap_or("parent").to_string(),
168 "static" => ctx
169 .static_fqcn
170 .as_deref()
171 .unwrap_or(ctx.self_fqcn.as_deref().unwrap_or("static"))
172 .to_string(),
173 _ => name.to_string(),
174 }
175}