[red-knot] Use iterative approach to collect overloads (#17607)

## Summary

This PR updates the `to_overloaded` method to use an iterative approach
instead of a recursive one.

Refer to
https://github.com/astral-sh/ruff/pull/17585#discussion_r2056804587 for
context.

The main benefit here is that it avoids calling the `to_overloaded`
function in a recursive manner which is a salsa query. So, this is a bit
hand wavy but we should also see less memory used because the cache will
only contain a single entry which should be the entire overload chain.
Previously, the recursive approach would mean that each of the function
involved in an overload chain would have a cache entry. This reduce in
memory shouldn't be too much and I haven't looked at the actual data for
it.

## Test Plan

Existing test cases should pass.
This commit is contained in:
Dhruv Manilawala 2025-04-24 22:23:50 +05:30 committed by GitHub
parent 8d2c79276d
commit 9937064761
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 43 additions and 53 deletions

View File

@ -6308,64 +6308,54 @@ impl<'db> FunctionType<'db> {
db: &'db dyn Db, db: &'db dyn Db,
function: FunctionType<'db>, function: FunctionType<'db>,
) -> Option<OverloadedFunction<'db>> { ) -> Option<OverloadedFunction<'db>> {
// The semantic model records a use for each function on the name node. This is used here let mut current = function;
// to get the previous function definition with the same name. let mut overloads = vec![];
let scope = function.definition(db).scope(db);
let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db));
let use_id = function
.body_scope(db)
.node(db)
.expect_function()
.name
.scoped_use_id(db, scope);
if let Symbol::Type(Type::FunctionLiteral(function_literal), Boundness::Bound) = loop {
symbol_from_bindings(db, use_def.bindings_at_use(use_id)) // The semantic model records a use for each function on the name node. This is used
{ // here to get the previous function definition with the same name.
match function_literal.to_overloaded(db) { let scope = current.definition(db).scope(db);
None => { let use_def =
debug_assert!( semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db));
!function_literal.has_known_decorator(db, FunctionDecorators::OVERLOAD), let use_id = current
"Expected `Some(OverloadedFunction)` if the previous function was an overload" .body_scope(db)
); .node(db)
} .expect_function()
Some(OverloadedFunction { .name
implementation: Some(_), .scoped_use_id(db, scope);
..
}) => { let Symbol::Type(Type::FunctionLiteral(previous), Boundness::Bound) =
// If the previous overloaded function already has an implementation, then this symbol_from_bindings(db, use_def.bindings_at_use(use_id))
// new signature completely replaces it. else {
} break;
Some(OverloadedFunction { };
overloads,
implementation: None, if previous.has_known_decorator(db, FunctionDecorators::OVERLOAD) {
}) => { overloads.push(previous);
return Some( } else {
if function.has_known_decorator(db, FunctionDecorators::OVERLOAD) { break;
let mut overloads = overloads.clone();
overloads.push(function);
OverloadedFunction {
overloads,
implementation: None,
}
} else {
OverloadedFunction {
overloads: overloads.clone(),
implementation: Some(function),
}
},
);
}
} }
current = previous;
} }
if function.has_known_decorator(db, FunctionDecorators::OVERLOAD) { // Overloads are inserted in reverse order, from bottom to top.
Some(OverloadedFunction { overloads.reverse();
overloads: vec![function],
implementation: None, let implementation = if function.has_known_decorator(db, FunctionDecorators::OVERLOAD) {
}) overloads.push(function);
} else {
None None
} else {
Some(function)
};
if overloads.is_empty() {
None
} else {
Some(OverloadedFunction {
overloads,
implementation,
})
} }
} }