diff --git a/crates/ty_python_semantic/resources/mdtest/async.md b/crates/ty_python_semantic/resources/mdtest/async.md index 0d57f4d8f8..c46d0f86ba 100644 --- a/crates/ty_python_semantic/resources/mdtest/async.md +++ b/crates/ty_python_semantic/resources/mdtest/async.md @@ -79,9 +79,8 @@ async def main(): task("B"), ) - # TODO: these should be `int` - reveal_type(a) # revealed: Unknown - reveal_type(b) # revealed: Unknown + reveal_type(a) # revealed: int + reveal_type(b) # revealed: int ``` ## Under the hood diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index 4b28f150bc..6a3a9ad37d 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -388,6 +388,8 @@ ListOrTuple = list[T] | tuple[T, ...] ListOrTupleLegacy = Union[list[T], tuple[T, ...]] MyCallable = Callable[P, T] AnnotatedType = Annotated[T, "tag"] +TransparentAlias = T +MyOptional = T | None # TODO: Consider displaying this as ``, … instead? (and similar for some others below) reveal_type(MyList) # revealed: @@ -400,43 +402,40 @@ reveal_type(ListOrTuple) # revealed: types.UnionType reveal_type(ListOrTupleLegacy) # revealed: types.UnionType reveal_type(MyCallable) # revealed: GenericAlias reveal_type(AnnotatedType) # revealed: +reveal_type(TransparentAlias) # revealed: typing.TypeVar +reveal_type(MyOptional) # revealed: types.UnionType def _( list_of_ints: MyList[int], dict_str_to_int: MyDict[str, int], - # TODO: no error here - # error: [invalid-type-form] "`typing.TypeVar` is not a generic class" subclass_of_int: MyType[int], int_and_str: IntAndType[str], pair_of_ints: Pair[int], int_and_bytes: Sum[int, bytes], list_or_tuple: ListOrTuple[int], list_or_tuple_legacy: ListOrTupleLegacy[int], - # TODO: no error here + # TODO: no errors here # error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[str, bytes]`?" + # error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2" my_callable: MyCallable[[str, bytes], int], annotated_int: AnnotatedType[int], + transparent_alias: TransparentAlias[int], + optional_int: MyOptional[int], ): - # TODO: This should be `list[int]` - reveal_type(list_of_ints) # revealed: @Todo(specialized generic alias in type expression) - # TODO: This should be `dict[str, int]` - reveal_type(dict_str_to_int) # revealed: @Todo(specialized generic alias in type expression) - # TODO: This should be `type[int]` - reveal_type(subclass_of_int) # revealed: Unknown - # TODO: This should be `tuple[int, str]` - reveal_type(int_and_str) # revealed: @Todo(specialized generic alias in type expression) - # TODO: This should be `tuple[int, int]` - reveal_type(pair_of_ints) # revealed: @Todo(specialized generic alias in type expression) - # TODO: This should be `tuple[int, bytes]` - reveal_type(int_and_bytes) # revealed: @Todo(specialized generic alias in type expression) - # TODO: This should be `list[int] | tuple[int, ...]` - reveal_type(list_or_tuple) # revealed: @Todo(Generic specialization of types.UnionType) - # TODO: This should be `list[int] | tuple[int, ...]` - reveal_type(list_or_tuple_legacy) # revealed: @Todo(Generic specialization of types.UnionType) + reveal_type(list_of_ints) # revealed: list[int] + reveal_type(dict_str_to_int) # revealed: dict[str, int] + reveal_type(subclass_of_int) # revealed: type[int] + reveal_type(int_and_str) # revealed: tuple[int, str] + reveal_type(pair_of_ints) # revealed: tuple[int, int] + reveal_type(int_and_bytes) # revealed: tuple[int, bytes] + reveal_type(list_or_tuple) # revealed: list[int] | tuple[int, ...] + reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...] + reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...] # TODO: This should be `(str, bytes) -> int` - reveal_type(my_callable) # revealed: @Todo(Generic specialization of typing.Callable) - # TODO: This should be `int` - reveal_type(annotated_int) # revealed: @Todo(Generic specialization of typing.Annotated) + reveal_type(my_callable) # revealed: Unknown + reveal_type(annotated_int) # revealed: int + reveal_type(transparent_alias) # revealed: int + reveal_type(optional_int) # revealed: int | None ``` Generic implicit type aliases can be partially specialized: @@ -446,15 +445,12 @@ U = TypeVar("U") DictStrTo = MyDict[str, U] -reveal_type(DictStrTo) # revealed: GenericAlias +reveal_type(DictStrTo) # revealed: def _( - # TODO: No error here - # error: [invalid-type-form] "Invalid subscript of object of type `GenericAlias` in type expression" dict_str_to_int: DictStrTo[int], ): - # TODO: This should be `dict[str, int]` - reveal_type(dict_str_to_int) # revealed: Unknown + reveal_type(dict_str_to_int) # revealed: dict[str, int] ``` Using specializations of generic implicit type aliases in other implicit type aliases works as @@ -465,25 +461,31 @@ IntsOrNone = MyList[int] | None IntsOrStrs = Pair[int] | Pair[str] ListOfPairs = MyList[Pair[str]] -reveal_type(IntsOrNone) # revealed: UnionType -reveal_type(IntsOrStrs) # revealed: UnionType -reveal_type(ListOfPairs) # revealed: GenericAlias +reveal_type(IntsOrNone) # revealed: types.UnionType +reveal_type(IntsOrStrs) # revealed: types.UnionType +reveal_type(ListOfPairs) # revealed: def _( - # TODO: This should not be an error - # error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression" ints_or_none: IntsOrNone, - # TODO: This should not be an error - # error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression" ints_or_strs: IntsOrStrs, list_of_pairs: ListOfPairs, ): - # TODO: This should be `list[int] | None` - reveal_type(ints_or_none) # revealed: Unknown - # TODO: This should be `tuple[int, int] | tuple[str, str]` - reveal_type(ints_or_strs) # revealed: Unknown - # TODO: This should be `list[tuple[str, str]]` - reveal_type(list_of_pairs) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions) + reveal_type(ints_or_none) # revealed: list[int] | None + reveal_type(ints_or_strs) # revealed: tuple[int, int] | tuple[str, str] + reveal_type(list_of_pairs) # revealed: list[tuple[str, str]] +``` + +A generic implicit type alias can also be used in another generic implicit type alias: + +```py +MyOtherList = MyList[T] + +reveal_type(MyOtherList) # revealed: + +def _( + list_of_ints: MyOtherList[int], +): + reveal_type(list_of_ints) # revealed: list[int] ``` If a generic implicit type alias is used unspecialized in a type expression, we treat it as an @@ -522,8 +524,6 @@ reveal_mro(Derived1) GenericBaseAlias = GenericBase[T] -# TODO: No error here -# error: [non-subscriptable] "Cannot subscript object of type `` with no `__class_getitem__` method" class Derived2(GenericBaseAlias[int]): pass ``` @@ -533,10 +533,9 @@ A generic alias that is already fully specialized cannot be specialized again: ```py ListOfInts = list[int] -# TODO: this should be an error +# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 1" def _(doubly_specialized: ListOfInts[int]): - # TODO: this should be `Unknown` - reveal_type(doubly_specialized) # revealed: @Todo(specialized generic alias in type expression) + reveal_type(doubly_specialized) # revealed: Unknown ``` Specializing a generic implicit type alias with an incorrect number of type arguments also results @@ -544,15 +543,13 @@ in an error: ```py def _( - # TODO: this should be an error + # error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2" list_too_many_args: MyList[int, str], - # TODO: this should be an error + # error: [missing-argument] "No argument provided for required parameter `U`" dict_too_few_args: MyDict[int], ): - # TODO: this should be `Unknown` - reveal_type(list_too_many_args) # revealed: @Todo(specialized generic alias in type expression) - # TODO: this should be `Unknown` - reveal_type(dict_too_few_args) # revealed: @Todo(specialized generic alias in type expression) + reveal_type(list_too_many_args) # revealed: Unknown + reveal_type(dict_too_few_args) # revealed: Unknown ``` ## `Literal`s @@ -642,8 +639,7 @@ Deprecated = Annotated[T, "deprecated attribute"] class C: old: Deprecated[int] -# TODO: Should be `int` -reveal_type(C().old) # revealed: @Todo(Generic specialization of typing.Annotated) +reveal_type(C().old) # revealed: int ``` If the metadata argument is missing, we emit an error (because this code fails at runtime), but diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 1f5dfc71b8..2b86bb40e2 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -7198,17 +7198,70 @@ impl<'db> Type<'db> { } } - Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping { - TypeMapping::BindLegacyTypevars(binding_context) => { - Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context)) + Type::KnownInstance(known_instance) => match known_instance { + KnownInstanceType::TypeVar(typevar) => { + match type_mapping { + TypeMapping::BindLegacyTypevars(binding_context) => { + Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context)) + } + TypeMapping::Specialization(_) | + TypeMapping::PartialSpecialization(_) | + TypeMapping::PromoteLiterals(_) | + TypeMapping::BindSelf(_) | + TypeMapping::ReplaceSelf { .. } | + TypeMapping::Materialize(_) | + TypeMapping::ReplaceParameterDefaults => self, + } } - TypeMapping::Specialization(_) | - TypeMapping::PartialSpecialization(_) | - TypeMapping::PromoteLiterals(_) | - TypeMapping::BindSelf(_) | - TypeMapping::ReplaceSelf { .. } | - TypeMapping::Materialize(_) | - TypeMapping::ReplaceParameterDefaults => self, + KnownInstanceType::UnionType(instance) => { + if let Ok(union_type) = instance.union_type(db) { + Type::KnownInstance(KnownInstanceType::UnionType( + UnionTypeInstance::new( + db, + instance._value_expr_types(db), + Ok(union_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor) + ) + ))) + } else { + self + } + }, + KnownInstanceType::Annotated(ty) => { + Type::KnownInstance(KnownInstanceType::Annotated( + InternedType::new( + db, + ty.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor), + ) + )) + }, + KnownInstanceType::Callable(callable_type) => { + Type::KnownInstance(KnownInstanceType::Callable( + callable_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor), + )) + }, + KnownInstanceType::TypeGenericAlias(ty) => { + Type::KnownInstance(KnownInstanceType::TypeGenericAlias( + InternedType::new( + db, + ty.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor), + ) + )) + }, + + KnownInstanceType::SubscriptedProtocol(_) | + KnownInstanceType::SubscriptedGeneric(_) | + KnownInstanceType::TypeAliasType(_) | + KnownInstanceType::Deprecated(_) | + KnownInstanceType::Field(_) | + KnownInstanceType::ConstraintSet(_) | + KnownInstanceType::GenericContext(_) | + KnownInstanceType::Specialization(_) | + KnownInstanceType::Literal(_) | + KnownInstanceType::LiteralStringAlias(_) | + KnownInstanceType::NewType(_) => { + // TODO: ? + self + }, } Type::FunctionLiteral(function) => { @@ -7369,8 +7422,7 @@ impl<'db> Type<'db> { // some other generic context's specialization is applied to it. | Type::ClassLiteral(_) | Type::BoundSuper(_) - | Type::SpecialForm(_) - | Type::KnownInstance(_) => self, + | Type::SpecialForm(_) => self, } } @@ -7507,6 +7559,44 @@ impl<'db> Type<'db> { }); } + Type::KnownInstance(known_instance) => match known_instance { + KnownInstanceType::UnionType(instance) => { + if let Ok(union_type) = instance.union_type(db) { + union_type.find_legacy_typevars_impl( + db, + binding_context, + typevars, + visitor, + ); + } + } + KnownInstanceType::Annotated(ty) => { + ty.inner(db) + .find_legacy_typevars_impl(db, binding_context, typevars, visitor); + } + KnownInstanceType::Callable(callable_type) => { + callable_type.find_legacy_typevars_impl(db, binding_context, typevars, visitor); + } + KnownInstanceType::TypeGenericAlias(ty) => { + ty.inner(db) + .find_legacy_typevars_impl(db, binding_context, typevars, visitor); + } + KnownInstanceType::SubscriptedProtocol(_) + | KnownInstanceType::SubscriptedGeneric(_) + | KnownInstanceType::TypeVar(_) + | KnownInstanceType::TypeAliasType(_) + | KnownInstanceType::Deprecated(_) + | KnownInstanceType::Field(_) + | KnownInstanceType::ConstraintSet(_) + | KnownInstanceType::GenericContext(_) + | KnownInstanceType::Specialization(_) + | KnownInstanceType::Literal(_) + | KnownInstanceType::LiteralStringAlias(_) + | KnownInstanceType::NewType(_) => { + // TODO? + } + }, + Type::Dynamic(_) | Type::Never | Type::AlwaysTruthy @@ -7534,7 +7624,6 @@ impl<'db> Type<'db> { | Type::EnumLiteral(_) | Type::BoundSuper(_) | Type::SpecialForm(_) - | Type::KnownInstance(_) | Type::TypedDict(_) => {} } } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index fe1dba2059..ce0a6e1d66 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -10794,6 +10794,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> { let value_ty = self.infer_expression(&subscript.value, TypeContext::default()); + + if value_ty.is_generic_alias() { + return self + .infer_explicitly_specialized_implicit_type_alias(subscript, value_ty, false); + } + self.infer_subscript_load_impl(value_ty, subscript) } 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 6404364278..c65a747368 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 @@ -2,6 +2,7 @@ use itertools::Either; use ruff_python_ast as ast; use super::{DeferredExpressionState, TypeInferenceBuilder}; +use crate::FxOrderSet; use crate::types::diagnostic::{ self, INVALID_TYPE_FORM, NON_SUBSCRIPTABLE, report_invalid_argument_number_to_special_form, report_invalid_arguments_to_callable, @@ -11,9 +12,9 @@ use crate::types::string_annotation::parse_string_annotation; use crate::types::tuple::{TupleSpecBuilder, TupleType}; use crate::types::visitor::any_over_type; use crate::types::{ - CallableType, DynamicType, IntersectionBuilder, KnownClass, KnownInstanceType, - LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType, Type, - TypeAliasType, TypeContext, TypeIsType, UnionBuilder, UnionType, todo_type, + BindingContext, CallableType, DynamicType, GenericContext, IntersectionBuilder, KnownClass, + KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType, + Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, UnionBuilder, UnionType, todo_type, }; /// Type expressions @@ -750,6 +751,49 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } } + pub(crate) fn infer_explicitly_specialized_implicit_type_alias( + &mut self, + subscript: &ast::ExprSubscript, + value_ty: Type<'db>, + in_type_expression: bool, + ) -> Type<'db> { + let db = self.db(); + + let generic_type_alias = value_ty.apply_type_mapping( + db, + &TypeMapping::BindLegacyTypevars(BindingContext::Synthetic), + TypeContext::default(), + ); + + let mut variables = FxOrderSet::default(); + generic_type_alias.find_legacy_typevars(db, None, &mut variables); + let generic_context = GenericContext::from_typevar_instances(db, variables); + + let scope_id = self.scope(); + let typevar_binding_context = self.typevar_binding_context; + let specialize = |types: &[Option>]| { + let specialized = generic_type_alias.apply_specialization( + db, + generic_context.specialize_partial(db, types.iter().copied()), + ); + + if in_type_expression { + specialized + .in_type_expression(db, scope_id, typevar_binding_context) + .unwrap_or_else(|_| Type::unknown()) + } else { + specialized + } + }; + + self.infer_explicit_callable_specialization( + subscript, + value_ty, + generic_context, + specialize, + ) + } + fn infer_subscript_type_expression( &mut self, subscript: &ast::ExprSubscript, @@ -840,10 +884,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - KnownInstanceType::TypeVar(_) => { - self.infer_type_expression(slice); - todo_type!("TypeVar annotations") - } KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => { match type_alias.generic_context(self.db()) { Some(generic_context) => { @@ -886,11 +926,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_type_expression(slice); todo_type!("Generic stringified PEP-613 type alias") } - KnownInstanceType::UnionType(_) => { - self.infer_type_expression(slice); - todo_type!("Generic specialization of types.UnionType") - } - KnownInstanceType::Literal(ty) | KnownInstanceType::TypeGenericAlias(ty) => { + KnownInstanceType::Literal(ty) => { self.infer_type_expression(slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( @@ -900,13 +936,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - KnownInstanceType::Callable(_) => { - self.infer_type_expression(slice); - todo_type!("Generic specialization of typing.Callable") - } - KnownInstanceType::Annotated(_) => { - self.infer_type_expression(slice); - todo_type!("Generic specialization of typing.Annotated") + KnownInstanceType::TypeVar(_) => self + .infer_explicitly_specialized_implicit_type_alias(subscript, value_ty, false), + + KnownInstanceType::UnionType(_) + | KnownInstanceType::Callable(_) + | KnownInstanceType::Annotated(_) + | KnownInstanceType::TypeGenericAlias(_) => { + self.infer_explicitly_specialized_implicit_type_alias(subscript, value_ty, true) } KnownInstanceType::NewType(newtype) => { self.infer_type_expression(&subscript.slice); @@ -949,11 +986,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } } Type::GenericAlias(_) => { - self.infer_type_expression(slice); - // If the generic alias is already fully specialized, this is an error. But it - // could have been specialized with another typevar (e.g. a type alias like `MyList - // = list[T]`), in which case it's later valid to do `MyList[int]`. - todo_type!("specialized generic alias in type expression") + self.infer_explicitly_specialized_implicit_type_alias(subscript, value_ty, true) } Type::StringLiteral(_) => { self.infer_type_expression(slice);