Skip to content

Commit 2ef9d84

Browse files
arthaudmeta-codesync[bot]
authored andcommitted
Remove override graph building in pyrefly
Summary: # Context This stack will re-design how `pyrefly --report-pysa` is implemented. Right now, it is a post processing step that requires pyrefly to preserve internal states for all modules, which can lead to high memory usage. To avoid this, we will incorporate the pysa reporting steps within the type checking. See https://docs.google.com/document/d/1Bk8izFv4nQxbQmaog9OqcpcSxyzAW3Z-bn0JUV4wkg4/ for context # This diff The current call graph building logic relies on the override graph, which has a global view of the program. If we implement the pysa reporting step within type checking, we won't have the override graph at that point. This diff changes our logic so we don't depend on the override graph: when we see a virtual method call (i.e a regular call), we simply emit a `Target::Override{class.method}` with a `receiver_class`. We move the logic that expands overrides and filter by the receiver class in the pysa analysis, since it has an override graph. Reviewed By: tianhan0 Differential Revision: D96513871 fbshipit-source-id: 26fc45f097a916bc5c73a5bd32cf332c205f1a2d
1 parent 6b72e3c commit 2ef9d84

File tree

4 files changed

+188
-391
lines changed

4 files changed

+188
-391
lines changed

pyrefly/lib/report/pysa/call_graph.rs

Lines changed: 21 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ use crate::report::pysa::global_variable::WholeProgramGlobalVariables;
9898
use crate::report::pysa::location::PysaLocation;
9999
use crate::report::pysa::module::ModuleId;
100100
use crate::report::pysa::module::ModuleKey;
101-
use crate::report::pysa::override_graph::OverrideGraph;
102101
use crate::report::pysa::types::ScalarTypeProperties;
103-
use crate::report::pysa::types::has_superclass;
104102
use crate::report::pysa::types::string_for_type;
105103
use crate::state::lsp::FindDefinitionItemWithDocstring;
106104
use crate::state::lsp::FindPreference;
@@ -282,26 +280,10 @@ pub trait FunctionTrait:
282280

283281
impl FunctionTrait for FunctionRef {}
284282

285-
/// Maximum number of targets in an override subset before we collapse it into
286-
/// `OverrideSubsetThreshold`. Large subsets lead to very large call-graph JSON
287-
/// files and significant slowdowns during both serialization and Pysa's
288-
/// analysis. When the number of targets exceeds this threshold we fall back to
289-
/// recording only the base method.
290-
const OVERRIDE_SUBSET_THRESHOLD: usize = 500;
291-
292283
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, PartialOrd, Ord)]
293284
pub enum Target<Function: FunctionTrait> {
294-
Function(Function), // Either a function or a method
295-
AllOverrides(Function), // All overrides of the given method
296-
OverrideSubset {
297-
base_method: Function,
298-
subset: Vec1<Target<Function>>,
299-
},
300-
/// Like `OverrideSubset`, but used when the number of targets in the subset
301-
/// exceeds `OVERRIDE_SUBSET_THRESHOLD`.
302-
OverrideSubsetThreshold {
303-
base_method: Function,
304-
},
285+
Function(Function), // Either a function or a method
286+
Overrides(Function), // All overrides of the given method
305287
FormatString,
306288
}
307289

