1use std::cell::RefCell;
4use std::rc::Rc;
5
6use mago_atom::Atom;
7use mago_atom::atom;
8use mago_codex::context::ScopeContext;
9use mago_codex::metadata::CodebaseMetadata;
10use mago_codex::metadata::class_like::ClassLikeMetadata;
11use mago_codex::metadata::function_like::FunctionLikeMetadata;
12use mago_codex::ttype::atomic::TAtomic;
13use mago_codex::ttype::atomic::scalar::TScalar;
14use mago_codex::ttype::atomic::scalar::string::TString;
15use mago_codex::ttype::atomic::scalar::string::TStringLiteral;
16use mago_codex::ttype::union::TUnion;
17use mago_reporting::Issue;
18use mago_span::HasSpan;
19use mago_span::Span;
20use mago_syntax::ast::Argument;
21use mago_syntax::ast::ClassLikeMemberSelector;
22use mago_syntax::ast::Expression;
23use mago_syntax::ast::PartialApplication;
24use mago_syntax::ast::PartialArgument;
25
26use crate::artifacts::AnalysisArtifacts;
27use crate::code::IssueCode;
28use crate::context::block::BlockContext;
29use crate::invocation::Invocation;
30use crate::invocation::InvocationArgument;
31use crate::invocation::InvocationArgumentsSource;
32
33pub struct ReportedIssue {
34 pub code: IssueCode,
35 pub issue: Issue,
36}
37
38pub struct ProviderContext<'a, 'b, 'c> {
39 pub(crate) codebase: &'a CodebaseMetadata,
40 pub(crate) artifacts: &'b AnalysisArtifacts,
41 pub(crate) block_context: &'c BlockContext<'a>,
42 pub(crate) reported_issues: RefCell<Vec<ReportedIssue>>,
43}
44
45impl<'a, 'b, 'c> ProviderContext<'a, 'b, 'c> {
46 pub(crate) fn new(
47 codebase: &'a CodebaseMetadata,
48 block_context: &'c BlockContext<'a>,
49 artifacts: &'b AnalysisArtifacts,
50 ) -> Self {
51 Self { codebase, artifacts, block_context, reported_issues: RefCell::new(Vec::new()) }
52 }
53
54 pub fn report(&self, code: IssueCode, issue: Issue) {
55 self.reported_issues.borrow_mut().push(ReportedIssue { code, issue });
56 }
57
58 pub(crate) fn take_issues(&self) -> Vec<ReportedIssue> {
59 std::mem::take(&mut *self.reported_issues.borrow_mut())
60 }
61
62 #[inline]
63 pub fn codebase(&self) -> &'a CodebaseMetadata {
64 self.codebase
65 }
66
67 #[inline]
68 pub fn get_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&TUnion> {
69 self.artifacts.get_expression_type(expr)
70 }
71
72 #[inline]
73 pub fn get_rc_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&Rc<TUnion>> {
74 self.artifacts.get_rc_expression_type(expr)
75 }
76
77 #[inline]
78 pub fn get_variable_type(&self, name: &str) -> Option<&Rc<TUnion>> {
79 self.block_context.locals.get(&atom(name))
80 }
81
82 #[inline]
83 pub fn scope(&self) -> &ScopeContext<'a> {
84 &self.block_context.scope
85 }
86
87 #[inline]
88 pub fn is_instance_of(&self, class: &str, parent: &str) -> bool {
89 self.codebase.is_instance_of(class, parent)
90 }
91
92 #[inline]
93 pub fn get_closure_metadata<'arena>(&self, expr: &Expression<'arena>) -> Option<&'a FunctionLikeMetadata> {
94 match expr {
95 Expression::ArrowFunction(arrow_fn) => {
96 let span = arrow_fn.span();
97 self.codebase.get_closure(&span.file_id, &span.start)
98 }
99 Expression::Closure(closure) => {
100 let span = closure.span();
101 self.codebase.get_closure(&span.file_id, &span.start)
102 }
103 _ => None,
104 }
105 }
106
107 #[inline]
112 pub fn get_callable_metadata<'arena>(&self, expr: &Expression<'arena>) -> Option<&'a FunctionLikeMetadata> {
113 match expr {
114 Expression::ArrowFunction(arrow_fn) => {
115 let span = arrow_fn.span();
116
117 self.codebase.get_closure(&span.file_id, &span.start)
118 }
119 Expression::Closure(closure) => {
120 let span = closure.span();
121
122 self.codebase.get_closure(&span.file_id, &span.start)
123 }
124 Expression::PartialApplication(partial) => match partial {
125 PartialApplication::Function(func_partial) => {
126 if !func_partial.argument_list.is_first_class_callable() {
127 return None;
128 }
129
130 if let Expression::Identifier(identifier) = func_partial.function {
131 self.codebase.get_function(identifier.value())
132 } else {
133 None
134 }
135 }
136 PartialApplication::StaticMethod(static_partial) => {
137 if !static_partial.argument_list.is_first_class_callable() {
138 return None;
139 }
140
141 if let Expression::Identifier(class_id) = static_partial.class {
142 if let ClassLikeMemberSelector::Identifier(method_id) = &static_partial.method {
143 self.codebase.get_method(class_id.value(), method_id.value)
144 } else {
145 None
146 }
147 } else {
148 None
149 }
150 }
151 PartialApplication::Method(_) => None,
152 },
153 _ => {
154 let expr_type = self.get_rc_expression_type(expr)?;
155 if !expr_type.is_single() {
156 return None;
157 }
158
159 match expr_type.get_single() {
160 TAtomic::Callable(first_callable) => {
161 if let Some(identifier) = first_callable.get_alias() {
162 self.codebase.get_function_like(identifier)
163 } else {
164 None
165 }
166 }
167 TAtomic::Scalar(TScalar::String(TString {
168 literal: Some(TStringLiteral::Value(literal_string)),
169 ..
170 })) => {
171 if let Some((class_like, method_name)) = literal_string.split_once("::") {
172 self.codebase.get_method(class_like, method_name)
173 } else {
174 self.codebase.get_function(literal_string)
175 }
176 }
177 _ => None,
178 }
179 }
180 }
181 }
182
183 #[inline]
184 pub fn get_class_like(&self, name: &Atom) -> Option<&ClassLikeMetadata> {
185 self.codebase.get_class_like(name)
186 }
187
188 #[inline]
189 pub fn current_class_name(&self) -> Option<Atom> {
190 self.block_context.scope.get_class_like_name()
191 }
192}
193
194pub struct HookContext<'ctx, 'a> {
199 pub(crate) codebase: &'ctx CodebaseMetadata,
200 pub(crate) block_context: &'a mut BlockContext<'ctx>,
201 pub(crate) artifacts: &'a mut AnalysisArtifacts,
202 pub(crate) reported_issues: RefCell<Vec<ReportedIssue>>,
203}
204
205impl<'ctx, 'a> HookContext<'ctx, 'a> {
206 pub(crate) fn new(
207 codebase: &'ctx CodebaseMetadata,
208 block_context: &'a mut BlockContext<'ctx>,
209 artifacts: &'a mut AnalysisArtifacts,
210 ) -> Self {
211 Self { codebase, artifacts, block_context, reported_issues: RefCell::new(Vec::new()) }
212 }
213
214 pub fn report(&self, code: IssueCode, issue: Issue) {
216 self.reported_issues.borrow_mut().push(ReportedIssue { code, issue });
217 }
218
219 pub(crate) fn take_issues(&self) -> Vec<ReportedIssue> {
220 std::mem::take(&mut *self.reported_issues.borrow_mut())
221 }
222
223 #[inline]
225 pub fn codebase(&self) -> &'ctx CodebaseMetadata {
226 self.codebase
227 }
228
229 #[inline]
231 pub fn get_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&TUnion> {
232 self.artifacts.get_expression_type(expr)
233 }
234
235 #[inline]
237 pub fn get_rc_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&Rc<TUnion>> {
238 self.artifacts.get_rc_expression_type(expr)
239 }
240
241 #[inline]
243 pub fn get_variable_type(&self, name: &str) -> Option<&Rc<TUnion>> {
244 self.block_context.locals.get(&atom(name))
245 }
246
247 #[inline]
249 pub fn scope(&self) -> &ScopeContext<'ctx> {
250 &self.block_context.scope
251 }
252
253 #[inline]
255 pub fn is_instance_of(&self, class: &str, parent: &str) -> bool {
256 self.codebase.is_instance_of(class, parent)
257 }
258
259 #[inline]
261 pub fn get_closure_metadata<'arena>(&self, expr: &Expression<'arena>) -> Option<&'ctx FunctionLikeMetadata> {
262 match expr {
263 Expression::ArrowFunction(arrow_fn) => {
264 let span = arrow_fn.span();
265 self.codebase.get_closure(&span.file_id, &span.start)
266 }
267 Expression::Closure(closure) => {
268 let span = closure.span();
269 self.codebase.get_closure(&span.file_id, &span.start)
270 }
271 _ => None,
272 }
273 }
274
275 #[inline]
277 pub fn get_class_like(&self, name: &Atom) -> Option<&ClassLikeMetadata> {
278 self.codebase.get_class_like(name)
279 }
280
281 #[inline]
283 pub fn current_class_name(&self) -> Option<Atom> {
284 self.block_context.scope.get_class_like_name()
285 }
286
287 #[inline]
289 pub fn set_expression_type<T: HasSpan>(&mut self, expr: &T, ty: TUnion) {
290 self.artifacts.set_expression_type(expr, ty);
291 }
292
293 #[inline]
295 pub fn set_variable_type(&mut self, name: &str, ty: TUnion) {
296 self.block_context.locals.insert(atom(name), Rc::new(ty));
297 }
298
299 #[inline]
301 pub fn artifacts_mut(&mut self) -> &mut AnalysisArtifacts {
302 self.artifacts
303 }
304
305 #[inline]
307 pub fn artifacts(&self) -> &AnalysisArtifacts {
308 self.artifacts
309 }
310
311 #[inline]
313 pub fn block_context_mut(&mut self) -> &mut BlockContext<'ctx> {
314 self.block_context
315 }
316
317 #[inline]
319 pub fn block_context(&self) -> &BlockContext<'ctx> {
320 self.block_context
321 }
322}
323
324pub struct InvocationInfo<'ctx, 'ast, 'arena> {
325 pub(crate) invocation: &'ctx Invocation<'ctx, 'ast, 'arena>,
326}
327
328impl<'ctx, 'ast, 'arena> InvocationInfo<'ctx, 'ast, 'arena> {
329 pub(crate) fn new(invocation: &'ctx Invocation<'ctx, 'ast, 'arena>) -> Self {
330 Self { invocation }
331 }
332
333 #[inline]
334 #[must_use]
335 pub fn get_argument(&self, index: usize, names: &[&str]) -> Option<&'ast Expression<'arena>> {
336 get_argument(self.invocation.arguments_source, index, names)
337 }
338
339 #[inline]
340 #[must_use]
341 pub fn arguments(&self) -> Vec<InvocationArgument<'ast, 'arena>> {
342 self.invocation.arguments_source.get_arguments()
343 }
344
345 #[inline]
346 #[must_use]
347 pub fn argument_count(&self) -> usize {
348 self.invocation.arguments_source.get_arguments().len()
349 }
350
351 #[inline]
352 #[must_use]
353 pub fn has_no_arguments(&self) -> bool {
354 self.invocation.arguments_source.get_arguments().is_empty()
355 }
356
357 #[inline]
358 #[must_use]
359 pub fn span(&self) -> Span {
360 self.invocation.span
361 }
362
363 #[inline]
364 #[must_use]
365 pub fn inner(&self) -> &'ctx Invocation<'ctx, 'ast, 'arena> {
366 self.invocation
367 }
368
369 #[inline]
370 #[must_use]
371 pub fn function_name(&self) -> String {
372 self.invocation.target.guess_name()
373 }
374}
375
376impl HasSpan for InvocationInfo<'_, '_, '_> {
377 fn span(&self) -> Span {
378 self.invocation.span
379 }
380}
381
382fn get_argument<'ast, 'arena>(
383 call_arguments: InvocationArgumentsSource<'ast, 'arena>,
384 index: usize,
385 names: &[&str],
386) -> Option<&'ast Expression<'arena>> {
387 match call_arguments {
388 InvocationArgumentsSource::ArgumentList(argument_list) => {
389 if let Some(Argument::Positional(argument)) = argument_list.arguments.get(index) {
390 return Some(&argument.value);
391 }
392
393 for argument in &argument_list.arguments {
394 if let Argument::Named(named_argument) = argument
395 && names.contains(&named_argument.name.value)
396 {
397 return Some(&named_argument.value);
398 }
399 }
400
401 None
402 }
403 InvocationArgumentsSource::PartialArgumentList(partial_argument_list) => {
404 if let Some(PartialArgument::Positional(argument)) = partial_argument_list.arguments.get(index) {
405 return Some(&argument.value);
406 }
407
408 for argument in &partial_argument_list.arguments {
409 if let PartialArgument::Named(named_argument) = argument
410 && names.contains(&named_argument.name.value)
411 {
412 return Some(&named_argument.value);
413 }
414 }
415
416 None
417 }
418 InvocationArgumentsSource::PipeInput(pipe) => {
419 if index == 0 {
420 Some(pipe.input)
421 } else {
422 None
423 }
424 }
425 InvocationArgumentsSource::None(_) => None,
426 }
427}