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

@ -8,9 +8,8 @@ import collections
import enum import enum
import typing import typing
# TODO: should not error (requires understanding metaclass `__call__`) MyEnum = enum.Enum("MyEnum", ["foo", "bar", "baz"])
MyEnum = enum.Enum("MyEnum", ["foo", "bar", "baz"]) # error: [too-many-positional-arguments] MyIntEnum = enum.IntEnum("MyIntEnum", ["foo", "bar", "baz"])
MyTypedDict = typing.TypedDict("MyTypedDict", {"foo": int}) MyTypedDict = typing.TypedDict("MyTypedDict", {"foo": int})
MyNamedTuple1 = typing.NamedTuple("MyNamedTuple1", [("foo", int)]) MyNamedTuple1 = typing.NamedTuple("MyNamedTuple1", [("foo", int)])
MyNamedTuple2 = collections.namedtuple("MyNamedTuple2", ["foo"]) MyNamedTuple2 = collections.namedtuple("MyNamedTuple2", ["foo"])

View file

@ -3890,6 +3890,9 @@ impl<'db> Type<'db> {
); );
Signatures::single(signature) Signatures::single(signature)
} }
Some(KnownClass::Enum) => {
Signatures::single(CallableSignature::todo("functional `Enum` syntax"))
}
Some(KnownClass::Super) => { Some(KnownClass::Super) => {
// ```py // ```py
@ -4836,14 +4839,6 @@ impl<'db> Type<'db> {
Some(KnownClass::NamedTuple) => Ok(todo_type!( Some(KnownClass::NamedTuple) => Ok(todo_type!(
"Support for functional `typing.NamedTuple` syntax" "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 { _ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType( invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self *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 let class = match callable_type {
// `type[]` types. But it's deliberate! The typing spec explicitly mandates that `type[]` types Type::ClassLiteral(class) => Some(ClassType::NonGeneric(class)),
// can be called even though class-literals cannot. This is because even though a protocol class Type::GenericAlias(generic) => Some(ClassType::Generic(generic)),
// `SomeProtocol` is always an abstract class, `type[SomeProtocol]` can be a concrete subclass of Type::SubclassOf(subclass) => subclass.subclass_of().into_class(),
// 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())),
_ => None, _ => None,
}; };
if let Some(protocol) = if let Some(class) = class {
possible_protocol_class.and_then(|class| class.into_protocol_class(self.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
report_attempted_protocol_instantiation(&self.context, call_expression, protocol); // 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 // 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 // in a separate function. For some known classes we have manual signatures defined and use
// the `try_call` path below. // the `try_call` path below.
// TODO: it should be possible to move these special cases into the `try_call_constructor` // 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. // path instead, or even remove some entirely once we support overloads fully.
let (call_constructor, known_class) = match callable_type { if !matches!(
Type::ClassLiteral(class) => (true, class.known(self.db())), 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,
Some( Some(
KnownClass::Bool KnownClass::Bool
| KnownClass::Str | KnownClass::Str
@ -4687,17 +4683,24 @@ impl<'db> TypeInferenceBuilder<'db> {
| KnownClass::TypeVar | KnownClass::TypeVar
) )
) )
{ // temporary special-casing for all subclasses of `enum.Enum`
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()]; // until we support the functional syntax for creating enum classes
let call_argument_types = && KnownClass::Enum
self.infer_argument_types(arguments, call_arguments, &argument_forms); .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 return callable_type
.try_call_constructor(self.db(), call_argument_types) .try_call_constructor(self.db(), call_argument_types)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
err.report_diagnostic(&self.context, callable_type, call_expression.into()); err.report_diagnostic(&self.context, callable_type, call_expression.into());
err.return_type() err.return_type()
}); });
}
} }
let signatures = callable_type.signatures(self.db()); let signatures = callable_type.signatures(self.db());