[red-knot] add support for typing_extensions.reveal_type (#13397)

Before `typing.reveal_type` existed, there was
`typing_extensions.reveal_type`. We should support both.

Also adds a test to verify that we can handle aliasing of `reveal_type`
to a different name.

Adds a bit of code to ensure that if we have a union of different
`reveal_type` functions (e.g. a union containing both
`typing_extensions.reveal_type` and `typing.reveal_type`) we still emit
the reveal-type diagnostic only once. This is probably unlikely in
practice, but it doesn't hurt to handle it smoothly. (It comes up now
because we don't support `version_info` checks yet, so
`typing_extensions.reveal_type` is actually that union.)

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Carl Meyer 2024-09-18 21:39:03 -07:00 committed by GitHub
parent 4aca9b91ba
commit 7aae80903c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 66 additions and 6 deletions

View file

@ -762,12 +762,25 @@ impl<'db> CallOutcome<'db> {
} => {
let mut not_callable = vec![];
let mut union_builder = UnionBuilder::new(db);
let mut revealed = false;
for outcome in &**outcomes {
let return_ty = if let Self::NotCallable { not_callable_ty } = outcome {
let return_ty = match outcome {
Self::NotCallable { not_callable_ty } => {
not_callable.push(*not_callable_ty);
Type::Unknown
}
Self::RevealType {
return_ty,
revealed_ty: _,
} => {
if revealed {
*return_ty
} else {
revealed = true;
outcome.unwrap_with_diagnostic(db, node, builder)
}
}
_ => outcome.unwrap_with_diagnostic(db, node, builder),
};
union_builder = union_builder.add(return_ty);
}
@ -841,6 +854,15 @@ impl<'db> FunctionType<'db> {
})
}
/// Return true if this is a symbol with given name from `typing` or `typing_extensions`.
pub(crate) fn is_typing_symbol(self, db: &'db dyn Db, name: &str) -> bool {
name == self.name(db)
&& file_to_module(db, self.definition(db).file(db)).is_some_and(|module| {
module.search_path().is_standard_library()
&& matches!(&**module.name(), "typing" | "typing_extensions")
})
}
pub fn has_decorator(self, db: &dyn Db, decorator: Type<'_>) -> bool {
self.decorators(db).contains(&decorator)
}

View file

@ -705,7 +705,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
let function_type = FunctionType::new(self.db, name.id.clone(), definition, decorator_tys);
let function_ty = if function_type.is_stdlib_symbol(self.db, "typing", "reveal_type") {
let function_ty = if function_type.is_typing_symbol(self.db, "reveal_type") {
Type::RevealTypeFunction(function_type)
} else {
Type::Function(function_type)
@ -2761,6 +2761,44 @@ mod tests {
Ok(())
}
#[test]
fn reveal_type_aliased() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_dedented(
"/src/a.py",
"
from typing import reveal_type as rt
x = 1
rt(x)
",
)?;
assert_file_diagnostics(&db, "/src/a.py", &["Revealed type is 'Literal[1]'."]);
Ok(())
}
#[test]
fn reveal_type_typing_extensions() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_dedented(
"/src/a.py",
"
import typing_extensions
x = 1
typing_extensions.reveal_type(x)
",
)?;
assert_file_diagnostics(&db, "/src/a.py", &["Revealed type is 'Literal[1]'."]);
Ok(())
}
#[test]
fn follow_import_to_class() -> anyhow::Result<()> {
let mut db = setup_db();