Skip to main content

mir_analyzer/call/
static_call.rs

1use 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}