@@ -316,27 +298,15 @@ impl<Function: FunctionTrait> Target<Function> {
316298
{
317299
match self {
318300
Target::Function(function) => Target::Function(map(function)),
319-
Target::AllOverrides(function) => Target::AllOverrides(map(function)),
320-
Target::OverrideSubset {
321-
base_method,
322-
subset,
323-
} => Target::OverrideSubset {
324-
base_method: map(base_method),
325-
subset: Vec1::mapped(subset, |target| target.map_function(map)),
326-
},
327-
Target::OverrideSubsetThreshold { base_method } => Target::OverrideSubsetThreshold {
328-
base_method: map(base_method),
329-
},
301+
Target::Overrides(function) => Target::Overrides(map(function)),
330302
Target::FormatString => Target::FormatString,
331303
}
332304
}
333305

334306
fn base_function(&self) -> Option<&Function> {
335307
match self {
336308
Target::Function(function) => Some(function),
337-
Target::AllOverrides(method) => Some(method),
338-
Target::OverrideSubset { base_method, .. }
339-
| Target::OverrideSubsetThreshold { base_method } => Some(base_method),
309+
Target::Overrides(method) => Some(method),
340310
Target::FormatString => None,
341311
}
342312
}
@@ -1578,7 +1548,6 @@ struct CallGraphVisitor<'a> {
15781548
module_id: ModuleId,
15791549
module_name: ModuleName,
15801550
function_base_definitions: &'a WholeProgramFunctionDefinitions<FunctionBaseDefinition>,
1581-
override_graph: &'a OverrideGraph,
15821551
global_variables: &'a WholeProgramGlobalVariables,
15831552
captured_variables: &'a ModuleCapturedVariables<FunctionRef>,
15841553
current_function: Option<FunctionRef>, // The current function, if it is exported.
@@ -1758,26 +1727,13 @@ impl<'a> CallGraphVisitor<'a> {
17581727
}
17591728

17601729
// Figure out what target to pick for an indirect call that resolves to implementation_target.
1761-
// E.g., if the receiver type is A, and A derives from Base, and the target is Base.method, then
1762-
// targeting the override tree of Base.method is wrong, as it would include all siblings for A.//
1763-
// Instead, we have the following cases:
1764-
// a) receiver type matches implementation_target's declaring type -> override implementation_target
1765-
// b) no implementation_target override entries are subclasses of A -> real implementation_target
1766-
// c) some override entries are subclasses of A -> search upwards for actual implementation,
1767-
// and override all those where the override name is
1768-
// 1) the override target if it exists in the override shared mem
1769-
// 2) the real target otherwise
1730+
// The receiver_class is already part of the PysaCallTarget, so we just need to decide
1731+
// between Function (no receiver) and Overrides (has receiver).
17701732
fn compute_targets_for_virtual_call(
17711733
&self,
1772-
callee_type: Option<&Type>,
1773-
precise_receiver_type: Option<&Type>,
1734+
receiver_type: Option<&Type>,
17741735
callee: FunctionRef,
17751736
) -> Target<FunctionRef> {
1776-
let receiver_type = if precise_receiver_type.is_some() {
1777-
precise_receiver_type
1778-
} else {
1779-
receiver_type_from_callee_type(callee_type)
1780-
};
17811737
if receiver_type.is_none() {
17821738
return Target::Function(callee);
17831739
}
@@ -1793,70 +1749,9 @@ impl<'a> CallGraphVisitor<'a> {
17931749
if receiver_class.is_none() {
17941750
return Target::Function(callee);
17951751
}
1796-
let receiver_class = receiver_class.unwrap();
1797-
1798-
let callee_class = self
1799-
.function_base_definitions
1800-
.get(callee.module_id, &callee.function_id)
1801-
.and_then(|definition| definition.defining_class.clone());
1802-
let callee_class = callee_class
1803-
.unwrap_or_else(|| panic!("Expect a callee class for callee `{:#?}`", callee));
1804-
1805-
let get_actual_target = |callee: FunctionRef| {
1806-
if self.override_graph.overrides_exist(&callee) {
1807-
Target::AllOverrides(callee)
1808-
} else {
1809-
Target::Function(callee)
1810-
}
1811-
};
1812-
if callee_class == receiver_class {
1813-
// case a
1814-
get_actual_target(callee)
1815-
} else if let Some(overriding_classes) = self.override_graph.get_overriding_classes(&callee)
1816-
{
1817-
// case c
1818-
if overriding_classes.len() > OVERRIDE_SUBSET_THRESHOLD {
1819-
Target::OverrideSubsetThreshold {
1820-
base_method: callee,
1821-
}
1822-
} else {
1823-
let mut callees = overriding_classes
1824-
.iter()
1825-
.filter_map(|overriding_class| {
1826-
if has_superclass(
1827-
&overriding_class.class,
1828-
&receiver_class.class,
1829-
self.module_context,
1830-
) {
1831-
self.function_ref_from_class_field(
1832-
&overriding_class.class,
1833-
&callee.function_name,
1834-
/* exclude_object_methods */ false,
1835-
)
1836-
.ok()
1837-
.map(get_actual_target)
1838-
} else {
1839-
None
1840-
}
1841-
})
1842-
.collect::<Vec<_>>();
1843-
1844-
if callees.is_empty() {
1845-
Target::Function(callee)
1846-
} else if callees.len() == overriding_classes.len() {
1847-
Target::AllOverrides(callee)
1848-
} else {
1849-
callees.sort();
1850-
Target::OverrideSubset {
1851-
base_method: callee,
1852-
subset: Vec1::try_from_vec(callees).unwrap(),
1853-
}
1854-
}
1855-
}
1856-
} else {
1857-
// case b
1858-
Target::Function(callee)
1859-
}
1752+
// Pysa is responsible for filtering the overridden methods
1753+
// to only those from classes that extend the receiver_class.
1754+
Target::Overrides(callee)
18601755
}
18611756

18621757
fn call_targets_from_callable_type(
@@ -2032,22 +1927,16 @@ impl<'a> CallGraphVisitor<'a> {
20321927
override_implicit_receiver,
20331928
)
20341929
} else {
2035-
let target = self.compute_targets_for_virtual_call(
2036-
callee_type,
2037-
precise_receiver_type,
2038-
function_ref,
2039-
);
1930+
let target = self.compute_targets_for_virtual_call(receiver_type, function_ref);
20401931
match target {
2041-
Target::Function(_)
2042-
| Target::AllOverrides(_)
2043-
| Target::OverrideSubset { .. }
2044-
| Target::OverrideSubsetThreshold { .. } => self.call_target_from_function_target(
2045-
target,
2046-
return_type,
2047-
receiver_type,
2048-
callee_expr_suffix,
2049-
override_implicit_receiver,
2050-
),
1932+
Target::Function(_) | Target::Overrides(_) => self
1933+
.call_target_from_function_target(
1934+
target,
1935+
return_type,
1936+
receiver_type,
1937+
callee_expr_suffix,
1938+
override_implicit_receiver,
1939+
),
20511940
Target::FormatString => PysaCallTarget {
20521941
target,
20531942
implicit_receiver: ImplicitReceiver::False,
@@ -4374,7 +4263,6 @@ fn resolve_call(
43744263
call: &ExprCall,
43754264
function_definitions: &WholeProgramFunctionDefinitions<FunctionBaseDefinition>,
43764265
module_context: &ModuleContext,
4377-
override_graph: &OverrideGraph,
43784266
) -> Vec<PysaCallTarget<FunctionRef>> {
43794267
let mut call_graphs = CallGraphs::new();
43804268
let visitor = CallGraphVisitor {
@@ -4386,7 +4274,6 @@ fn resolve_call(
43864274
current_function: None,
43874275
debug: false,
43884276
debug_scopes: Vec::new(),
4389-
override_graph,
43904277
global_variables: &WholeProgramGlobalVariables::new(),
43914278
captured_variables: &ModuleCapturedVariables::new(),
43924279
error_collector: ErrorCollector::new(module_context.module_info.dupe(), ErrorStyle::Never),
@@ -4414,7 +4301,6 @@ fn resolve_expression(
44144301
expression: &Expr,
44154302
function_definitions: &WholeProgramFunctionDefinitions<FunctionBaseDefinition>,
44164303
module_context: &ModuleContext,
4417-
override_graph: &OverrideGraph,
44184304
parent_expression: Option<&Expr>,
44194305
) -> Vec<PysaCallTarget<FunctionRef>> {
44204306
// This needs to be provided. Otherwise the callees won't be registered into `call_graphs`.
@@ -4434,7 +4320,6 @@ fn resolve_expression(
44344320
current_function: Some(current_function.clone()),
44354321
debug: false,
44364322
debug_scopes: Vec::new(),
4437-
override_graph,
44384323
global_variables: &WholeProgramGlobalVariables::new(),
44394324
captured_variables: &ModuleCapturedVariables::new(),
44404325
error_collector: ErrorCollector::new(module_context.module_info.dupe(), ErrorStyle::Never),
@@ -4467,19 +4352,8 @@ pub fn resolve_decorator_callees(
44674352
) -> HashMap<PysaLocation, Vec<Target<FunctionRef>>> {
44684353
let mut decorator_callees = HashMap::new();
44694354

4470-
// We do not care about overrides here
4471-
let override_graph = OverrideGraph::new();
4472-
44734355
let is_object_new_or_init_target = |target: &Target<FunctionRef>| match target {
4474-
Target::Function(function_ref)
4475-
| Target::AllOverrides(function_ref)
4476-
| Target::OverrideSubset {
4477-
base_method: function_ref,
4478-
..
4479-
}
4480-
| Target::OverrideSubsetThreshold {
4481-
base_method: function_ref,
4482-
} => {
4356+
Target::Function(function_ref) | Target::Overrides(function_ref) => {
44834357
function_ref.module_name == ModuleName::builtins()
44844358
&& (function_ref.function_name == dunder::INIT
44854359
|| function_ref.function_name == dunder::NEW)
@@ -4491,8 +4365,7 @@ pub fn resolve_decorator_callees(
44914365
let (range, callees) = match &decorator.expression {
44924366
Expr::Call(call) => {
44934367
// Decorator factor, e.g `@foo(1)`. We export the callee of `foo`.
4494-
let callees =
4495-
resolve_call(call, function_base_definitions, context, &override_graph);
4368+
let callees = resolve_call(call, function_base_definitions, context);
44964369
(
44974370
(*call.func).range(),
44984371
callees
@@ -4507,7 +4380,6 @@ pub fn resolve_decorator_callees(
45074380
expr,
45084381
function_base_definitions,
45094382
context,
4510-
&override_graph,
45114383
/* parent_expression */ None,
45124384
);
45134385
(
@@ -4536,7 +4408,6 @@ pub fn resolve_decorator_callees(
45364408
pub fn export_call_graphs(
45374409
context: &ModuleContext,
45384410
function_base_definitions: &WholeProgramFunctionDefinitions<FunctionBaseDefinition>,
4539-
override_graph: &OverrideGraph,
45404411
global_variables: &WholeProgramGlobalVariables,
45414412
captured_variables: &WholeProgramCapturedVariables,
45424413
) -> CallGraphs<ExpressionIdentifier, FunctionRef> {
@@ -4552,7 +4423,6 @@ pub fn export_call_graphs(
45524423
current_function: None,
45534424
debug: false,
45544425
debug_scopes: Vec::new(),
4555-
override_graph,
45564426
global_variables,
45574427
captured_variables: captured_variables
45584428
.get_for_module(context.module_id)

pyrefly/lib/report/pysa/mod.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ use crate::report::pysa::location::PysaLocation;
7777
use crate::report::pysa::module::ModuleId;
7878
use crate::report::pysa::module::ModuleIds;
7979
use crate::report::pysa::module::ModuleKey;
80-
use crate::report::pysa::override_graph::OverrideGraph;
8180
use crate::report::pysa::override_graph::build_reversed_override_graph;
8281
use crate::report::pysa::slow_fun_monitor::slow_fun_monitor_scope;
8382
use crate::report::pysa::step_logger::StepLogger;
@@ -185,14 +184,12 @@ pub fn export_module_type_of_expressions(context: &ModuleContext) -> PysaModuleT
185184
pub fn export_module_call_graphs(
186185
context: &ModuleContext,
187186
function_base_definitions: &WholeProgramFunctionDefinitions<FunctionBaseDefinition>,
188-
override_graph: &OverrideGraph,
189187
global_variables: &WholeProgramGlobalVariables,
190188
captured_variables: &WholeProgramCapturedVariables,
191189
) -> PysaModuleCallGraphs {
192190
let call_graphs = export_call_graphs(
193191
context,
194192
function_base_definitions,
195-
override_graph,
196193
global_variables,
197194
captured_variables,
198195
)
@@ -391,7 +388,6 @@ fn write_module_type_of_expressions_files(
391388
fn write_module_call_graph_files(
392389
module_work_list: &Vec<(Handle, ModuleId, PathBuf)>,
393390
function_base_definitions: &WholeProgramFunctionDefinitions<FunctionBaseDefinition>,
394-
override_graph: &OverrideGraph,
395391
global_variables: &WholeProgramGlobalVariables,
396392
captured_variables: &WholeProgramCapturedVariables,
397393
transaction: &Transaction,
@@ -414,7 +410,6 @@ fn write_module_call_graph_files(
414410
export_module_call_graphs(
415411
&context,
416412
function_base_definitions,
417-
override_graph,
418413
global_variables,
419414
captured_variables,
420415
)
@@ -593,9 +588,6 @@ pub fn write_results(
593588
let global_variables = collect_global_variables(&handles, transaction, &module_ids);
594589
let captured_variables = collect_captured_variables(&handles, transaction, &module_ids);
595590

596-
let override_graph =
597-
OverrideGraph::from_reversed(&reversed_override_graph, &function_base_definitions);
598-
599591
write_module_definitions_files(
600592
&module_work_list,
601593
&function_base_definitions,
@@ -616,7 +608,6 @@ pub fn write_results(
616608
write_module_call_graph_files(
617609
&module_work_list,
618610
&function_base_definitions,
619-
&override_graph,
620611
&global_variables,
621612
&captured_variables,
622613
transaction,

0 commit comments

Comments
 (0)