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::{check_args, spread_element_type, substitute_static_in_return, CheckArgsParams};
14use super::CallAnalyzer;
15
16impl CallAnalyzer {
17    pub fn analyze_static_method_call<'a, 'arena, 'src>(
18        ea: &mut ExpressionAnalyzer<'a>,
19        call: &StaticMethodCallExpr<'arena, 'src>,
20        ctx: &mut Context,
21        span: Span,
22    ) -> Union {
23        let method_name = match &call.method.kind {
24            ExprKind::Identifier(name) => name.as_str(),
25            _ => return Union::mixed(),
26        };
27
28        let fqcn = match &call.class.kind {
29            ExprKind::Identifier(name) => ea.codebase.resolve_class_name(&ea.file, name.as_ref()),
30            _ => return Union::mixed(),
31        };
32
33        let fqcn = resolve_static_class(&fqcn, ctx);
34
35        let arg_types: Vec<Union> = call
36            .args
37            .iter()
38            .map(|arg| {
39                let ty = ea.analyze(&arg.value, ctx);
40                if arg.unpack {
41                    spread_element_type(&ty)
42                } else {
43                    ty
44                }
45            })
46            .collect();
47        let arg_spans: Vec<Span> = call.args.iter().map(|a| a.span).collect();
48
49        if let Some(method) = ea.codebase.get_method(&fqcn, method_name) {
50            let method_span = call.method.span;
51            ea.codebase.mark_method_referenced_at(
52                &fqcn,
53                method_name,
54                ea.file.clone(),
55                method_span.start,
56                method_span.end,
57            );
58            if let Some(msg) = method.deprecated.clone() {
59                ea.emit(
60                    IssueKind::DeprecatedMethodCall {
61                        class: fqcn.clone(),
62                        method: method_name.to_string(),
63                        message: Some(msg).filter(|m| !m.is_empty()),
64                    },
65                    Severity::Info,
66                    span,
67                );
68            }
69            let arg_names: Vec<Option<String>> = call
70                .args
71                .iter()
72                .map(|a| a.name.as_ref().map(|n| n.to_string_repr().into_owned()))
73                .collect();
74            check_args(
75                ea,
76                CheckArgsParams {
77                    fn_name: method_name,
78                    params: &method.params,
79                    arg_types: &arg_types,
80                    arg_spans: &arg_spans,
81                    arg_names: &arg_names,
82                    call_span: span,
83                    has_spread: call.args.iter().any(|a| a.unpack),
84                },
85            );
86            let ret_raw = method
87                .effective_return_type()
88                .cloned()
89                .unwrap_or_else(Union::mixed);
90            let fqcn_arc: Arc<str> = Arc::from(fqcn.as_str());
91            let ret = substitute_static_in_return(ret_raw, &fqcn_arc);
92            ea.record_symbol(
93                method_span,
94                SymbolKind::StaticCall {
95                    class: fqcn_arc,
96                    method: Arc::from(method_name),
97                },
98                ret.clone(),
99            );
100            ret
101        } else if ea.codebase.type_exists(&fqcn) && !ea.codebase.has_unknown_ancestor(&fqcn) {
102            let is_interface = ea.codebase.interfaces.contains_key(fqcn.as_str());
103            let is_abstract = ea.codebase.is_abstract_class(&fqcn);
104            if is_interface || is_abstract || ea.codebase.get_method(&fqcn, "__call").is_some() {
105                Union::mixed()
106            } else {
107                ea.emit(
108                    IssueKind::UndefinedMethod {
109                        class: fqcn,
110                        method: method_name.to_string(),
111                    },
112                    Severity::Error,
113                    span,
114                );
115                Union::mixed()
116            }
117        } else if !ea.codebase.type_exists(&fqcn)
118            && !matches!(fqcn.as_str(), "self" | "static" | "parent")
119        {
120            ea.emit(
121                IssueKind::UndefinedClass { name: fqcn },
122                Severity::Error,
123                call.class.span,
124            );
125            Union::mixed()
126        } else {
127            Union::mixed()
128        }
129    }
130
131    pub fn analyze_static_dyn_method_call<'a, 'arena, 'src>(
132        ea: &mut ExpressionAnalyzer<'a>,
133        call: &StaticDynMethodCallExpr<'arena, 'src>,
134        ctx: &mut Context,
135    ) -> Union {
136        for arg in call.args.iter() {
137            ea.analyze(&arg.value, ctx);
138        }
139        Union::mixed()
140    }
141}
142
143fn resolve_static_class(name: &str, ctx: &Context) -> String {
144    match name.to_lowercase().as_str() {
145        "self" => ctx.self_fqcn.as_deref().unwrap_or("self").to_string(),
146        "parent" => ctx.parent_fqcn.as_deref().unwrap_or("parent").to_string(),
147        "static" => ctx
148            .static_fqcn
149            .as_deref()
150            .unwrap_or(ctx.self_fqcn.as_deref().unwrap_or("static"))
151            .to_string(),
152        _ => name.to_string(),
153    }
154}