## Summary Garbage collect ASTs once we are done checking a given file. Queries with a cross-file dependency on the AST will reparse the file on demand. This reduces ty's peak memory usage by ~20-30%. The primary change of this PR is adding a `node_index` field to every AST node, that is assigned by the parser. `ParsedModule` can use this to create a flat index of AST nodes any time the file is parsed (or reparsed). This allows `AstNodeRef` to simply index into the current instance of the `ParsedModule`, instead of storing a pointer directly. The indices are somewhat hackily (using an atomic integer) assigned by the `parsed_module` query instead of by the parser directly. Assigning the indices in source-order in the (recursive) parser turns out to be difficult, and collecting the nodes during semantic indexing is impossible as `SemanticIndex` does not hold onto a specific `ParsedModuleRef`, which the pointers in the flat AST are tied to. This means that we have to do an extra AST traversal to assign and collect the nodes into a flat index, but the small performance impact (~3% on cold runs) seems worth it for the memory savings. Part of https://github.com/astral-sh/ty/issues/214.
76 lines
2.2 KiB
Rust
76 lines
2.2 KiB
Rust
use ruff_formatter::{format_args, write};
|
|
use ruff_python_ast::AnyNodeRef;
|
|
use ruff_python_ast::ExprNamed;
|
|
|
|
use crate::comments::dangling_comments;
|
|
use crate::expression::parentheses::{
|
|
NeedsParentheses, OptionalParentheses, in_parentheses_only_soft_line_break_or_space,
|
|
};
|
|
use crate::prelude::*;
|
|
|
|
#[derive(Default)]
|
|
pub struct FormatExprNamed;
|
|
|
|
impl FormatNodeRule<ExprNamed> for FormatExprNamed {
|
|
fn fmt_fields(&self, item: &ExprNamed, f: &mut PyFormatter) -> FormatResult<()> {
|
|
let ExprNamed {
|
|
target,
|
|
value,
|
|
range: _,
|
|
node_index: _,
|
|
} = item;
|
|
|
|
// This context, a dangling comment is a comment between the `:=` and the value.
|
|
let comments = f.context().comments().clone();
|
|
let dangling = comments.dangling(item);
|
|
|
|
write!(
|
|
f,
|
|
[
|
|
group(&format_args![
|
|
target.format(),
|
|
in_parentheses_only_soft_line_break_or_space()
|
|
]),
|
|
token(":=")
|
|
]
|
|
)?;
|
|
|
|
if dangling.is_empty() {
|
|
write!(f, [space()])?;
|
|
} else {
|
|
write!(f, [dangling_comments(dangling), hard_line_break()])?;
|
|
}
|
|
|
|
write!(f, [value.format()])
|
|
}
|
|
}
|
|
|
|
impl NeedsParentheses for ExprNamed {
|
|
fn needs_parentheses(
|
|
&self,
|
|
parent: AnyNodeRef,
|
|
_context: &PyFormatContext,
|
|
) -> OptionalParentheses {
|
|
// Unlike tuples, named expression parentheses are not part of the range even when
|
|
// mandatory. See [PEP 572](https://peps.python.org/pep-0572/) for details.
|
|
if parent.is_stmt_ann_assign()
|
|
|| parent.is_stmt_assign()
|
|
|| parent.is_stmt_aug_assign()
|
|
|| parent.is_stmt_assert()
|
|
|| parent.is_stmt_return()
|
|
|| parent.is_except_handler_except_handler()
|
|
|| parent.is_with_item()
|
|
|| parent.is_expr_yield()
|
|
|| parent.is_expr_yield_from()
|
|
|| parent.is_expr_await()
|
|
|| parent.is_stmt_delete()
|
|
|| parent.is_stmt_for()
|
|
|| parent.is_stmt_function_def()
|
|
{
|
|
OptionalParentheses::Always
|
|
} else {
|
|
OptionalParentheses::Multiline
|
|
}
|
|
}
|
|
}
|