Refactor symbol lookup APIs to hide re-export implementation details (#16133)

## Summary

This PR refactors the symbol lookup APIs to better facilitate the
re-export implementation. Specifically,
* Add `module_type_symbol` which returns the `Symbol` that's a member of
`types.ModuleType`
* Rename `symbol` -> `symbol_impl`; add `symbol` which delegates to
`symbol_impl` with `RequireExplicitReExport::No`
* Update `global_symbol` to do `symbol_impl` -> fall back to
`module_type_symbol` and default to `RequireExplicitReExport::No`
* Add `imported_symbol` to do `symbol_impl` with
`RequireExplicitReExport` as `Yes` if the module is in a stub file else
`No`
* Update `known_module_symbol` to use `imported_symbol` with a fallback
to `module_type_symbol`
* Update `ModuleLiteralType::member` to use `imported_symbol` with a
custom fallback

We could potentially also update `symbol_from_declarations` and
`symbol_from_bindings` to avoid passing in the `RequireExplicitReExport`
as it would be always `No` if called directly. We could add
`symbol_from_declarations_impl` and `symbol_from_bindings_impl`.

Looking at the `_impl` functions, I think we should move all of these
symbol related logic into `symbol.rs` where `Symbol` is defined and the
`_impl` could be private while we expose the public APIs at the crate
level. This would also make the `RequireExplicitReExport` an
implementation detail and the caller doesn't need to worry about it.
This commit is contained in:
Dhruv Manilawala 2025-02-14 15:25:48 +05:30 committed by GitHub
parent 60b3ef2c98
commit 63dd68e0ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 160 additions and 119 deletions

View File

@ -50,10 +50,6 @@ impl<'db> Definition<'db> {
self.kind(db).category()
}
pub(crate) fn in_stub(self, db: &'db dyn Db) -> bool {
self.file(db).is_stub(db.upcast())
}
pub(crate) fn is_declaration(self, db: &'db dyn Db) -> bool {
self.kind(db).category().is_declaration()
}

View File

@ -2,7 +2,7 @@ use crate::module_resolver::{resolve_module, KnownModule};
use crate::semantic_index::global_scope;
use crate::semantic_index::symbol::ScopeId;
use crate::symbol::Symbol;
use crate::types::{global_symbol, SymbolLookup};
use crate::types::imported_symbol;
use crate::Db;
/// Lookup the type of `symbol` in a given known module
@ -14,18 +14,10 @@ pub(crate) fn known_module_symbol<'db>(
symbol: &str,
) -> Symbol<'db> {
resolve_module(db, &known_module.name())
.map(|module| global_symbol(db, SymbolLookup::External, module.file(), symbol))
.map(|module| imported_symbol(db, &module, symbol))
.unwrap_or(Symbol::Unbound)
}
/// Lookup the type of `symbol` in the builtins namespace.
///
/// Returns `Symbol::Unbound` if the `builtins` module isn't available for some reason.
#[inline]
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
known_module_symbol(db, KnownModule::Builtins, symbol)
}
/// Lookup the type of `symbol` in the `typing` module namespace.
///
/// Returns `Symbol::Unbound` if the `typing` module isn't available for some reason.

View File

