mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 04:19:18 +00:00
[red-knot] Fallback for typing._NoDefaultType
(#14783)
## Summary `typing_extensions` has a `>=3.13` re-export for the `typing.NoDefault` singleton, but not for `typing._NoDefaultType`. This causes problems as soon as we understand `sys.version_info` branches, so we explicity switch to `typing._NoDefaultType` for Python 3.13 and later. This is a part of #14759 that I thought might make sense to break out and merge in isolation. ## Test Plan New test that will become more meaningful with #12700 --------- Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
bd27bfab5d
commit
2d3f557875
1 changed files with 34 additions and 11 deletions
|
@ -28,7 +28,7 @@ use crate::symbol::{Boundness, Symbol};
|
||||||
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
|
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
|
||||||
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
|
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
|
||||||
use crate::types::narrow::narrowing_constraint;
|
use crate::types::narrow::narrowing_constraint;
|
||||||
use crate::{Db, FxOrderSet, Module, Program};
|
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
mod diagnostic;
|
mod diagnostic;
|
||||||
|
@ -1897,13 +1897,13 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
core_module_symbol(db, self.canonical_module(), self.as_str())
|
core_module_symbol(db, self.canonical_module(db), self.as_str())
|
||||||
.ignore_possibly_unbound()
|
.ignore_possibly_unbound()
|
||||||
.unwrap_or(Type::Unknown)
|
.unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the module in which we should look up the definition for this class
|
/// Return the module in which we should look up the definition for this class
|
||||||
pub(crate) const fn canonical_module(self) -> CoreStdlibModule {
|
pub(crate) fn canonical_module(self, db: &'db dyn Db) -> CoreStdlibModule {
|
||||||
match self {
|
match self {
|
||||||
Self::Bool
|
Self::Bool
|
||||||
| Self::Object
|
| Self::Object
|
||||||
|
@ -1921,10 +1921,18 @@ impl<'db> KnownClass {
|
||||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
|
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
|
||||||
Self::NoneType => CoreStdlibModule::Typeshed,
|
Self::NoneType => CoreStdlibModule::Typeshed,
|
||||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing,
|
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing,
|
||||||
// TODO when we understand sys.version_info, we will need an explicit fallback here,
|
Self::NoDefaultType => {
|
||||||
// because typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
let python_version = Program::get(db).target_version(db);
|
||||||
// singleton, but not for `typing._NoDefaultType`
|
|
||||||
Self::NoDefaultType => CoreStdlibModule::TypingExtensions,
|
// typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||||
|
// singleton, but not for `typing._NoDefaultType`. So we need to switch
|
||||||
|
// to `typing._NoDefaultType` for newer versions:
|
||||||
|
if python_version >= PythonVersion::PY313 {
|
||||||
|
CoreStdlibModule::Typing
|
||||||
|
} else {
|
||||||
|
CoreStdlibModule::TypingExtensions
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1984,11 +1992,11 @@ impl<'db> KnownClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
let module = file_to_module(db, file)?;
|
let module = file_to_module(db, file)?;
|
||||||
candidate.check_module(&module).then_some(candidate)
|
candidate.check_module(db, &module).then_some(candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the module of `self` matches `module_name`
|
/// Return `true` if the module of `self` matches `module_name`
|
||||||
fn check_module(self, module: &Module) -> bool {
|
fn check_module(self, db: &'db dyn Db, module: &Module) -> bool {
|
||||||
if !module.search_path().is_standard_library() {
|
if !module.search_path().is_standard_library() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2008,7 +2016,7 @@ impl<'db> KnownClass {
|
||||||
| Self::GenericAlias
|
| Self::GenericAlias
|
||||||
| Self::ModuleType
|
| Self::ModuleType
|
||||||
| Self::VersionInfo
|
| Self::VersionInfo
|
||||||
| Self::FunctionType => module.name() == self.canonical_module().as_str(),
|
| Self::FunctionType => module.name() == self.canonical_module(db).as_str(),
|
||||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
|
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
|
||||||
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
||||||
|
@ -3683,13 +3691,28 @@ pub(crate) mod tests {
|
||||||
#[test_case(Ty::None)]
|
#[test_case(Ty::None)]
|
||||||
#[test_case(Ty::BooleanLiteral(true))]
|
#[test_case(Ty::BooleanLiteral(true))]
|
||||||
#[test_case(Ty::BooleanLiteral(false))]
|
#[test_case(Ty::BooleanLiteral(false))]
|
||||||
#[test_case(Ty::KnownClassInstance(KnownClass::NoDefaultType))]
|
|
||||||
fn is_singleton(from: Ty) {
|
fn is_singleton(from: Ty) {
|
||||||
let db = setup_db();
|
let db = setup_db();
|
||||||
|
|
||||||
assert!(from.into_type(&db).is_singleton(&db));
|
assert!(from.into_type(&db).is_singleton(&db));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
|
||||||
|
/// the fallback to `typing_extensions` is working correctly.
|
||||||
|
/// See [`KnownClass::canonical_module`] for more information.
|
||||||
|
#[test_case(PythonVersion::PY312)]
|
||||||
|
#[test_case(PythonVersion::PY313)]
|
||||||
|
fn no_default_type_is_singleton(python_version: PythonVersion) {
|
||||||
|
let db = TestDbBuilder::new()
|
||||||
|
.with_python_version(python_version)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let no_default = Ty::KnownClassInstance(KnownClass::NoDefaultType).into_type(&db);
|
||||||
|
|
||||||
|
assert!(no_default.is_singleton(&db));
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(Ty::None)]
|
#[test_case(Ty::None)]
|
||||||
#[test_case(Ty::BooleanLiteral(true))]
|
#[test_case(Ty::BooleanLiteral(true))]
|
||||||
#[test_case(Ty::IntLiteral(1))]
|
#[test_case(Ty::IntLiteral(1))]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue