diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md index 7711a7c3db936..5414312f9fe73 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md @@ -171,7 +171,7 @@ class Config: import generic_a import generic_b -# TODO should be error: [invalid-assignment] "Object of type `` is not assignable to `type[generic_a.Container[int]]`" +# error: [invalid-assignment] "Object of type `` is not assignable to `type[generic_a.Container[int]]`" container: type[generic_a.Container[int]] = generic_b.Container[int] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_of/basic.md b/crates/ty_python_semantic/resources/mdtest/type_of/basic.md index 3a167d528b344..4e3d9e9f07778 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_of/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/type_of/basic.md @@ -174,6 +174,39 @@ def _(x: Foo[int], y: Bar[str], z: list[bytes]): reveal_type(type(z)) # revealed: type[list[bytes]] ``` +## Checking generic `type[]` types + +```toml +[environment] +python-version = "3.12" +``` + +```py +class C[T]: + pass + +class D[T]: + pass + +var: type[C[int]] = C[int] +var: type[C[int]] = D[int] # error: [invalid-assignment] "Object of type `` is not assignable to `type[C[int]]`" +``` + +However, generic `Protocol` classes are still TODO: + +```py +from typing import Protocol + +class Proto[U](Protocol): + def some_method(self): ... + +# TODO: should be error: [invalid-assignment] +var: type[Proto[int]] = C[int] + +def _(p: type[Proto[int]]): + reveal_type(p) # revealed: type[@Todo(type[T] for protocols)] +``` + ## `@final` classes `type[]` types are eagerly converted to class-literal types if a class decorated with `@final` is diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 3ac4f9b65216c..885d3c2e4f194 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -243,13 +243,13 @@ static_assert(is_assignable_to(TypeOf[Bar[int]], type[Foo[int]])) static_assert(is_assignable_to(TypeOf[Bar[bool]], type[Foo[int]])) static_assert(is_assignable_to(TypeOf[Bar], type[Foo[int]])) static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[int]])) +static_assert(is_assignable_to(TypeOf[Bar[Unknown]], type[Foo[int]])) static_assert(is_assignable_to(TypeOf[Bar], type[Foo])) static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[Any]])) static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[int]])) -# TODO: these should pass (all subscripts inside `type[]` type expressions are currently TODO types) -static_assert(not is_assignable_to(TypeOf[Bar[int]], type[Foo[bool]])) # error: [static-assert-error] -static_assert(not is_assignable_to(TypeOf[Foo[bool]], type[Bar[int]])) # error: [static-assert-error] +static_assert(not is_assignable_to(TypeOf[Bar[int]], type[Foo[bool]])) +static_assert(not is_assignable_to(TypeOf[Foo[bool]], type[Bar[int]])) ``` ## `type[]` is not assignable to types disjoint from `builtins.type` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index a5a2d1c6af617..47f9f4b40e29a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1561,7 +1561,7 @@ impl<'db> Type<'db> { } } Type::ClassLiteral(class_literal) => { - Some(ClassType::NonGeneric(class_literal).into_callable(db)) + Some(class_literal.default_specialization(db).into_callable(db)) } Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)), @@ -2386,7 +2386,7 @@ impl<'db> Type<'db> { .subclass_of() .into_class() .map(|subclass_of_class| { - ClassType::NonGeneric(class).has_relation_to_impl( + class.default_specialization(db).has_relation_to_impl( db, subclass_of_class, inferable, @@ -6687,7 +6687,9 @@ impl<'db> Type<'db> { KnownClass::Float.to_instance(db), ], ), - _ if class.is_typed_dict(db) => Type::typed_dict(*class), + _ if class.is_typed_dict(db) => { + Type::typed_dict(class.default_specialization(db)) + } _ => Type::instance(db, class.default_specialization(db)), }; Ok(ty) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index aaec186b9554a..2433ee04312d3 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -362,6 +362,11 @@ impl<'db> VarianceInferable<'db> for GenericAlias<'db> { get_size2::GetSize, )] pub enum ClassType<'db> { + // `NonGeneric` is intended to mean that the `ClassLiteral` has no type parameters. There are + // places where we currently violate this rule (e.g. so that we print `Foo` instead of + // `Foo[Unknown]`), but most callers who need to make a `ClassType` from a `ClassLiteral` + // should use `ClassLiteral::default_specialization` instead of assuming + // `ClassType::NonGeneric`. NonGeneric(ClassLiteral<'db>), Generic(GenericAlias<'db>), } @@ -3662,12 +3667,6 @@ impl<'db> From> for Type<'db> { } } -impl<'db> From> for ClassType<'db> { - fn from(class: ClassLiteral<'db>) -> ClassType<'db> { - ClassType::NonGeneric(class) - } -} - #[salsa::tracked] impl<'db> VarianceInferable<'db> for ClassLiteral<'db> { #[salsa::tracked(cycle_initial=crate::types::variance_cycle_initial, heap_size=ruff_memory_usage::heap_size)] diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index eff43e23e83a5..3a536249ccef0 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -679,11 +679,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - ast::Expr::Subscript(ast::ExprSubscript { - value, - slice: parameters, - .. - }) => { + ast::Expr::Subscript( + subscript @ ast::ExprSubscript { + value, + slice: parameters, + .. + }, + ) => { let parameters_ty = match self.infer_expression(value, TypeContext::default()) { Type::SpecialForm(SpecialFormType::Union) => match &**parameters { ast::Expr::Tuple(tuple) => { @@ -698,6 +700,40 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } _ => self.infer_subclass_of_type_expression(parameters), }, + value_ty @ Type::ClassLiteral(class_literal) => { + if class_literal.is_protocol(self.db()) { + SubclassOfType::from( + self.db(), + todo_type!("type[T] for protocols").expect_dynamic(), + ) + } else { + match class_literal.generic_context(self.db()) { + Some(generic_context) => { + let db = self.db(); + let specialize = |types: &[Option>]| { + SubclassOfType::from( + db, + class_literal.apply_specialization(db, |_| { + generic_context + .specialize_partial(db, types.iter().copied()) + }), + ) + }; + self.infer_explicit_callable_specialization( + subscript, + value_ty, + generic_context, + specialize, + ) + } + None => { + // TODO: emit a diagnostic if you try to specialize a non-generic class. + self.infer_type_expression(parameters); + todo_type!("specialized non-generic class") + } + } + } + } _ => { self.infer_type_expression(parameters); todo_type!("unsupported nested subscript in type[X]")