From 0590b3821413c405b46ef315b755b76d58a991ec Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 14 May 2025 12:26:52 -0400 Subject: [PATCH] [ty] Fix more generics-related TODOs (#18062) --- .../resources/mdtest/exception/except_star.md | 15 ++++------ crates/ty_python_semantic/src/types/class.rs | 28 ++++++++++++++++--- crates/ty_python_semantic/src/types/infer.rs | 17 ++++++----- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/exception/except_star.md b/crates/ty_python_semantic/resources/mdtest/exception/except_star.md index ef75db6482..8f862e747e 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/except_star.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/except_star.md @@ -22,9 +22,7 @@ except* BaseException as e: try: help() except* OSError as e: - # TODO: more precise would be `ExceptionGroup[OSError]` --Alex - # (needs homogeneous tuples + generics) - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: ExceptionGroup[OSError] ``` ## `except*` with multiple exceptions @@ -33,9 +31,7 @@ except* OSError as e: try: help() except* (TypeError, AttributeError) as e: - # TODO: more precise would be `ExceptionGroup[TypeError | AttributeError]` --Alex - # (needs homogeneous tuples + generics) - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: ExceptionGroup[TypeError | AttributeError] ``` ## `except*` with mix of `Exception`s and `BaseException`s @@ -44,8 +40,7 @@ except* (TypeError, AttributeError) as e: try: help() except* (KeyboardInterrupt, AttributeError) as e: - # TODO: more precise would be `BaseExceptionGroup[KeyboardInterrupt | AttributeError]` --Alex - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: BaseExceptionGroup[KeyboardInterrupt | AttributeError] ``` ## Invalid `except*` handlers @@ -54,10 +49,10 @@ except* (KeyboardInterrupt, AttributeError) as e: try: help() except* 3 as e: # error: [invalid-exception-caught] - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: BaseExceptionGroup[Unknown] try: help() except* (AttributeError, 42) as e: # error: [invalid-exception-caught] - reveal_type(e) # revealed: BaseExceptionGroup[BaseException] + reveal_type(e) # revealed: BaseExceptionGroup[AttributeError | Unknown] ``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 0f7936a18d..8a6836253b 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -896,8 +896,8 @@ impl<'db> ClassLiteral<'db> { } else { let name = Type::string_literal(db, self.name(db)); let bases = TupleType::from_elements(db, self.explicit_bases(db)); - // TODO: Should be `dict[str, Any]` - let namespace = KnownClass::Dict.to_instance(db); + let namespace = KnownClass::Dict + .to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()]); // TODO: Other keyword arguments? let arguments = CallArgumentTypes::positional([name, bases, namespace]); @@ -1913,7 +1913,9 @@ pub enum KnownClass { Slice, Property, BaseException, + Exception, BaseExceptionGroup, + ExceptionGroup, Classmethod, Super, // enum @@ -2004,6 +2006,8 @@ impl<'db> KnownClass { Self::Any | Self::BaseException + | Self::Exception + | Self::ExceptionGroup | Self::Object | Self::OrderedDict | Self::BaseExceptionGroup @@ -2079,6 +2083,8 @@ impl<'db> KnownClass { | Self::Property | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::GenericAlias | Self::GeneratorType @@ -2137,6 +2143,8 @@ impl<'db> KnownClass { Self::Property => "property", Self::BaseException => "BaseException", Self::BaseExceptionGroup => "BaseExceptionGroup", + Self::Exception => "Exception", + Self::ExceptionGroup => "ExceptionGroup", Self::Classmethod => "classmethod", Self::GenericAlias => "GenericAlias", Self::ModuleType => "ModuleType", @@ -2234,7 +2242,9 @@ impl<'db> KnownClass { db: &'db dyn Db, specialization: impl IntoIterator>, ) -> Type<'db> { - let class_literal = self.to_class_literal(db).expect_class_literal(); + let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else { + return Type::unknown(); + }; let Some(generic_context) = class_literal.generic_context(db) else { return Type::unknown(); }; @@ -2354,6 +2364,8 @@ impl<'db> KnownClass { | Self::Dict | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::Slice | Self::Super @@ -2444,6 +2456,8 @@ impl<'db> KnownClass { | Self::Property | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::GenericAlias | Self::ModuleType @@ -2522,6 +2536,8 @@ impl<'db> KnownClass { | Self::SupportsIndex | Self::BaseException | Self::BaseExceptionGroup + | Self::Exception + | Self::ExceptionGroup | Self::Classmethod | Self::TypeVar | Self::ParamSpec @@ -2565,6 +2581,8 @@ impl<'db> KnownClass { "property" => Self::Property, "BaseException" => Self::BaseException, "BaseExceptionGroup" => Self::BaseExceptionGroup, + "Exception" => Self::Exception, + "ExceptionGroup" => Self::ExceptionGroup, "classmethod" => Self::Classmethod, "GenericAlias" => Self::GenericAlias, "NoneType" => Self::NoneType, @@ -2643,6 +2661,8 @@ impl<'db> KnownClass { | Self::ModuleType | Self::VersionInfo | Self::BaseException + | Self::Exception + | Self::ExceptionGroup | Self::EllipsisType | Self::BaseExceptionGroup | Self::Classmethod @@ -2856,7 +2876,7 @@ mod tests { for class in KnownClass::iter() { let version_added = match class { KnownClass::UnionType => PythonVersion::PY310, - KnownClass::BaseExceptionGroup => PythonVersion::PY311, + KnownClass::BaseExceptionGroup | KnownClass::ExceptionGroup => PythonVersion::PY311, KnownClass::GenericAlias => PythonVersion::PY39, _ => PythonVersion::PY37, }; diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index be64625dc2..adfe5c48e9 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1843,9 +1843,7 @@ impl<'db> TypeInferenceBuilder<'db> { } let use_def = self.index.use_def_map(scope_id); if use_def.can_implicit_return(self.db()) - && !KnownClass::NoneType - .to_instance(self.db()) - .is_assignable_to(self.db(), declared_ty) + && !Type::none(self.db()).is_assignable_to(self.db(), declared_ty) { report_implicit_return_type(&self.context, returns.range(), declared_ty); } @@ -2623,9 +2621,14 @@ impl<'db> TypeInferenceBuilder<'db> { }; let symbol_ty = if except_handler_definition.is_star() { - // TODO: we should infer `ExceptionGroup` if `node_ty` is a subtype of `tuple[type[Exception], ...]` - // TODO: should be generic with `symbol_ty` as the generic parameter - KnownClass::BaseExceptionGroup.to_instance(self.db()) + let class = if symbol_ty + .is_subtype_of(self.db(), KnownClass::Exception.to_instance(self.db())) + { + KnownClass::ExceptionGroup + } else { + KnownClass::BaseExceptionGroup + }; + class.to_specialized_instance(self.db(), [symbol_ty]) } else { symbol_ty }; @@ -4104,7 +4107,7 @@ impl<'db> TypeInferenceBuilder<'db> { .map_or(ret.range(), |value| value.range()); self.record_return_type(ty, range); } else { - self.record_return_type(KnownClass::NoneType.to_instance(self.db()), ret.range()); + self.record_return_type(Type::none(self.db()), ret.range()); } }