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::union::TUnion;
13use mago_reporting::Issue;
14use mago_span::HasSpan;
15use mago_span::Span;
16use mago_syntax::ast::Argument;
17use mago_syntax::ast::Expression;
18use mago_syntax::ast::PartialArgument;
19
20use crate::artifacts::AnalysisArtifacts;
21use crate::code::IssueCode;
22use crate::context::block::BlockContext;
23use crate::invocation::Invocation;
24use crate::invocation::InvocationArgument;
25use crate::invocation::InvocationArgumentsSource;
26
27pub struct ReportedIssue {
28 pub code: IssueCode,
29 pub issue: Issue,
30}
31
32pub struct ProviderContext<'a, 'b, 'c> {
33 pub(crate) codebase: &'a CodebaseMetadata,
34 pub(crate) artifacts: &'b AnalysisArtifacts,
35 pub(crate) block_context: &'c BlockContext<'a>,
36 pub(crate) reported_issues: RefCell<Vec<ReportedIssue>>,
37}
38
39impl<'a, 'b, 'c> ProviderContext<'a, 'b, 'c> {
40 pub(crate) fn new(
41 codebase: &'a CodebaseMetadata,
42 block_context: &'c BlockContext<'a>,
43 artifacts: &'b AnalysisArtifacts,
44 ) -> Self {
45 Self { codebase, artifacts, block_context, reported_issues: RefCell::new(Vec::new()) }
46 }
47
48 pub fn report(&self, code: IssueCode, issue: Issue) {
49 self.reported_issues.borrow_mut().push(ReportedIssue { code, issue });
50 }
51
52 pub(crate) fn take_issues(&self) -> Vec<ReportedIssue> {
53 std::mem::take(&mut *self.reported_issues.borrow_mut())
54 }
55
56 #[inline]
57 pub fn codebase(&self) -> &'a CodebaseMetadata {
58 self.codebase
59 }
60
61 #[inline]
62 pub fn get_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&TUnion> {
63 self.artifacts.get_expression_type(expr)
64 }
65
66 #[inline]
67 pub fn get_rc_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&Rc<TUnion>> {
68 self.artifacts.get_rc_expression_type(expr)
69 }
70
71 #[inline]
72 pub fn get_variable_type(&self, name: &str) -> Option<&Rc<TUnion>> {
73 self.block_context.locals.get(&atom(name))
74 }
75
76 #[inline]
77 pub fn scope(&self) -> &ScopeContext<'a> {
78 &self.block_context.scope
79 }
80
81 #[inline]
82 pub fn is_instance_of(&self, class: &str, parent: &str) -> bool {
83 self.codebase.is_instance_of(class, parent)
84 }
85
86 #[inline]
87 pub fn get_closure_metadata<'arena>(&self, expr: &Expression<'arena>) -> Option<&'a FunctionLikeMetadata> {
88 match expr {
89 Expression::ArrowFunction(arrow_fn) => {
90 let span = arrow_fn.span();
91 self.codebase.get_closure(&span.file_id, &span.start)
92 }
93 Expression::Closure(closure) => {
94 let span = closure.span();
95 self.codebase.get_closure(&span.file_id, &span.start)
96 }
97 _ => None,
98 }
99 }
100
101 #[inline]
102 pub fn get_class_like(&self, name: &Atom) -> Option<&ClassLikeMetadata> {
103 self.codebase.get_class_like(name)
104 }
105
106 #[inline]
107 pub fn current_class_name(&self) -> Option<Atom> {
108 self.block_context.scope.get_class_like_name()
109 }
110}
111
112pub struct HookContext<'ctx, 'a> {
117 pub(crate) codebase: &'ctx CodebaseMetadata,
118 pub(crate) block_context: &'a mut BlockContext<'ctx>,
119 pub(crate) artifacts: &'a mut AnalysisArtifacts,
120 pub(crate) reported_issues: RefCell<Vec<ReportedIssue>>,
121}
122
123impl<'ctx, 'a> HookContext<'ctx, 'a> {
124 pub(crate) fn new(
125 codebase: &'ctx CodebaseMetadata,
126 block_context: &'a mut BlockContext<'ctx>,
127 artifacts: &'a mut AnalysisArtifacts,
128 ) -> Self {
129 Self { codebase, artifacts, block_context, reported_issues: RefCell::new(Vec::new()) }
130 }
131
132 pub fn report(&self, code: IssueCode, issue: Issue) {
134 self.reported_issues.borrow_mut().push(ReportedIssue { code, issue });
135 }
136
137 pub(crate) fn take_issues(&self) -> Vec<ReportedIssue> {
138 std::mem::take(&mut *self.reported_issues.borrow_mut())
139 }
140
141 #[inline]
143 pub fn codebase(&self) -> &'ctx CodebaseMetadata {
144 self.codebase
145 }
146
147 #[inline]
149 pub fn get_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&TUnion> {
150 self.artifacts.get_expression_type(expr)
151 }
152
153 #[inline]
155 pub fn get_rc_expression_type<T: HasSpan>(&self, expr: &T) -> Option<&Rc<TUnion>> {
156 self.artifacts.get_rc_expression_type(expr)
157 }
158
159 #[inline]
161 pub fn get_variable_type(&self, name: &str) -> Option<&Rc<TUnion>> {
162 self.block_context.locals.get(&atom(name))
163 }
164
165 #[inline]
167 pub fn scope(&self) -> &ScopeContext<'ctx> {
168 &self.block_context.scope
169 }
170
171 #[inline]
173 pub fn is_instance_of(&self, class: &str, parent: &str) -> bool {
174 self.codebase.is_instance_of(class, parent)
175 }
176
177 #[inline]
179 pub fn get_closure_metadata<'arena>(&self, expr: &Expression<'arena>) -> Option<&'ctx FunctionLikeMetadata> {
180 match expr {
181 Expression::ArrowFunction(arrow_fn) => {
182 let span = arrow_fn.span();
183 self.codebase.get_closure(&span.file_id, &span.start)
184 }
185 Expression::Closure(closure) => {
186 let span = closure.span();
187 self.codebase.get_closure(&span.file_id, &span.start)
188 }
189 _ => None,
190 }
191 }
192
193 #[inline]
195 pub fn get_class_like(&self, name: &Atom) -> Option<&ClassLikeMetadata> {
196 self.codebase.get_class_like(name)
197 }
198
199 #[inline]
201 pub fn current_class_name(&self) -> Option<Atom> {
202 self.block_context.scope.get_class_like_name()
203 }
204
205 #[inline]
207 pub fn set_expression_type<T: HasSpan>(&mut self, expr: &T, ty: TUnion) {
208 self.artifacts.set_expression_type(expr, ty);
209 }
210
211 #[inline]
213 pub fn set_variable_type(&mut self, name: &str, ty: TUnion) {
214 self.block_context.locals.insert(atom(name), Rc::new(ty));
215 }
216
217 #[inline]
219 pub fn artifacts_mut(&mut self) -> &mut AnalysisArtifacts {
220 self.artifacts
221 }
222
223 #[inline]
225 pub fn artifacts(&self) -> &AnalysisArtifacts {
226 self.artifacts
227 }
228
229 #[inline]
231 pub fn block_context_mut(&mut self) -> &mut BlockContext<'ctx> {
232 self.block_context
233 }
234
235 #[inline]
237 pub fn block_context(&self) -> &BlockContext<'ctx> {
238 self.block_context
239 }
240}
241
242pub struct InvocationInfo<'ctx, 'ast, 'arena> {
243 pub(crate) invocation: &'ctx Invocation<'ctx, 'ast, 'arena>,
244}
245
246impl<'ctx, 'ast, 'arena> InvocationInfo<'ctx, 'ast, 'arena> {
247 pub(crate) fn new(invocation: &'ctx Invocation<'ctx, 'ast, 'arena>) -> Self {
248 Self { invocation }
249 }
250
251 #[inline]
252 #[must_use]
253 pub fn get_argument(&self, index: usize, names: &[&str]) -> Option<&'ast Expression<'arena>> {
254 get_argument(self.invocation.arguments_source, index, names)
255 }
256
257 #[inline]
258 #[must_use]
259 pub fn arguments(&self) -> Vec<InvocationArgument<'ast, 'arena>> {
260 self.invocation.arguments_source.get_arguments()
261 }
262
263 #[inline]
264 #[must_use]
265 pub fn argument_count(&self) -> usize {
266 self.invocation.arguments_source.get_arguments().len()
267 }
268
269 #[inline]
270 #[must_use]
271 pub fn has_no_arguments(&self) -> bool {
272 self.invocation.arguments_source.get_arguments().is_empty()
273 }
274
275 #[inline]
276 #[must_use]
277 pub fn span(&self) -> Span {
278 self.invocation.span
279 }
280
281 #[inline]
282 #[must_use]
283 pub fn inner(&self) -> &'ctx Invocation<'ctx, 'ast, 'arena> {
284 self.invocation
285 }
286
287 #[inline]
288 #[must_use]
289 pub fn function_name(&self) -> String {
290 self.invocation.target.guess_name()
291 }
292}
293
294impl HasSpan for InvocationInfo<'_, '_, '_> {
295 fn span(&self) -> Span {
296 self.invocation.span
297 }
298}
299
300fn get_argument<'ast, 'arena>(
301 call_arguments: InvocationArgumentsSource<'ast, 'arena>,
302 index: usize,
303 names: &[&str],
304) -> Option<&'ast Expression<'arena>> {
305 match call_arguments {
306 InvocationArgumentsSource::ArgumentList(argument_list) => {
307 if let Some(Argument::Positional(argument)) = argument_list.arguments.get(index) {
308 return Some(&argument.value);
309 }
310
311 for argument in &argument_list.arguments {
312 if let Argument::Named(named_argument) = argument
313 && names.contains(&named_argument.name.value)
314 {
315 return Some(&named_argument.value);
316 }
317 }
318
319 None
320 }
321 InvocationArgumentsSource::PartialArgumentList(partial_argument_list) => {
322 if let Some(PartialArgument::Positional(argument)) = partial_argument_list.arguments.get(index) {
323 return Some(&argument.value);
324 }
325
326 for argument in &partial_argument_list.arguments {
327 if let PartialArgument::Named(named_argument) = argument
328 && names.contains(&named_argument.name.value)
329 {
330 return Some(&named_argument.value);
331 }
332 }
333
334 None
335 }
336 InvocationArgumentsSource::PipeInput(pipe) => {
337 if index == 0 {
338 Some(pipe.input)
339 } else {
340 None
341 }
342 }
343 InvocationArgumentsSource::None(_) => None,
344 }
345}