Skip to content

Commit 975ef99

Browse files
arthaudmeta-codesync[bot]
authored andcommitted
Export decorated target call graphs
Summary: In pysa, we create artificial functions called "decorated targets" that correspond to the application of decorators on a function. For instance: ``` foo(1) bar def baz(): ... ``` `baz` will have a decorated targets: ``` def baz@decorated(): return foo(1)(bar(baz)) ``` We need the call graph of this function from pyrefly. This diff implements that logic: when computing call graphs, expressions in decorators are exported in a special `FunctionDecoratedTarget` function id. Reviewed By: tianhan0 Differential Revision: D85569064 fbshipit-source-id: e5f27d1c63fae8136c6110da4a775e42c0063580
1 parent 284a4ff commit 975ef99

File tree

5 files changed

+269
-119
lines changed

5 files changed

+269
-119
lines changed

pyrefly/lib/report/pysa/ast_visitor.rs

Lines changed: 202 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -76,84 +76,147 @@ pub struct Scopes {
7676
stack: Vec<Scope>,
7777
}
7878

79+
pub enum ExportFunctionDecorators {
80+
InDecoratedFunction,
81+
InParentScope,
82+
InDecoratedTarget,
83+
Ignore,
84+
}
85+
86+
pub enum ExportClassDecorators {
87+
InDecoratedClassTopLevel,
88+
InParentScope,
89+
Ignore,
90+
}
91+
92+
pub enum ExportDefaultArguments {
93+
InFunction,
94+
InParentScope,
95+
Ignore,
96+
}
97+
98+
pub struct ScopeExportedFunctionFlags {
99+
pub include_top_level: bool,
100+
pub include_class_top_level: bool,
101+
pub include_function_decorators: ExportFunctionDecorators,
102+
pub include_class_decorators: ExportClassDecorators,
103+
pub include_default_arguments: ExportDefaultArguments,
104+
}
105+
79106
impl Scopes {
80107
pub fn current_exported_function(
81108
&self,
82109
module_id: ModuleId,
83110
module_name: ModuleName,
84-
include_top_level: bool,
85-
include_class_top_level: bool,
86-
include_decorators_in_decorated_definition: bool,
87-
include_default_arguments_in_function: bool,
111+
flags: ScopeExportedFunctionFlags,
88112
) -> Option<FunctionRef> {
89-
let mut iterator = self.stack.iter().rev();
90-
loop {
91-
match iterator.next().unwrap() {
92-
Scope::TopLevel => {
93-
if include_top_level {
94-
return Some(FunctionRef {
95-
module_id,
96-
module_name,
97-
function_id: FunctionId::ModuleTopLevel,
98-
function_name: Name::from("$toplevel"),
99-
});
100-
} else {
101-
return None;
102-
}
113+
Self::current_exported_function_impl(self.stack.iter().rev(), module_id, module_name, flags)
114+
}
115+
116+
fn current_exported_function_impl<'a>(
117+
mut iterator: impl Iterator<Item = &'a Scope>,
118+
module_id: ModuleId,
119+
module_name: ModuleName,
120+
flags: ScopeExportedFunctionFlags,
121+
) -> Option<FunctionRef> {
122+
match iterator.next().unwrap() {
123+
Scope::TopLevel => {
124+
if flags.include_top_level {
125+
Some(FunctionRef {
126+
module_id,
127+
module_name,
128+
function_id: FunctionId::ModuleTopLevel,
129+
function_name: Name::from("$toplevel"),
130+
})
131+
} else {
132+
None
103133
}
104-
Scope::ExportedFunction {
105-
function_id,
106-
function_name,
107-
..
108-
} => {
109-
return Some(FunctionRef {
134+
}
135+
Scope::ExportedFunction {
136+
function_id,
137+
function_name,
138+
..
139+
} => Some(FunctionRef {
140+
module_id,
141+
module_name,
142+
function_id: function_id.clone(),
143+
function_name: function_name.clone(),
144+
}),
145+
Scope::NonExportedFunction { .. } => None,
146+
Scope::ExportedClass { class_id, .. } => {
147+
if flags.include_class_top_level {
148+
Some(FunctionRef {
110149
module_id,
111150
module_name,
112-
function_id: function_id.clone(),
113-
function_name: function_name.clone(),
114-
});
151+
function_id: FunctionId::ClassTopLevel {
152+
class_id: *class_id,
153+
},
154+
function_name: Name::from("$class_toplevel"),
155+
})
156+
} else {
157+
None
115158
}
116-
Scope::NonExportedFunction { .. } => {
117-
return None;
159+
}
160+
Scope::NonExportedClass { .. } => None,
161+
Scope::FunctionParameters => match flags.include_default_arguments {
162+
ExportDefaultArguments::InFunction => {
163+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
118164
}
119-
Scope::ExportedClass { class_id, .. } => {
120-
if include_class_top_level {
121-
return Some(FunctionRef {
122-
module_id,
123-
module_name,
124-
function_id: FunctionId::ClassTopLevel {
125-
class_id: *class_id,
126-
},
127-
function_name: Name::from("$class_toplevel"),
128-
});
129-
} else {
130-
return None;
131-
}
165+
ExportDefaultArguments::InParentScope => {
166+
iterator.next().unwrap();
167+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
132168
}
133-
Scope::NonExportedClass { .. } => {
134-
return None;
169+
ExportDefaultArguments::Ignore => None,
170+
},
171+
Scope::FunctionTypeParams
172+
| Scope::FunctionReturnAnnotation
173+
| Scope::ClassTypeParams
174+
| Scope::ClassArguments => {
175+
// These are not true "semantic" scopes.
176+
// We need to skip the parent scope, which is the wrapping function/class scope.
177+
iterator.next().unwrap();
178+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
179+
}
180+
Scope::ClassDecorators => match flags.include_class_decorators {
181+
ExportClassDecorators::InDecoratedClassTopLevel => {
182+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
135183
}
136-
Scope::FunctionParameters => {
137-
if !include_default_arguments_in_function {
138-
// This not a true "semantic" scope.
139-
// We need to skip the parent scope, which is the wrapping function/class scope.
140-
iterator.next().unwrap();
141-
}
184+
ExportClassDecorators::InParentScope => {
185+
iterator.next().unwrap();
186+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
142187
}
143-
Scope::FunctionTypeParams
144-
| Scope::FunctionReturnAnnotation
145-
| Scope::ClassTypeParams
146-
| Scope::ClassArguments => {
147-
// These are not true "semantic" scopes.
148-
// We need to skip the parent scope, which is the wrapping function/class scope.
188+
ExportClassDecorators::Ignore => None,
189+
},
190+
Scope::FunctionDecorators => match flags.include_function_decorators {
191+
ExportFunctionDecorators::InDecoratedFunction => {
192+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
193+
}
194+
ExportFunctionDecorators::InParentScope => {
149195
iterator.next().unwrap();
196+
Self::current_exported_function_impl(iterator, module_id, module_name, flags)
150197
}
151-
Scope::FunctionDecorators | Scope::ClassDecorators => {
152-
if !include_decorators_in_decorated_definition {
153-
iterator.next().unwrap();
198+
ExportFunctionDecorators::InDecoratedTarget => {
199+
match Self::current_exported_function_impl(
200+
iterator,
201+
module_id,
202+
module_name,
203+
flags,
204+
) {
205+
Some(FunctionRef {
206+
function_id: FunctionId::Function { location },
207+
function_name,
208+
..
209+
}) => Some(FunctionRef {
210+
module_id,
211+
module_name,
212+
function_id: FunctionId::FunctionDecoratedTarget { location },
213+
function_name,
214+
}),
215+
_ => None,
154216
}
155217
}
156-
}
218+
ExportFunctionDecorators::Ignore => None,
219+
},
157220
}
158221
}
159222
}
@@ -192,33 +255,58 @@ impl ScopeId {
192255
}
193256

194257
pub trait AstScopedVisitor {
195-
fn visit_statement(&mut self, _stmt: &Stmt, _scopes: &Scopes) {}
258+
fn visit_statement(&mut self, stmt: &Stmt, scopes: &Scopes) {
259+
let _ = stmt;
260+
let _ = scopes;
261+
}
196262
fn visit_expression(
197263
&mut self,
198-
_expr: &Expr,
199-
_scopes: &Scopes,
200-
_parent_expression: Option<&Expr>,
264+
expr: &Expr,
265+
scopes: &Scopes,
266+
parent_expression: Option<&Expr>,
201267
// If the current expression is in an assignment, this is the left side of the assignment
202-
_assignment_targets: Option<&Vec<&Expr>>,
268+
assignment_targets: Option<&Vec<&Expr>>,
203269
) {
270+
let _ = expr;
271+
let _ = scopes;
272+
let _ = parent_expression;
273+
let _ = assignment_targets;
204274
}
205275
fn enter_function_scope(
206276
&mut self,
207-
_function_def: &StmtFunctionDef,
208-
_scopes_in_function: &Scopes,
277+
function_def: &StmtFunctionDef,
278+
scopes_in_function: &Scopes,
209279
) {
280+
let _ = function_def;
281+
let _ = scopes_in_function;
210282
}
211283
fn exit_function_scope(
212284
&mut self,
213-
_function_def: &StmtFunctionDef,
214-
_scopes_outside_function: &Scopes,
285+
function_def: &StmtFunctionDef,
286+
scopes_outside_function: &Scopes,
215287
) {
288+
let _ = function_def;
289+
let _ = scopes_outside_function;
290+
}
291+
fn enter_class_scope(&mut self, class_def: &StmtClassDef, scopes_in_class: &Scopes) {
292+
let _ = class_def;
293+
let _ = scopes_in_class;
294+
}
295+
fn exit_class_scope(&mut self, function_def: &StmtClassDef, scopes_outside_class: &Scopes) {
296+
let _ = function_def;
297+
let _ = scopes_outside_class;
298+
}
299+
fn enter_toplevel_scope(&mut self, ast: &ModModule, scopes_in_toplevel: &Scopes) {
300+
let _ = ast;
301+
let _ = scopes_in_toplevel;
302+
}
303+
fn exit_toplevel_scope(&mut self, ast: &ModModule, scopes_in_toplevel: &Scopes) {
304+
let _ = ast;
305+
let _ = scopes_in_toplevel;
306+
}
307+
fn on_scope_update(&mut self, scopes: &Scopes) {
308+
let _ = scopes;
216309
}
217-
fn enter_class_scope(&mut self, _class_def: &StmtClassDef, _scopes_in_class: &Scopes) {}
218-
fn exit_class_scope(&mut self, _function_def: &StmtClassDef, _scopes_outside_class: &Scopes) {}
219-
fn enter_toplevel_scope(&mut self, _ast: &ModModule, _scopes_in_toplevel: &Scopes) {}
220-
fn exit_toplevel_scope(&mut self, _ast: &ModModule, _scopes_in_toplevel: &Scopes) {}
221-
fn on_scope_update(&mut self, _scopes: &Scopes) {}
222310
fn visit_type_annotations() -> bool;
223311
}
224312

@@ -305,26 +393,28 @@ fn visit_statement<V: AstScopedVisitor>(
305393
visitor.enter_function_scope(function_def, scopes);
306394
visitor.on_scope_update(scopes);
307395

308-
scopes.stack.push(Scope::FunctionDecorators);
309-
visitor.on_scope_update(scopes);
310-
function_def.decorator_list.iter().for_each(&mut |e| {
311-
ruff_python_ast::visitor::source_order::SourceOrderVisitor::visit_decorator(
312-
&mut CustomSourceOrderVisitor {
313-
visitor,
314-
scopes,
315-
module_context,
316-
parent_expression: None,
317-
assignment_targets: None,
318-
},
319-
e,
320-
)
321-
});
322-
scopes.stack.pop();
323-
visitor.on_scope_update(scopes);
396+
if !function_def.decorator_list.is_empty() {
397+
scopes.stack.push(Scope::FunctionDecorators);
398+
visitor.on_scope_update(scopes);
399+
function_def.decorator_list.iter().for_each(&mut |e| {
400+
ruff_python_ast::visitor::source_order::SourceOrderVisitor::visit_decorator(
401+
&mut CustomSourceOrderVisitor {
402+
visitor,
403+
scopes,
404+
module_context,
405+
parent_expression: None,
406+
assignment_targets: None,
407+
},
408+
e,
409+
)
410+
});
411+
scopes.stack.pop();
412+
visitor.on_scope_update(scopes);
413+
}
324414

325-
scopes.stack.push(Scope::FunctionTypeParams);
326-
visitor.on_scope_update(scopes);
327415
if let Some(type_params) = &function_def.type_params {
416+
scopes.stack.push(Scope::FunctionTypeParams);
417+
visitor.on_scope_update(scopes);
328418
ruff_python_ast::visitor::source_order::SourceOrderVisitor::visit_type_params(
329419
&mut CustomSourceOrderVisitor {
330420
visitor,
@@ -335,9 +425,9 @@ fn visit_statement<V: AstScopedVisitor>(
335425
},
336426
type_params,
337427
);
428+
scopes.stack.pop();
429+
visitor.on_scope_update(scopes);
338430
}
339-
scopes.stack.pop();
340-
visitor.on_scope_update(scopes);
341431

342432
scopes.stack.push(Scope::FunctionParameters);
343433
visitor.on_scope_update(scopes);
@@ -354,22 +444,24 @@ fn visit_statement<V: AstScopedVisitor>(
354444
scopes.stack.pop();
355445
visitor.on_scope_update(scopes);
356446

357-
scopes.stack.push(Scope::FunctionReturnAnnotation);
358-
visitor.on_scope_update(scopes);
359-
function_def.returns.iter().for_each(|return_annotation| {
360-
ruff_python_ast::visitor::source_order::SourceOrderVisitor::visit_annotation(
361-
&mut CustomSourceOrderVisitor {
362-
visitor,
363-
scopes,
364-
module_context,
365-
parent_expression: None,
366-
assignment_targets: None,
367-
},
368-
return_annotation,
369-
);
370-
});
371-
scopes.stack.pop();
372-
visitor.on_scope_update(scopes);
447+
if function_def.returns.is_some() {
448+
scopes.stack.push(Scope::FunctionReturnAnnotation);
449+
visitor.on_scope_update(scopes);
450+
function_def.returns.iter().for_each(|return_annotation| {
451+
ruff_python_ast::visitor::source_order::SourceOrderVisitor::visit_annotation(
452+
&mut CustomSourceOrderVisitor {
453+
visitor,
454+
scopes,
455+
module_context,
456+
parent_expression: None,
457+
assignment_targets: None,
458+
},
459+
return_annotation,
460+
);
461+
});
462+
scopes.stack.pop();
463+
visitor.on_scope_update(scopes);
464+
}
373465

374466
for stmt in &function_def.body {
375467
visit_statement(stmt, visitor, scopes, module_context);

0 commit comments

Comments
 (0)