[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:
Carl Meyer 2024-11-08 13:23:05 -08:00 committed by GitHub
parent 1430f21283
commit 645ce7e5ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 395 additions and 80 deletions

View file

@ -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),
};
}
}

View file

@ -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))
}
}

View file

@ -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();

View file

@ -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"),

View file

@ -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,
}
}

View file

@ -381,6 +381,7 @@ impl<'db> ClassBase<'db> {
| Type::SubclassOf(_) => None,
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::Literal => None,
KnownInstanceType::TypeVar(_) => None,
},
}
}