@ -32,7 +32,7 @@ use crate::semantic_index::{
use_def_map, BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint,
DeclarationsIterator,
};
use crate::stdlib::{builtins_symbol, known_module_symbol, typing_extensions_symbol};
use crate::stdlib::{known_module_symbol, typing_extensions_symbol};
use crate::suppression::check_suppressions;
use crate::symbol::{Boundness, Symbol};
use crate::types::call::{
@ -107,32 +107,29 @@ fn widen_type_for_undeclared_public_symbol<'db>(
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum SymbolLookup {
/// Look up the symbol as seen from within the same module.
Internal,
/// Look up the symbol as seen from outside the module.
External,
enum RequiresExplicitReExport {
Yes,
No,
}
impl SymbolLookup {
const fn is_external(self) -> bool {
matches!(self, Self::External)
impl RequiresExplicitReExport {
const fn is_yes(self) -> bool {
matches!(self, RequiresExplicitReExport::Yes)
}
}
/// Infer the public type of a symbol (its type as seen from outside its scope).
fn symbol<'db>(
fn symbol_impl<'db>(
db: &'db dyn Db,
lookup: SymbolLookup,
scope: ScopeId<'db>,
name: &str,
requires_explicit_reexport: RequiresExplicitReExport,
) -> Symbol<'db> {
#[salsa::tracked]
fn symbol_by_id<'db>(
db: &'db dyn Db,
lookup: SymbolLookup,
scope: ScopeId<'db>,
symbol_id: ScopedSymbolId,
requires_explicit_reexport: RequiresExplicitReExport,
) -> Symbol<'db> {
let use_def = use_def_map(db, scope);
@ -140,7 +137,7 @@ fn symbol<'db>(
// on inference from bindings.
let declarations = use_def.public_declarations(symbol_id);
let declared = symbol_from_declarations(db, lookup, declarations);
let declared = symbol_from_declarations(db, declarations, requires_explicit_reexport);
let is_final = declared.as_ref().is_ok_and(SymbolAndQualifiers::is_final);
let declared = declared.map(|SymbolAndQualifiers(symbol, _)| symbol);
@ -150,7 +147,7 @@ fn symbol<'db>(
// Symbol is possibly declared
Ok(Symbol::Type(declared_ty, Boundness::PossiblyUnbound)) => {
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, lookup, bindings);
let inferred = symbol_from_bindings(db, bindings, requires_explicit_reexport);
match inferred {
// Symbol is possibly undeclared and definitely unbound
@ -170,7 +167,7 @@ fn symbol<'db>(
// Symbol is undeclared, return the union of `Unknown` with the inferred type
Ok(Symbol::Unbound) => {
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, lookup, bindings);
let inferred = symbol_from_bindings(db, bindings, requires_explicit_reexport);
// `__slots__` is a symbol with special behavior in Python's runtime. It can be
// modified externally, but those changes do not take effect. We therefore issue
@ -232,7 +229,7 @@ fn symbol<'db>(
symbol_table(db, scope)
.symbol_id_by_name(name)
.map(|symbol| symbol_by_id(db, lookup, scope, symbol))
.map(|symbol| symbol_by_id(db, scope, symbol, requires_explicit_reexport))
.unwrap_or(Symbol::Unbound)
}
@ -271,27 +268,99 @@ fn module_type_symbols<'db>(db: &'db dyn Db) -> smallvec::SmallVec<[ast::name::N
.collect()
}
pub(crate) fn global_symbol<'db>(
db: &'db dyn Db,
lookup: SymbolLookup,
file: File,
name: &str,
) -> Symbol<'db> {
// Not defined explicitly in the global scope?
// All modules are instances of `types.ModuleType`;
// look it up there (with a few very special exceptions)
symbol(db, lookup, global_scope(db, file), name).or_fall_back_to(db, || {
if module_type_symbols(db)
.iter()
.any(|module_type_member| &**module_type_member == name)
{
KnownClass::ModuleType.to_instance(db).member(db, name)
} else {
/// Return the symbol for a member of `types.ModuleType`.
pub(crate) fn module_type_symbol<'db>(db: &'db dyn Db, name: &str) -> Symbol<'db> {
if module_type_symbols(db)
.iter()
.any(|module_type_member| &**module_type_member == name)
{
KnownClass::ModuleType.to_instance(db).member(db, name)
} else {
Symbol::Unbound
}
}
/// Infer the public type of a symbol (its type as seen from outside its scope) in the given
/// `scope`.
fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> {
symbol_impl(db, scope, name, RequiresExplicitReExport::No)
}
/// Infers the public type of a module-global symbol as seen from within the same file.
///
/// If it's not defined explicitly in the global scope, it will look it up in `types.ModuleType`
/// with a few very special exceptions.
///
/// Use [`imported_symbol`] to perform the lookup as seen from outside the file (e.g. via imports).
pub(crate) fn global_symbol<'db>(db: &'db dyn Db, file: File, name: &str) -> Symbol<'db> {
symbol_impl(
db,
global_scope(db, file),
name,
RequiresExplicitReExport::No,
)
.or_fall_back_to(db, || module_type_symbol(db, name))
}
/// Infers the public type of an imported symbol.
pub(crate) fn imported_symbol<'db>(db: &'db dyn Db, module: &Module, name: &str) -> Symbol<'db> {
// If it's not found in the global scope, check if it's present as an instance on
// `types.ModuleType` or `builtins.object`.
//
// We do a more limited version of this in `global_symbol`, but there are two crucial
// differences here:
// - If a member is looked up as an attribute, `__init__` is also available on the module, but
// it isn't available as a global from inside the module
// - If a member is looked up as an attribute, members on `builtins.object` are also available
// (because `types.ModuleType` inherits from `object`); these attributes are also not
// available as globals from inside the module.
//
// The same way as in `global_symbol`, however, we need to be careful to ignore
// `__getattr__`. Typeshed has a fake `__getattr__` on `types.ModuleType` to help out with
// dynamic imports; we shouldn't use it for `ModuleLiteral` types where we know exactly which
// module we're dealing with.
external_symbol_impl(db, module.file(), name).or_fall_back_to(db, || {
if name == "__getattr__" {
Symbol::Unbound
} else {
KnownClass::ModuleType.to_instance(db).member(db, name)
}
})
}
/// Lookup the type of `symbol` in the builtins namespace.
///
/// Returns `Symbol::Unbound` if the `builtins` module isn't available for some reason.
///
/// Note that this function is only intended for use in the context of the builtins *namespace*
/// and should not be used when a symbol is being explicitly imported from the `builtins` module
/// (e.g. `from builtins import int`).
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
resolve_module(db, &KnownModule::Builtins.name())
.map(|module| {
external_symbol_impl(db, module.file(), symbol).or_fall_back_to(db, || {
// We're looking up in the builtins namespace and not the module, so we should
// do the normal lookup in `types.ModuleType` and not the special one as in
// `imported_symbol`.
module_type_symbol(db, symbol)
})
})
.unwrap_or(Symbol::Unbound)
}
fn external_symbol_impl<'db>(db: &'db dyn Db, file: File, name: &str) -> Symbol<'db> {
symbol_impl(
db,
global_scope(db, file),
name,
if file.is_stub(db.upcast()) {
RequiresExplicitReExport::Yes
} else {
RequiresExplicitReExport::No
},
)
}
/// Infer the type of a binding.
pub(crate) fn binding_type<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
let inference = infer_definition_types(db, definition);
@ -340,14 +409,14 @@ fn definition_expression_type<'db>(
/// The type will be a union if there are multiple bindings with different types.
fn symbol_from_bindings<'db>(
db: &'db dyn Db,
lookup: SymbolLookup,
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
requires_explicit_reexport: RequiresExplicitReExport,
) -> Symbol<'db> {
let visibility_constraints = bindings_with_constraints.visibility_constraints;
let mut bindings_with_constraints = bindings_with_constraints.peekable();
let is_non_exported = |binding: Definition<'db>| {
lookup.is_external() && !binding.is_reexported(db) && binding.in_stub(db)
requires_explicit_reexport.is_yes() && !binding.is_reexported(db)
};
let unbound_visibility = match bindings_with_constraints.peek() {
@ -471,14 +540,14 @@ type SymbolFromDeclarationsResult<'db> =
/// [`TypeQualifiers`] that have been specified on the declaration(s).
fn symbol_from_declarations<'db>(
db: &'db dyn Db,
lookup: SymbolLookup,
declarations: DeclarationsIterator<'_, 'db>,
requires_explicit_reexport: RequiresExplicitReExport,
) -> SymbolFromDeclarationsResult<'db> {
let visibility_constraints = declarations.visibility_constraints;
let mut declarations = declarations.peekable();
let is_non_exported = |declaration: Definition<'db>| {
lookup.is_external() && !declaration.is_reexported(db) && declaration.in_stub(db)
requires_explicit_reexport.is_yes() && !declaration.is_reexported(db)
};
let undeclared_visibility = match declarations.peek() {
@ -3839,31 +3908,7 @@ impl<'db> ModuleLiteralType<'db> {
}
}
// If it's not found in the global scope, check if it's present as an instance
// on `types.ModuleType` or `builtins.object`.
//
// We do a more limited version of this in `global_symbol_ty`,
// but there are two crucial differences here:
// - If a member is looked up as an attribute, `__init__` is also available
// on the module, but it isn't available as a global from inside the module
// - If a member is looked up as an attribute, members on `builtins.object`
// are also available (because `types.ModuleType` inherits from `object`);
// these attributes are also not available as globals from inside the module.
//
// The same way as in `global_symbol_ty`, however, we need to be careful to
// ignore `__getattr__`. Typeshed has a fake `__getattr__` on `types.ModuleType`
// to help out with dynamic imports; we shouldn't use it for `ModuleLiteral` types
// where we know exactly which module we're dealing with.
global_symbol(db, SymbolLookup::External, self.module(db).file(), name).or_fall_back_to(
db,
|| {
if name == "__getattr__" {
Symbol::Unbound
} else {
KnownClass::ModuleType.to_instance(db).member(db, name)
}
},
)
imported_symbol(db, &self.module(db), name)
}
}
@ -4198,7 +4243,7 @@ impl<'db> Class<'db> {
/// traverse through the MRO until it finds the member.
pub(crate) fn own_class_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
let scope = self.body_scope(db);
symbol(db, SymbolLookup::Internal, scope, name)
symbol(db, scope, name)
}
/// Returns the `name` attribute of an instance of this class.
@ -4340,7 +4385,7 @@ impl<'db> Class<'db> {
let declarations = use_def.public_declarations(symbol_id);
match symbol_from_declarations(db, SymbolLookup::Internal, declarations) {
match symbol_from_declarations(db, declarations, RequiresExplicitReExport::No) {
Ok(SymbolAndQualifiers(Symbol::Type(declared_ty, _), qualifiers)) => {
// The attribute is declared in the class body.
@ -4362,7 +4407,7 @@ impl<'db> Class<'db> {
// in a method, and it could also be *bound* in the class body (and/or in a method).
let bindings = use_def.public_bindings(symbol_id);
let inferred = symbol_from_bindings(db, SymbolLookup::Internal, bindings);
let inferred = symbol_from_bindings(db, bindings, RequiresExplicitReExport::No);
let inferred_ty = inferred.ignore_possibly_unbound();
Self::implicit_instance_attribute(db, body_scope, name, inferred_ty).into()
@ -4980,7 +5025,7 @@ pub(crate) mod tests {
)?;
let bar = system_path_to_file(&db, "src/bar.py")?;
let a = global_symbol(&db, SymbolLookup::Internal, bar, "a");
let a = global_symbol(&db, bar, "a");
assert_eq!(
a.expect_type(),
@ -4999,7 +5044,7 @@ pub(crate) mod tests {
)?;
db.clear_salsa_events();
let a = global_symbol(&db, SymbolLookup::Internal, bar, "a");
let a = global_symbol(&db, bar, "a");
assert_eq!(
a.expect_type(),

View File

@ -67,9 +67,9 @@ use crate::types::{
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, DynamicType,
FunctionType, InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome,
KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind,
SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, SymbolLookup, Truthiness,
TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers,
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
RequiresExplicitReExport, SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers,
Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay,
TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@ -871,22 +871,25 @@ impl<'db> TypeInferenceBuilder<'db> {
let use_def = self.index.use_def_map(binding.file_scope(self.db()));
let declarations = use_def.declarations_at_binding(binding);
let mut bound_ty = ty;
let declared_ty = symbol_from_declarations(self.db(), SymbolLookup::Internal, declarations)
.map(|SymbolAndQualifiers(s, _)| s.ignore_possibly_unbound().unwrap_or(Type::unknown()))
.unwrap_or_else(|(ty, conflicting)| {
// TODO point out the conflicting declarations in the diagnostic?
let symbol_table = self.index.symbol_table(binding.file_scope(self.db()));
let symbol_name = symbol_table.symbol(binding.symbol(self.db())).name();
self.context.report_lint(
&CONFLICTING_DECLARATIONS,
node,
format_args!(
"Conflicting declared types for `{symbol_name}`: {}",
conflicting.display(self.db())
),
);
ty.inner_type()
});
let declared_ty =
symbol_from_declarations(self.db(), declarations, RequiresExplicitReExport::No)
.map(|SymbolAndQualifiers(s, _)| {
s.ignore_possibly_unbound().unwrap_or(Type::unknown())
})
.unwrap_or_else(|(ty, conflicting)| {
// TODO point out the conflicting declarations in the diagnostic?
let symbol_table = self.index.symbol_table(binding.file_scope(self.db()));
let symbol_name = symbol_table.symbol(binding.symbol(self.db())).name();
self.context.report_lint(
&CONFLICTING_DECLARATIONS,
node,
format_args!(
"Conflicting declared types for `{symbol_name}`: {}",
conflicting.display(self.db())
),
);
ty.inner_type()
});
if !bound_ty.is_assignable_to(self.db(), declared_ty) {
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
// allow declarations to override inference in case of invalid assignment
@ -906,9 +909,10 @@ impl<'db> TypeInferenceBuilder<'db> {
let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
let prior_bindings = use_def.bindings_at_declaration(declaration);
// unbound_ty is Never because for this check we don't care about unbound
let inferred_ty = symbol_from_bindings(self.db(), SymbolLookup::Internal, prior_bindings)
.ignore_possibly_unbound()
.unwrap_or(Type::Never);
let inferred_ty =
symbol_from_bindings(self.db(), prior_bindings, RequiresExplicitReExport::No)
.ignore_possibly_unbound()
.unwrap_or(Type::Never);
let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_type()) {
ty
} else {
@ -3309,8 +3313,8 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Some(symbol_id) = symbol_table.symbol_id_by_name(symbol_name) {
symbol_from_bindings(
db,
SymbolLookup::Internal,
use_def.public_bindings(symbol_id),
RequiresExplicitReExport::No,
)
} else {
assert!(
@ -3321,7 +3325,11 @@ impl<'db> TypeInferenceBuilder<'db> {
}
} else {
let use_id = name_node.scoped_use_id(db, scope);
symbol_from_bindings(db, SymbolLookup::Internal, use_def.bindings_at_use(use_id))
symbol_from_bindings(
db,
use_def.bindings_at_use(use_id),
RequiresExplicitReExport::No,
)
};
let symbol = local_scope_symbol.or_fall_back_to(db, || {
@ -3372,7 +3380,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// runtime, it is the scope that creates the cell for our closure.) If the name
// isn't bound in that scope, we should get an unbound name, not continue
// falling back to other scopes / globals / builtins.
return symbol(db, SymbolLookup::Internal, enclosing_scope_id, symbol_name);
return symbol(db, enclosing_scope_id, symbol_name);
}
}
@ -3383,7 +3391,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if file_scope_id.is_global() {
Symbol::Unbound
} else {
global_symbol(db, SymbolLookup::Internal, self.file(), symbol_name)
global_symbol(db, self.file(), symbol_name)
}
})
// Not found in globals? Fallback to builtins
@ -6055,7 +6063,7 @@ mod tests {
assert_eq!(scope.name(db), *expected_scope_name);
}
symbol(db, SymbolLookup::Internal, scope, symbol_name)
symbol(db, scope, symbol_name)
}
#[track_caller]
@ -6271,7 +6279,7 @@ mod tests {
])?;
let a = system_path_to_file(&db, "/src/a.py").unwrap();
let x_ty = global_symbol(&db, SymbolLookup::Internal, a, "x").expect_type();
let x_ty = global_symbol(&db, a, "x").expect_type();
assert_eq!(x_ty.display(&db).to_string(), "int");
@ -6280,7 +6288,7 @@ mod tests {
let a = system_path_to_file(&db, "/src/a.py").unwrap();
let x_ty_2 = global_symbol(&db, SymbolLookup::Internal, a, "x").expect_type();
let x_ty_2 = global_symbol(&db, a, "x").expect_type();
assert_eq!(x_ty_2.display(&db).to_string(), "bool");
@ -6297,7 +6305,7 @@ mod tests {
])?;
let a = system_path_to_file(&db, "/src/a.py").unwrap();
let x_ty = global_symbol(&db, SymbolLookup::Internal, a, "x").expect_type();
let x_ty = global_symbol(&db, a, "x").expect_type();
assert_eq!(x_ty.display(&db).to_string(), "int");
@ -6307,7 +6315,7 @@ mod tests {
db.clear_salsa_events();
let x_ty_2 = global_symbol(&db, SymbolLookup::Internal, a, "x").expect_type();
let x_ty_2 = global_symbol(&db, a, "x").expect_type();
assert_eq!(x_ty_2.display(&db).to_string(), "int");
@ -6333,7 +6341,7 @@ mod tests {
])?;
let a = system_path_to_file(&db, "/src/a.py").unwrap();
let x_ty = global_symbol(&db, SymbolLookup::Internal, a, "x").expect_type();
let x_ty = global_symbol(&db, a, "x").expect_type();
assert_eq!(x_ty.display(&db).to_string(), "int");
@ -6343,7 +6351,7 @@ mod tests {
db.clear_salsa_events();
let x_ty_2 = global_symbol(&db, SymbolLookup::Internal, a, "x").expect_type();
let x_ty_2 = global_symbol(&db, a, "x").expect_type();
assert_eq!(x_ty_2.display(&db).to_string(), "int");
@ -6390,7 +6398,7 @@ mod tests {
)?;
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
let attr_ty = global_symbol(&db, SymbolLookup::Internal, file_main, "x").expect_type();
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
@ -6405,7 +6413,7 @@ mod tests {
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, SymbolLookup::Internal, file_main, "x").expect_type();
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
db.take_salsa_events()
};
@ -6424,7 +6432,7 @@ mod tests {
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, SymbolLookup::Internal, file_main, "x").expect_type();
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
db.take_salsa_events()
};

View File

@ -322,13 +322,13 @@ pub(crate) enum ParameterKind<'db> {
mod tests {
use super::*;
use crate::db::tests::{setup_db, TestDb};
use crate::types::{global_symbol, FunctionType, KnownClass, SymbolLookup};
use crate::types::{global_symbol, FunctionType, KnownClass};
use ruff_db::system::DbWithTestSystem;
#[track_caller]
fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> {
let module = ruff_db::files::system_path_to_file(db, file).unwrap();
global_symbol(db, SymbolLookup::Internal, module, "f")
global_symbol(db, module, "f")
.expect_type()
.expect_function_literal()
}