mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 03:55:09 +00:00 
			
		
		
		
	 c9dff5c7d5
			
		
	
	
		c9dff5c7d5
		
			
		
	
	
	
	
		
			
			## 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.
		
			
				
	
	
		
			105 lines
		
	
	
	
		
			3.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			105 lines
		
	
	
	
		
			3.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use ruff_formatter::FormatRuleWithOptions;
 | |
| use ruff_python_ast::AnyNodeRef;
 | |
| use ruff_python_ast::{Expr, ExprCall};
 | |
| 
 | |
| use crate::comments::dangling_comments;
 | |
| use crate::expression::CallChainLayout;
 | |
| use crate::expression::parentheses::{
 | |
|     NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
 | |
| };
 | |
| use crate::prelude::*;
 | |
| 
 | |
| #[derive(Default)]
 | |
| pub struct FormatExprCall {
 | |
|     call_chain_layout: CallChainLayout,
 | |
| }
 | |
| 
 | |
| impl FormatRuleWithOptions<ExprCall, PyFormatContext<'_>> for FormatExprCall {
 | |
|     type Options = CallChainLayout;
 | |
| 
 | |
|     fn with_options(mut self, options: Self::Options) -> Self {
 | |
|         self.call_chain_layout = options;
 | |
|         self
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl FormatNodeRule<ExprCall> for FormatExprCall {
 | |
|     fn fmt_fields(&self, item: &ExprCall, f: &mut PyFormatter) -> FormatResult<()> {
 | |
|         let ExprCall {
 | |
|             range: _,
 | |
|             node_index: _,
 | |
|             func,
 | |
|             arguments,
 | |
|         } = item;
 | |
| 
 | |
|         let comments = f.context().comments().clone();
 | |
|         let dangling = comments.dangling(item);
 | |
| 
 | |
|         let call_chain_layout = self.call_chain_layout.apply_in_node(item, f);
 | |
| 
 | |
|         let fmt_func = format_with(|f: &mut PyFormatter| {
 | |
|             // Format the function expression.
 | |
|             if is_expression_parenthesized(
 | |
|                 func.into(),
 | |
|                 f.context().comments().ranges(),
 | |
|                 f.context().source(),
 | |
|             ) {
 | |
|                 func.format().with_options(Parentheses::Always).fmt(f)
 | |
|             } else {
 | |
|                 match func.as_ref() {
 | |
|                     Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f),
 | |
|                     Expr::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f),
 | |
|                     Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f),
 | |
|                     _ => func.format().with_options(Parentheses::Never).fmt(f),
 | |
|                 }
 | |
|             }?;
 | |
| 
 | |
|             // Format comments between the function and its arguments.
 | |
|             dangling_comments(dangling).fmt(f)?;
 | |
| 
 | |
|             // Format the arguments.
 | |
|             arguments.format().fmt(f)
 | |
|         });
 | |
| 
 | |
|         // Allow to indent the parentheses while
 | |
|         // ```python
 | |
|         // g1 = (
 | |
|         //     queryset.distinct().order_by(field.name).values_list(field_name_flat_long_long=True)
 | |
|         // )
 | |
|         // ```
 | |
|         if call_chain_layout == CallChainLayout::Fluent
 | |
|             && self.call_chain_layout == CallChainLayout::Default
 | |
|         {
 | |
|             group(&fmt_func).fmt(f)
 | |
|         } else {
 | |
|             fmt_func.fmt(f)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl NeedsParentheses for ExprCall {
 | |
|     fn needs_parentheses(
 | |
|         &self,
 | |
|         _parent: AnyNodeRef,
 | |
|         context: &PyFormatContext,
 | |
|     ) -> OptionalParentheses {
 | |
|         if CallChainLayout::from_expression(
 | |
|             self.into(),
 | |
|             context.comments().ranges(),
 | |
|             context.source(),
 | |
|         ) == CallChainLayout::Fluent
 | |
|         {
 | |
|             OptionalParentheses::Multiline
 | |
|         } else if context.comments().has_dangling(self) {
 | |
|             OptionalParentheses::Always
 | |
|         } else if is_expression_parenthesized(
 | |
|             self.func.as_ref().into(),
 | |
|             context.comments().ranges(),
 | |
|             context.source(),
 | |
|         ) {
 | |
|             OptionalParentheses::Never
 | |
|         } else {
 | |
|             self.func.needs_parentheses(self.into(), context)
 | |
|         }
 | |
|     }
 | |
| }
 |