Generalize special-casing for enums constructed with the functional syntax (#17885)

This commit is contained in:
Alex Waygood 2025-05-06 11:02:55 +01:00 committed by GitHub
parent aa0614509b
commit 457ec4dddd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 57 deletions

View file

@ -3890,6 +3890,9 @@ impl<'db> Type<'db> {
);
Signatures::single(signature)
}
Some(KnownClass::Enum) => {
Signatures::single(CallableSignature::todo("functional `Enum` syntax"))
}
Some(KnownClass::Super) => {
// ```py
@ -4836,14 +4839,6 @@ impl<'db> Type<'db> {
Some(KnownClass::NamedTuple) => Ok(todo_type!(
"Support for functional `typing.NamedTuple` syntax"
)),
_ if instance
.class()
.iter_mro(db)
.filter_map(ClassBase::into_class)
.any(|class| class.is_known(db, KnownClass::Enum)) =>
{
Ok(todo_type!("Support for functional `enum` syntax"))
}
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self

View file

@ -4637,46 +4637,42 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
// It might look odd here that we emit an error for class-literals and generic aliases but not
// `type[]` types. But it's deliberate! The typing spec explicitly mandates that `type[]` types
// can be called even though class-literals cannot. This is because even though a protocol class
// `SomeProtocol` is always an abstract class, `type[SomeProtocol]` can be a concrete subclass of
// that protocol -- and indeed, according to the spec, type checkers must disallow abstract
// subclasses of the protocol to be passed to parameters that accept `type[SomeProtocol]`.
// <https://typing.python.org/en/latest/spec/protocol.html#type-and-class-objects-vs-protocols>.
let possible_protocol_class = match callable_type {
Type::ClassLiteral(class) => Some(class),
Type::GenericAlias(generic) => Some(generic.origin(self.db())),
let class = match callable_type {
Type::ClassLiteral(class) => Some(ClassType::NonGeneric(class)),
Type::GenericAlias(generic) => Some(ClassType::Generic(generic)),
Type::SubclassOf(subclass) => subclass.subclass_of().into_class(),
_ => None,
};
if let Some(protocol) =
possible_protocol_class.and_then(|class| class.into_protocol_class(self.db()))
{
report_attempted_protocol_instantiation(&self.context, call_expression, protocol);
}
if let Some(class) = class {
// It might look odd here that we emit an error for class-literals and generic aliases but not
// `type[]` types. But it's deliberate! The typing spec explicitly mandates that `type[]` types
// can be called even though class-literals cannot. This is because even though a protocol class
// `SomeProtocol` is always an abstract class, `type[SomeProtocol]` can be a concrete subclass of
// that protocol -- and indeed, according to the spec, type checkers must disallow abstract
// subclasses of the protocol to be passed to parameters that accept `type[SomeProtocol]`.
// <https://typing.python.org/en/latest/spec/protocol.html#type-and-class-objects-vs-protocols>.
if !callable_type.is_subclass_of() {
if let Some(protocol) = class
.class_literal(self.db())
.0
.into_protocol_class(self.db())
{
report_attempted_protocol_instantiation(
&self.context,
call_expression,
protocol,
);
}
}
// For class literals we model the entire class instantiation logic, so it is handled
// in a separate function. For some known classes we have manual signatures defined and use
// the `try_call` path below.
// TODO: it should be possible to move these special cases into the `try_call_constructor`
// path instead, or even remove some entirely once we support overloads fully.
let (call_constructor, known_class) = match callable_type {
Type::ClassLiteral(class) => (true, class.known(self.db())),
Type::GenericAlias(generic) => (true, ClassType::Generic(generic).known(self.db())),
Type::SubclassOf(subclass) => (
true,
subclass
.subclass_of()
.into_class()
.and_then(|class| class.known(self.db())),
),
_ => (false, None),
};
if call_constructor
&& !matches!(
known_class,
// For class literals we model the entire class instantiation logic, so it is handled
// in a separate function. For some known classes we have manual signatures defined and use
// the `try_call` path below.
// TODO: it should be possible to move these special cases into the `try_call_constructor`
// path instead, or even remove some entirely once we support overloads fully.
if !matches!(
class.known(self.db()),
Some(
KnownClass::Bool
| KnownClass::Str
@ -4687,17 +4683,24 @@ impl<'db> TypeInferenceBuilder<'db> {
| KnownClass::TypeVar
)
)
{
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
let call_argument_types =
self.infer_argument_types(arguments, call_arguments, &argument_forms);
// temporary special-casing for all subclasses of `enum.Enum`
// until we support the functional syntax for creating enum classes
&& KnownClass::Enum
.to_class_literal(self.db())
.to_class_type(self.db())
.is_none_or(|enum_class| !class.is_subclass_of(self.db(), enum_class))
{
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
let call_argument_types =
self.infer_argument_types(arguments, call_arguments, &argument_forms);
return callable_type
.try_call_constructor(self.db(), call_argument_types)
.unwrap_or_else(|err| {
err.report_diagnostic(&self.context, callable_type, call_expression.into());
err.return_type()
});
return callable_type
.try_call_constructor(self.db(), call_argument_types)
.unwrap_or_else(|err| {
err.report_diagnostic(&self.context, callable_type, call_expression.into());
err.return_type()
});
}
}
let signatures = callable_type.signatures(self.db());