Compare commits

...

1 Commits

Author SHA1 Message Date
Carl Meyer
222805e210 [red-knot] qualify function/class name with module/file 2024-07-25 22:18:23 -07:00
5 changed files with 85 additions and 14 deletions

View File

@@ -3,7 +3,7 @@ use std::iter::FusedIterator;
pub use db::{Db, Jar};
pub use module::{Module, ModuleKind};
pub use module_name::ModuleName;
pub use resolver::resolve_module;
pub use resolver::{file_to_module, resolve_module};
use ruff_db::system::SystemPath;
pub use typeshed::{
vendored_typeshed_stubs, TypeshedVersionsParseError, TypeshedVersionsParseErrorKind,

View File

@@ -60,8 +60,9 @@ pub(crate) fn path_to_module(db: &dyn Db, path: &FilePath) -> Option<Module> {
/// Resolves the module for the file with the given id.
///
/// Returns `None` if the file is not a module locatable via any of the known search paths.
#[allow(unreachable_pub)]
#[salsa::tracked]
pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
pub fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
let _span = tracing::trace_span!("file_to_module", ?file).entered();
let path = file.path(db.upcast());

View File

@@ -1,3 +1,4 @@
use red_knot_module_resolver::{file_to_module, Module};
use ruff_db::files::File;
use ruff_python_ast::name::Name;
@@ -181,11 +182,18 @@ pub struct FunctionType<'db> {
/// name of the function at definition
pub name: Name,
/// file in which the function is defined
pub file: File,
/// types of all decorators on this function
decorators: Vec<Type<'db>>,
}
impl<'db> FunctionType<'db> {
pub fn module(self, db: &dyn Db) -> Option<Module> {
file_to_module(db.upcast(), self.file(db))
}
pub fn has_decorator(self, db: &dyn Db, decorator: Type<'_>) -> bool {
self.decorators(db).contains(&decorator)
}
@@ -196,6 +204,9 @@ pub struct ClassType<'db> {
/// Name of the class at definition
pub name: Name,
/// file in which the class is defined
pub file: File,
/// Types of all class bases
bases: Vec<Type<'db>>,
@@ -203,6 +214,10 @@ pub struct ClassType<'db> {
}
impl<'db> ClassType<'db> {
pub fn module(self, db: &dyn Db) -> Option<Module> {
file_to_module(db.upcast(), self.file(db))
}
/// Returns the class member of this class named `name`.
///
/// The member resolves to a member of the class itself or any of its bases.

View File

@@ -28,10 +28,31 @@ impl Display for DisplayType<'_> {
Type::Module(file) => {
write!(f, "<module '{:?}'>", file.path(self.db.upcast()))
}
// TODO functions and classes should display using a fully qualified name
Type::Class(class) => write!(f, "Literal[{}]", class.name(self.db)),
Type::Class(class) => {
if let Some(module) = class.module(self.db) {
write!(f, "Literal[{}.{}]", module.name(), class.name(self.db))
} else {
write!(
f,
"Literal[<{}>.{}]",
class.file(self.db).path(self.db.upcast()).as_ref(),
class.name(self.db)
)
}
}
Type::Instance(class) => f.write_str(&class.name(self.db)),
Type::Function(function) => write!(f, "Literal[{}]", function.name(self.db)),
Type::Function(function) => {
if let Some(module) = function.module(self.db) {
write!(f, "Literal[{}.{}]", module.name(), function.name(self.db))
} else {
write!(
f,
"Literal[<{}>.{}]",
function.file(self.db).path(self.db.upcast()).as_ref(),
function.name(self.db)
)
}
}
Type::Union(union) => union.display(self.db).fmt(f),
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
Type::IntLiteral(n) => write!(f, "Literal[{n}]"),

View File

@@ -397,8 +397,12 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_optional_expression(returns.as_deref());
}
let function_ty =
Type::Function(FunctionType::new(self.db, name.id.clone(), decorator_tys));
let function_ty = Type::Function(FunctionType::new(
self.db,
name.id.clone(),
self.file,
decorator_tys,
));
self.types.definitions.insert(definition, function_ty);
}
@@ -473,7 +477,13 @@ impl<'db> TypeInferenceBuilder<'db> {
.node_scope(NodeWithScopeRef::Class(class))
.to_scope_id(self.db, self.file);
let class_ty = Type::Class(ClassType::new(self.db, name.id.clone(), bases, body_scope));
let class_ty = Type::Class(ClassType::new(
self.db,
name.id.clone(),
self.file,
bases,
body_scope,
));
self.types.definitions.insert(definition, class_ty);
}
@@ -1554,7 +1564,7 @@ mod tests {
("src/b.py", "class C: pass"),
])?;
assert_public_ty(&db, "src/a.py", "E", "Literal[C]");
assert_public_ty(&db, "src/a.py", "E", "Literal[b.C]");
Ok(())
}
@@ -1587,7 +1597,31 @@ mod tests {
.map(|base_ty| format!("{}", base_ty.display(&db)))
.collect();
assert_eq!(base_names, vec!["Literal[Base]"]);
assert_eq!(base_names, vec!["Literal[mod.Base]"]);
Ok(())
}
#[test]
fn class_name_not_in_module() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_file("/src/a.py", "")?; // we have to create /src or the resolver errors
db.write_file("/not-a-module", "class C: pass")?;
assert_public_ty(&db, "not-a-module", "C", "Literal[</not-a-module>.C]");
Ok(())
}
#[test]
fn function_name_not_in_module() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_file("/src/a.py", "")?; // we have to create /src or the resolver errors
db.write_file("/not-a-module", "def f(): pass")?;
assert_public_ty(&db, "not-a-module", "f", "Literal[</not-a-module>.f]");
Ok(())
}
@@ -1631,7 +1665,7 @@ mod tests {
("src/b.py", "class C: pass"),
])?;
assert_public_ty(&db, "src/a.py", "D", "Literal[C]");
assert_public_ty(&db, "src/a.py", "D", "Literal[b.C]");
Ok(())
}
@@ -2095,7 +2129,7 @@ mod tests {
db.write_file("/src/a.py", "c = copyright")?;
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
assert_public_ty(&db, "/src/a.py", "c", "Literal[builtins.copyright]");
Ok(())
}
@@ -2113,7 +2147,7 @@ mod tests {
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
])?;
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
assert_public_ty(&db, "/src/a.py", "c", "Literal[builtins.copyright]");
Ok(())
}
@@ -2150,7 +2184,7 @@ mod tests {
db.write_file("/src/a.py", "import builtins; x = builtins.copyright")?;
assert_public_ty(&db, "/src/a.py", "x", "Literal[copyright]");
assert_public_ty(&db, "/src/a.py", "x", "Literal[builtins.copyright]");
// imported builtins module is the same file as the implicit builtins
let file = system_path_to_file(&db, "/src/a.py").expect("Expected file to exist.");
let builtins_ty = global_symbol_ty_by_name(&db, file, "builtins");