mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
[red-knot] infer types for PEP695 typevars (#14182)
## Summary Create definitions and infer types for PEP 695 type variables. This just gives us the type of the type variable itself (the type of `T` as a runtime object in the body of `def f[T](): ...`), with special handling for its attributes `__name__`, `__bound__`, `__constraints__`, and `__default__`. Mostly the support for these attributes exists because it is easy to implement and allows testing that we are internally representing the typevar correctly. This PR doesn't yet have support for interpreting a typevar as a type annotation, which is of course the primary use of a typevar. But the information we store in the typevar's type in this PR gives us everything we need to handle it correctly in a future PR when the typevar appears in an annotation. ## Test Plan Added mdtest.
This commit is contained in:
parent
1430f21283
commit
645ce7e5ec
8 changed files with 395 additions and 80 deletions
|
@ -373,6 +373,11 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
if let Some(default) = default {
|
||||
self.visit_expr(default);
|
||||
}
|
||||
match type_param {
|
||||
ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,6 +92,9 @@ pub(crate) enum DefinitionNodeRef<'a> {
|
|||
WithItem(WithItemDefinitionNodeRef<'a>),
|
||||
MatchPattern(MatchPatternDefinitionNodeRef<'a>),
|
||||
ExceptHandler(ExceptHandlerDefinitionNodeRef<'a>),
|
||||
TypeVar(&'a ast::TypeParamTypeVar),
|
||||
ParamSpec(&'a ast::TypeParamParamSpec),
|
||||
TypeVarTuple(&'a ast::TypeParamTypeVarTuple),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> {
|
||||
|
@ -130,6 +133,24 @@ impl<'a> From<&'a ast::Alias> for DefinitionNodeRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TypeParamTypeVar> for DefinitionNodeRef<'a> {
|
||||
fn from(value: &'a ast::TypeParamTypeVar) -> Self {
|
||||
Self::TypeVar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TypeParamParamSpec> for DefinitionNodeRef<'a> {
|
||||
fn from(value: &'a ast::TypeParamParamSpec) -> Self {
|
||||
Self::ParamSpec(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TypeParamTypeVarTuple> for DefinitionNodeRef<'a> {
|
||||
fn from(value: &'a ast::TypeParamTypeVarTuple) -> Self {
|
||||
Self::TypeVarTuple(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ImportFromDefinitionNodeRef<'a>> for DefinitionNodeRef<'a> {
|
||||
fn from(node_ref: ImportFromDefinitionNodeRef<'a>) -> Self {
|
||||
Self::ImportFrom(node_ref)
|
||||
|
@ -317,6 +338,15 @@ impl<'db> DefinitionNodeRef<'db> {
|
|||
handler: AstNodeRef::new(parsed, handler),
|
||||
is_star,
|
||||
}),
|
||||
DefinitionNodeRef::TypeVar(node) => {
|
||||
DefinitionKind::TypeVar(AstNodeRef::new(parsed, node))
|
||||
}
|
||||
DefinitionNodeRef::ParamSpec(node) => {
|
||||
DefinitionKind::ParamSpec(AstNodeRef::new(parsed, node))
|
||||
}
|
||||
DefinitionNodeRef::TypeVarTuple(node) => {
|
||||
DefinitionKind::TypeVarTuple(AstNodeRef::new(parsed, node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,6 +386,9 @@ impl<'db> DefinitionNodeRef<'db> {
|
|||
identifier.into()
|
||||
}
|
||||
Self::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler, .. }) => handler.into(),
|
||||
Self::TypeVar(node) => node.into(),
|
||||
Self::ParamSpec(node) => node.into(),
|
||||
Self::TypeVarTuple(node) => node.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -412,6 +445,9 @@ pub enum DefinitionKind<'db> {
|
|||
WithItem(WithItemDefinitionKind),
|
||||
MatchPattern(MatchPatternDefinitionKind),
|
||||
ExceptHandler(ExceptHandlerDefinitionKind),
|
||||
TypeVar(AstNodeRef<ast::TypeParamTypeVar>),
|
||||
ParamSpec(AstNodeRef<ast::TypeParamParamSpec>),
|
||||
TypeVarTuple(AstNodeRef<ast::TypeParamTypeVarTuple>),
|
||||
}
|
||||
|
||||
impl DefinitionKind<'_> {
|
||||
|
@ -421,7 +457,10 @@ impl DefinitionKind<'_> {
|
|||
DefinitionKind::Function(_)
|
||||
| DefinitionKind::Class(_)
|
||||
| DefinitionKind::Import(_)
|
||||
| DefinitionKind::ImportFrom(_) => DefinitionCategory::DeclarationAndBinding,
|
||||
| DefinitionKind::ImportFrom(_)
|
||||
| DefinitionKind::TypeVar(_)
|
||||
| DefinitionKind::ParamSpec(_)
|
||||
| DefinitionKind::TypeVarTuple(_) => DefinitionCategory::DeclarationAndBinding,
|
||||
// a parameter always binds a value, but is only a declaration if annotated
|
||||
DefinitionKind::Parameter(parameter) => {
|
||||
if parameter.annotation.is_some() {
|
||||
|
@ -696,3 +735,21 @@ impl From<&ast::ExceptHandlerExceptHandler> for DefinitionNodeKey {
|
|||
Self(NodeKey::from_node(handler))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::TypeParamTypeVar> for DefinitionNodeKey {
|
||||
fn from(value: &ast::TypeParamTypeVar) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::TypeParamParamSpec> for DefinitionNodeKey {
|
||||
fn from(value: &ast::TypeParamParamSpec) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey {
|
||||
fn from(value: &ast::TypeParamTypeVarTuple) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,7 +307,7 @@ fn declarations_ty<'db>(
|
|||
}
|
||||
|
||||
/// Representation of a type: a set of possible values at runtime.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub enum Type<'db> {
|
||||
/// The dynamic type: a statically unknown set of values
|
||||
Any,
|
||||
|
@ -336,7 +336,7 @@ pub enum Type<'db> {
|
|||
/// The set of Python objects with the given class in their __class__'s method resolution order
|
||||
Instance(InstanceType<'db>),
|
||||
/// A single Python object that requires special treatment in the type system
|
||||
KnownInstance(KnownInstanceType),
|
||||
KnownInstance(KnownInstanceType<'db>),
|
||||
/// The set of objects in any of the types in the union
|
||||
Union(UnionType<'db>),
|
||||
/// The set of objects in all of the types in the intersection
|
||||
|
@ -657,17 +657,23 @@ impl<'db> Type<'db> {
|
|||
// TODO: Once we have support for final classes, we can establish that
|
||||
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
|
||||
|
||||
// TODO: The following is a workaround that is required to unify the two different
|
||||
// versions of `NoneType` in typeshed. This should not be required anymore once we
|
||||
// understand `sys.version_info` branches.
|
||||
// TODO: The following is a workaround that is required to unify the two different versions
|
||||
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
|
||||
// we understand `sys.version_info` branches.
|
||||
self == other
|
||||
|| matches!((self, other),
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType { class: target_class })
|
||||
)
|
||||
if self_class.is_known(db, KnownClass::NoneType) &&
|
||||
target_class.is_known(db, KnownClass::NoneType))
|
||||
if (
|
||||
self_class.is_known(db, KnownClass::NoneType) &&
|
||||
target_class.is_known(db, KnownClass::NoneType)
|
||||
) || (
|
||||
self_class.is_known(db, KnownClass::NoDefaultType) &&
|
||||
target_class.is_known(db, KnownClass::NoDefaultType)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Return true if this type and `other` have no common elements.
|
||||
|
@ -907,8 +913,11 @@ impl<'db> Type<'db> {
|
|||
| Type::ModuleLiteral(..)
|
||||
| Type::KnownInstance(..) => true,
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
// TODO some more instance types can be singleton types (EllipsisType, NotImplementedType)
|
||||
matches!(class.known(db), Some(KnownClass::NoneType))
|
||||
if let Some(known_class) = class.known(db) {
|
||||
known_class.is_singleton()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Type::Tuple(..) => {
|
||||
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
|
||||
|
@ -961,7 +970,7 @@ impl<'db> Type<'db> {
|
|||
.all(|elem| elem.is_single_valued(db)),
|
||||
|
||||
Type::Instance(InstanceType { class }) => match class.known(db) {
|
||||
Some(KnownClass::NoneType) => true,
|
||||
Some(KnownClass::NoneType | KnownClass::NoDefaultType) => true,
|
||||
Some(
|
||||
KnownClass::Bool
|
||||
| KnownClass::Object
|
||||
|
@ -978,7 +987,8 @@ impl<'db> Type<'db> {
|
|||
| KnownClass::GenericAlias
|
||||
| KnownClass::ModuleType
|
||||
| KnownClass::FunctionType
|
||||
| KnownClass::SpecialForm,
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::TypeVar,
|
||||
) => false,
|
||||
None => false,
|
||||
},
|
||||
|
@ -1049,9 +1059,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
Type::ClassLiteral(class_ty) => class_ty.member(db, name),
|
||||
Type::SubclassOf(subclass_of_ty) => subclass_of_ty.member(db, name),
|
||||
Type::KnownInstance(known_instance) => {
|
||||
known_instance.instance_fallback(db).member(db, name)
|
||||
}
|
||||
Type::KnownInstance(known_instance) => known_instance.member(db, name),
|
||||
Type::Instance(_) => {
|
||||
// TODO MRO? get_own_instance_member, get_instance_member
|
||||
Type::Todo.into()
|
||||
|
@ -1442,7 +1450,7 @@ impl<'db> Type<'db> {
|
|||
Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db),
|
||||
Type::StringLiteral(_) | Type::LiteralString => *self,
|
||||
Type::KnownInstance(known_instance) => {
|
||||
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr()))
|
||||
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr(db)))
|
||||
}
|
||||
// TODO: handle more complex types
|
||||
_ => KnownClass::Str.to_instance(db),
|
||||
|
@ -1464,7 +1472,7 @@ impl<'db> Type<'db> {
|
|||
})),
|
||||
Type::LiteralString => Type::LiteralString,
|
||||
Type::KnownInstance(known_instance) => {
|
||||
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr()))
|
||||
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr(db)))
|
||||
}
|
||||
// TODO: handle more complex types
|
||||
_ => KnownClass::Str.to_instance(db),
|
||||
|
@ -1514,7 +1522,10 @@ pub enum KnownClass {
|
|||
FunctionType,
|
||||
// Typeshed
|
||||
NoneType, // Part of `types` for Python >= 3.10
|
||||
// Typing
|
||||
SpecialForm,
|
||||
TypeVar,
|
||||
NoDefaultType,
|
||||
}
|
||||
|
||||
impl<'db> KnownClass {
|
||||
|
@ -1537,6 +1548,8 @@ impl<'db> KnownClass {
|
|||
Self::FunctionType => "FunctionType",
|
||||
Self::NoneType => "NoneType",
|
||||
Self::SpecialForm => "_SpecialForm",
|
||||
Self::TypeVar => "TypeVar",
|
||||
Self::NoDefaultType => "_NoDefaultType",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1561,8 +1574,34 @@ impl<'db> KnownClass {
|
|||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => {
|
||||
types_symbol(db, self.as_str()).unwrap_or_unknown()
|
||||
}
|
||||
Self::SpecialForm => typing_symbol(db, self.as_str()).unwrap_or_unknown(),
|
||||
Self::NoneType => typeshed_symbol(db, self.as_str()).unwrap_or_unknown(),
|
||||
Self::SpecialForm => typing_symbol(db, self.as_str()).unwrap_or_unknown(),
|
||||
Self::TypeVar => typing_symbol(db, self.as_str()).unwrap_or_unknown(),
|
||||
Self::NoDefaultType => typing_extensions_symbol(db, self.as_str()).unwrap_or_unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_singleton(self) -> bool {
|
||||
// TODO there are other singleton types (EllipsisType, NotImplementedType)
|
||||
match self {
|
||||
Self::NoneType | Self::NoDefaultType => true,
|
||||
Self::Bool
|
||||
| Self::Object
|
||||
| Self::Bytes
|
||||
| Self::Tuple
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::Str
|
||||
| Self::Set
|
||||
| Self::Dict
|
||||
| Self::List
|
||||
| Self::Type
|
||||
| Self::Slice
|
||||
| Self::GenericAlias
|
||||
| Self::ModuleType
|
||||
| Self::FunctionType
|
||||
| Self::SpecialForm
|
||||
| Self::TypeVar => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1597,6 +1636,7 @@ impl<'db> KnownClass {
|
|||
"ModuleType" => Some(Self::ModuleType),
|
||||
"FunctionType" => Some(Self::FunctionType),
|
||||
"_SpecialForm" => Some(Self::SpecialForm),
|
||||
"_NoDefaultType" => Some(Self::NoDefaultType),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1621,7 +1661,7 @@ impl<'db> KnownClass {
|
|||
| Self::Slice => module.name() == "builtins",
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => module.name() == "types",
|
||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||
Self::SpecialForm => {
|
||||
Self::SpecialForm | Self::TypeVar | Self::NoDefaultType => {
|
||||
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
||||
}
|
||||
}
|
||||
|
@ -1629,17 +1669,20 @@ impl<'db> KnownClass {
|
|||
}
|
||||
|
||||
/// Enumeration of specific runtime that are special enough to be considered their own type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum KnownInstanceType {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub enum KnownInstanceType<'db> {
|
||||
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
|
||||
Literal,
|
||||
/// A single instance of `typing.TypeVar`
|
||||
TypeVar(TypeVarInstance<'db>),
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
}
|
||||
|
||||
impl KnownInstanceType {
|
||||
impl<'db> KnownInstanceType<'db> {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
KnownInstanceType::Literal => "Literal",
|
||||
KnownInstanceType::TypeVar(_) => "TypeVar",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1647,13 +1690,15 @@ impl KnownInstanceType {
|
|||
pub const fn bool(self) -> Truthiness {
|
||||
match self {
|
||||
Self::Literal => Truthiness::AlwaysTrue,
|
||||
Self::TypeVar(_) => Truthiness::AlwaysTrue,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the repr of the symbol at runtime
|
||||
pub const fn repr(self) -> &'static str {
|
||||
pub fn repr(self, db: &'db dyn Db) -> &'db str {
|
||||
match self {
|
||||
Self::Literal => "typing.Literal",
|
||||
Self::TypeVar(typevar) => typevar.name(db),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1661,6 +1706,7 @@ impl KnownInstanceType {
|
|||
pub const fn class(self) -> KnownClass {
|
||||
match self {
|
||||
Self::Literal => KnownClass::SpecialForm,
|
||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1682,6 +1728,93 @@ impl KnownInstanceType {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
|
||||
match (self, name) {
|
||||
(Self::TypeVar(typevar), "__name__") => Symbol::Type(
|
||||
Type::StringLiteral(StringLiteralType::new(db, typevar.name(db).as_str())),
|
||||
Boundness::Bound,
|
||||
),
|
||||
(Self::TypeVar(typevar), "__bound__") => Symbol::Type(
|
||||
typevar
|
||||
.upper_bound(db)
|
||||
.map(|ty| ty.to_meta_type(db))
|
||||
.unwrap_or(KnownClass::NoneType.to_instance(db)),
|
||||
Boundness::Bound,
|
||||
),
|
||||
(Self::TypeVar(typevar), "__constraints__") => Symbol::Type(
|
||||
Type::Tuple(TupleType::new(
|
||||
db,
|
||||
typevar
|
||||
.constraints(db)
|
||||
.map(|constraints| {
|
||||
constraints
|
||||
.iter()
|
||||
.map(|ty| ty.to_meta_type(db))
|
||||
.collect::<Box<_>>()
|
||||
})
|
||||
.unwrap_or_else(|| std::iter::empty().collect::<Box<_>>()),
|
||||
)),
|
||||
Boundness::Bound,
|
||||
),
|
||||
(Self::TypeVar(typevar), "__default__") => Symbol::Type(
|
||||
typevar
|
||||
.default_ty(db)
|
||||
.map(|ty| ty.to_meta_type(db))
|
||||
.unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)),
|
||||
Boundness::Bound,
|
||||
),
|
||||
_ => self.instance_fallback(db).member(db, name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data regarding a single type variable.
|
||||
///
|
||||
/// This is referenced by `KnownInstanceType::TypeVar` (to represent the singleton type of the
|
||||
/// runtime `typing.TypeVar` object itself). In the future, it will also be referenced also by a
|
||||
/// new `Type` variant to represent the type that this typevar represents as an annotation: that
|
||||
/// is, an unknown set of objects, constrained by the upper-bound/constraints on this type var,
|
||||
/// defaulting to the default type of this type var when not otherwise bound to a type.
|
||||
///
|
||||
/// This must be a tracked struct, not an interned one, because typevar equivalence is by identity,
|
||||
/// not by value. Two typevars that have the same name, bound/constraints, and default, are still
|
||||
/// different typevars: if used in the same scope, they may be bound to different types.
|
||||
#[salsa::tracked]
|
||||
pub struct TypeVarInstance<'db> {
|
||||
/// The name of this TypeVar (e.g. `T`)
|
||||
#[return_ref]
|
||||
name: ast::name::Name,
|
||||
|
||||
/// The upper bound or constraint on the type of this TypeVar
|
||||
bound_or_constraints: Option<TypeVarBoundOrConstraints<'db>>,
|
||||
|
||||
/// The default type for this TypeVar
|
||||
default_ty: Option<Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> TypeVarInstance<'db> {
|
||||
pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) {
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&[Type<'db>]> {
|
||||
if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) {
|
||||
Some(tuple.elements(db))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)]
|
||||
pub enum TypeVarBoundOrConstraints<'db> {
|
||||
UpperBound(Type<'db>),
|
||||
Constraints(TupleType<'db>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -2499,7 +2632,7 @@ impl<T: Hash + Eq> SeenSet<T> {
|
|||
}
|
||||
|
||||
/// A singleton type representing a single class object at runtime.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub struct ClassLiteralType<'db> {
|
||||
class: Class<'db>,
|
||||
}
|
||||
|
@ -2521,7 +2654,7 @@ impl<'db> From<ClassLiteralType<'db>> for Type<'db> {
|
|||
}
|
||||
|
||||
/// A type that represents `type[C]`, i.e. the class literal `C` and class literals that are subclasses of `C`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub struct SubclassOfType<'db> {
|
||||
class: Class<'db>,
|
||||
}
|
||||
|
@ -2533,7 +2666,7 @@ impl<'db> SubclassOfType<'db> {
|
|||
}
|
||||
|
||||
/// A type representing the set of runtime objects which are instances of a certain class.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
||||
pub struct InstanceType<'db> {
|
||||
class: Class<'db>,
|
||||
}
|
||||
|
@ -2746,9 +2879,10 @@ mod tests {
|
|||
// BuiltinInstance("str") corresponds to an instance of the builtin `str` class
|
||||
BuiltinInstance(&'static str),
|
||||
TypingInstance(&'static str),
|
||||
KnownInstance(KnownInstanceType),
|
||||
TypingLiteral,
|
||||
// BuiltinClassLiteral("str") corresponds to the builtin `str` class object itself
|
||||
BuiltinClassLiteral(&'static str),
|
||||
KnownClassInstance(KnownClass),
|
||||
Union(Vec<Ty>),
|
||||
Intersection { pos: Vec<Ty>, neg: Vec<Ty> },
|
||||
Tuple(Vec<Ty>),
|
||||
|
@ -2769,8 +2903,9 @@ mod tests {
|
|||
Ty::BytesLiteral(s) => Type::BytesLiteral(BytesLiteralType::new(db, s.as_bytes())),
|
||||
Ty::BuiltinInstance(s) => builtins_symbol(db, s).expect_type().to_instance(db),
|
||||
Ty::TypingInstance(s) => typing_symbol(db, s).expect_type().to_instance(db),
|
||||
Ty::KnownInstance(known_instance) => Type::KnownInstance(known_instance),
|
||||
Ty::TypingLiteral => Type::KnownInstance(KnownInstanceType::Literal),
|
||||
Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).expect_type(),
|
||||
Ty::KnownClassInstance(known_class) => known_class.to_instance(db),
|
||||
Ty::Union(tys) => {
|
||||
UnionType::from_elements(db, tys.into_iter().map(|ty| ty.into_type(db)))
|
||||
}
|
||||
|
@ -2876,14 +3011,8 @@ mod tests {
|
|||
#[test_case(Ty::Intersection{pos: vec![Ty::BuiltinInstance("str")], neg: vec![Ty::StringLiteral("foo")]}, Ty::Intersection{pos: vec![], neg: vec![Ty::IntLiteral(2)]})]
|
||||
#[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinClassLiteral("int"))]
|
||||
#[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinInstance("object"))]
|
||||
#[test_case(
|
||||
Ty::KnownInstance(KnownInstanceType::Literal),
|
||||
Ty::TypingInstance("_SpecialForm")
|
||||
)]
|
||||
#[test_case(
|
||||
Ty::KnownInstance(KnownInstanceType::Literal),
|
||||
Ty::BuiltinInstance("object")
|
||||
)]
|
||||
#[test_case(Ty::TypingLiteral, Ty::TypingInstance("_SpecialForm"))]
|
||||
#[test_case(Ty::TypingLiteral, Ty::BuiltinInstance("object"))]
|
||||
fn is_subtype_of(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
|
@ -3126,6 +3255,7 @@ mod tests {
|
|||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::BooleanLiteral(false))]
|
||||
#[test_case(Ty::KnownClassInstance(KnownClass::NoDefaultType))]
|
||||
fn is_singleton(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
|
|
|
@ -71,6 +71,11 @@ impl Display for DisplayRepresentation<'_> {
|
|||
{
|
||||
f.write_str("None")
|
||||
}
|
||||
Type::Instance(InstanceType { class })
|
||||
if class.is_known(self.db, KnownClass::NoDefaultType) =>
|
||||
{
|
||||
f.write_str("NoDefault")
|
||||
}
|
||||
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
||||
// any other type
|
||||
Type::Todo => f.write_str("@Todo"),
|
||||
|
|
|
@ -59,7 +59,8 @@ use crate::types::{
|
|||
Boundness, BytesLiteralType, Class, ClassLiteralType, FunctionType, InstanceType,
|
||||
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
|
||||
KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, StringLiteralType,
|
||||
Symbol, Truthiness, TupleType, Type, TypeArrayDisplay, UnionBuilder, UnionType,
|
||||
Symbol, Truthiness, TupleType, Type, TypeArrayDisplay, TypeVarBoundOrConstraints,
|
||||
TypeVarInstance, UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
|
@ -642,6 +643,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
DefinitionKind::ExceptHandler(except_handler_definition) => {
|
||||
self.infer_except_handler_definition(except_handler_definition, definition);
|
||||
}
|
||||
DefinitionKind::TypeVar(node) => {
|
||||
self.infer_typevar_definition(node, definition);
|
||||
}
|
||||
DefinitionKind::ParamSpec(node) => {
|
||||
self.infer_paramspec_definition(node, definition);
|
||||
}
|
||||
DefinitionKind::TypeVarTuple(node) => {
|
||||
self.infer_typevartuple_definition(node, definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1352,6 +1362,82 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
);
|
||||
}
|
||||
|
||||
fn infer_typevar_definition(
|
||||
&mut self,
|
||||
node: &ast::TypeParamTypeVar,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let ast::TypeParamTypeVar {
|
||||
range: _,
|
||||
name,
|
||||
bound,
|
||||
default,
|
||||
} = node;
|
||||
let bound_or_constraint = match bound.as_deref() {
|
||||
Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => {
|
||||
if elts.len() < 2 {
|
||||
self.diagnostics.add(
|
||||
expr.into(),
|
||||
"invalid-typevar-constraints",
|
||||
format_args!("TypeVar must have at least two constrained types"),
|
||||
);
|
||||
self.infer_expression(expr);
|
||||
None
|
||||
} else {
|
||||
let tuple = TupleType::new(
|
||||
self.db,
|
||||
elts.iter()
|
||||
.map(|expr| self.infer_type_expression(expr))
|
||||
.collect::<Box<_>>(),
|
||||
);
|
||||
let constraints = TypeVarBoundOrConstraints::Constraints(tuple);
|
||||
self.store_expression_type(expr, Type::Tuple(tuple));
|
||||
Some(constraints)
|
||||
}
|
||||
}
|
||||
Some(expr) => Some(TypeVarBoundOrConstraints::UpperBound(
|
||||
self.infer_type_expression(expr),
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
let default_ty = self.infer_optional_type_expression(default.as_deref());
|
||||
let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
|
||||
self.db,
|
||||
name.id.clone(),
|
||||
bound_or_constraint,
|
||||
default_ty,
|
||||
)));
|
||||
self.add_declaration_with_binding(node.into(), definition, ty, ty);
|
||||
}
|
||||
|
||||
fn infer_paramspec_definition(
|
||||
&mut self,
|
||||
node: &ast::TypeParamParamSpec,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let ast::TypeParamParamSpec {
|
||||
range: _,
|
||||
name: _,
|
||||
default,
|
||||
} = node;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo);
|
||||
}
|
||||
|
||||
fn infer_typevartuple_definition(
|
||||
&mut self,
|
||||
node: &ast::TypeParamTypeVarTuple,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let ast::TypeParamTypeVarTuple {
|
||||
range: _,
|
||||
name: _,
|
||||
default,
|
||||
} = node;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo);
|
||||
}
|
||||
|
||||
fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) {
|
||||
let ast::StmtMatch {
|
||||
range: _,
|
||||
|
@ -2013,13 +2099,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
expression.map(|expr| self.infer_expression(expr))
|
||||
}
|
||||
|
||||
fn infer_optional_annotation_expression(
|
||||
&mut self,
|
||||
expr: Option<&ast::Expr>,
|
||||
) -> Option<Type<'db>> {
|
||||
expr.map(|expr| self.infer_annotation_expression(expr))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn infer_expression(&mut self, expression: &ast::Expr) -> Type<'db> {
|
||||
debug_assert_eq!(
|
||||
|
@ -3912,32 +3991,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} = type_parameters;
|
||||
for type_param in type_params {
|
||||
match type_param {
|
||||
ast::TypeParam::TypeVar(typevar) => {
|
||||
let ast::TypeParamTypeVar {
|
||||
range: _,
|
||||
name: _,
|
||||
bound,
|
||||
default,
|
||||
} = typevar;
|
||||
self.infer_optional_expression(bound.as_deref());
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
}
|
||||
ast::TypeParam::ParamSpec(param_spec) => {
|
||||
let ast::TypeParamParamSpec {
|
||||
range: _,
|
||||
name: _,
|
||||
default,
|
||||
} = param_spec;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
}
|
||||
ast::TypeParam::TypeVarTuple(typevar_tuple) => {
|
||||
let ast::TypeParamTypeVarTuple {
|
||||
range: _,
|
||||
name: _,
|
||||
default,
|
||||
} = typevar_tuple;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
}
|
||||
ast::TypeParam::TypeVar(node) => self.infer_definition(node),
|
||||
ast::TypeParam::ParamSpec(node) => self.infer_definition(node),
|
||||
ast::TypeParam::TypeVarTuple(node) => self.infer_definition(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3971,6 +4027,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.store_expression_type(expression, annotation_ty);
|
||||
annotation_ty
|
||||
}
|
||||
|
||||
fn infer_optional_annotation_expression(
|
||||
&mut self,
|
||||
expr: Option<&ast::Expr>,
|
||||
) -> Option<Type<'db>> {
|
||||
expr.map(|expr| self.infer_annotation_expression(expr))
|
||||
}
|
||||
}
|
||||
|
||||
/// Type expressions
|
||||
|
@ -4145,6 +4208,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
ty
|
||||
}
|
||||
|
||||
fn infer_optional_type_expression(
|
||||
&mut self,
|
||||
opt_expression: Option<&ast::Expr>,
|
||||
) -> Option<Type<'db>> {
|
||||
opt_expression.map(|expr| self.infer_type_expression(expr))
|
||||
}
|
||||
|
||||
/// Given the slice of a `tuple[]` annotation, return the type that the annotation represents
|
||||
fn infer_tuple_type_expression(&mut self, tuple_slice: &ast::Expr) -> Type<'db> {
|
||||
/// In most cases, if a subelement of the tuple is inferred as `Todo`,
|
||||
|
@ -4262,6 +4332,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Type::Unknown
|
||||
}
|
||||
},
|
||||
KnownInstanceType::TypeVar(_) => Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -381,6 +381,7 @@ impl<'db> ClassBase<'db> {
|
|||
| Type::SubclassOf(_) => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::Literal => None,
|
||||
KnownInstanceType::TypeVar(_) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue