Compare commits
4 Commits
main
...
charlie/me
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b293aea973 | ||
|
|
6ea342e548 | ||
|
|
ebe4f00b82 | ||
|
|
817336ad35 |
@@ -163,7 +163,7 @@ static PANDAS: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
4000,
|
||||
4057,
|
||||
);
|
||||
|
||||
static PYDANTIC: Benchmark = Benchmark::new(
|
||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13116,
|
||||
13253,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
|
||||
@@ -3783,7 +3783,7 @@ quux.<CURSOR>
|
||||
__module__ :: str
|
||||
__mul__ :: bound method Quux.__mul__(value: SupportsIndex, /) -> tuple[int | str, ...]
|
||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||
__new__ :: (x: int, y: str) -> None
|
||||
__new__ :: (x: int, y: str) -> Quux
|
||||
__orig_bases__ :: tuple[Any, ...]
|
||||
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
# Class definitions
|
||||
|
||||
## `__new__` return type
|
||||
|
||||
Python's `__new__` method can return any type, not just an instance of the class. When `__new__`
|
||||
returns a type that is not assignable to the class instance type, we use that return type directly.
|
||||
|
||||
### `__new__` returning a different type
|
||||
|
||||
```py
|
||||
class ReturnsInt:
|
||||
def __new__(cls) -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(ReturnsInt()) # revealed: int
|
||||
|
||||
x: int = ReturnsInt() # OK
|
||||
y: ReturnsInt = ReturnsInt() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
### `__new__` returning a union type
|
||||
|
||||
```py
|
||||
class MaybeInt:
|
||||
def __new__(cls, value: str) -> "int | MaybeInt":
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(MaybeInt("42")) # revealed: int | MaybeInt
|
||||
|
||||
a: int | MaybeInt = MaybeInt("42") # OK
|
||||
b: int = MaybeInt("42") # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
### `__new__` returning the class type
|
||||
|
||||
When `__new__` returns the class type (or `Self`), the normal instance type is used.
|
||||
|
||||
```py
|
||||
class Normal:
|
||||
def __new__(cls) -> "Normal":
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(Normal()) # revealed: Normal
|
||||
```
|
||||
|
||||
### `__new__` with no return type annotation
|
||||
|
||||
When `__new__` has no return type annotation, we fall back to the instance type.
|
||||
|
||||
```py
|
||||
class NoAnnotation:
|
||||
def __new__(cls):
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(NoAnnotation()) # revealed: NoAnnotation
|
||||
```
|
||||
|
||||
## Deferred resolution of bases
|
||||
|
||||
### Only the stringified name is deferred
|
||||
|
||||
@@ -1,3 +1,152 @@
|
||||
## Custom `__call__` on metaclass
|
||||
|
||||
When a metaclass defines a custom `__call__` method, it controls what happens when the class is
|
||||
called. The return type and parameter types of the metaclass `__call__` are used instead of the
|
||||
class's `__new__` and `__init__` methods.
|
||||
|
||||
### Basic metaclass `__call__`
|
||||
|
||||
```py
|
||||
class Meta(type):
|
||||
def __call__(cls, x: int, y: str) -> str:
|
||||
return y
|
||||
|
||||
class Foo(metaclass=Meta): ...
|
||||
|
||||
reveal_type(Foo(1, "hello")) # revealed: str
|
||||
|
||||
a: str = Foo(1, "hello") # OK
|
||||
```
|
||||
|
||||
### Metaclass `__call__` with wrong arguments
|
||||
|
||||
```py
|
||||
class Meta(type):
|
||||
def __call__(cls, x: int) -> int:
|
||||
return x
|
||||
|
||||
class Foo(metaclass=Meta): ...
|
||||
|
||||
Foo("wrong") # error: [invalid-argument-type]
|
||||
Foo() # error: [missing-argument]
|
||||
Foo(1, 2) # error: [too-many-positional-arguments]
|
||||
```
|
||||
|
||||
### Metaclass `__call__` takes precedence over `__init__`
|
||||
|
||||
```py
|
||||
class Meta(type):
|
||||
def __call__(cls) -> str:
|
||||
return "hello"
|
||||
|
||||
class Foo(metaclass=Meta):
|
||||
def __init__(self, x: int, y: int) -> None:
|
||||
pass
|
||||
|
||||
# The metaclass __call__ takes precedence, so no arguments are needed
|
||||
# and the return type is str, not Foo.
|
||||
reveal_type(Foo()) # revealed: str
|
||||
```
|
||||
|
||||
### Metaclass `__call__` with TypeVar return type
|
||||
|
||||
When the metaclass `__call__` returns a TypeVar bound to the class type, it's essentially a
|
||||
pass-through to the normal constructor machinery. In this case, we should still check the `__new__`
|
||||
and `__init__` signatures.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Meta(type):
|
||||
def __call__(cls: type[T], *args, **kwargs) -> T:
|
||||
return object.__new__(cls)
|
||||
|
||||
class Foo(metaclass=Meta):
|
||||
def __init__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
# The metaclass __call__ returns T (bound to Foo), so we check __init__ parameters.
|
||||
Foo() # error: [missing-argument]
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
```
|
||||
|
||||
### Metaclass `__call__` with no return type annotation
|
||||
|
||||
When the metaclass `__call__` has no return type annotation (returns `Unknown`), we should still
|
||||
check the `__new__` and `__init__` signatures.
|
||||
|
||||
```py
|
||||
class Meta(type):
|
||||
def __call__(cls, *args, **kwargs):
|
||||
return object.__new__(cls)
|
||||
|
||||
class Foo(metaclass=Meta):
|
||||
def __init__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
# No return type annotation means we fall through to check __init__ parameters.
|
||||
Foo() # error: [missing-argument]
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
```
|
||||
|
||||
### Metaclass `__call__` with specific parameters
|
||||
|
||||
When the metaclass `__call__` has specific parameters (not just `*args, **kwargs`), we should check
|
||||
the metaclass `__call__` signature, even if the return type is the instance type.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Meta(type):
|
||||
def __call__(cls: type[T], x: int) -> T:
|
||||
return object.__new__(cls)
|
||||
|
||||
class Foo(metaclass=Meta):
|
||||
def __init__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
# The metaclass __call__ has specific parameters, so we check them.
|
||||
Foo("wrong") # error: [invalid-argument-type]
|
||||
Foo() # error: [missing-argument]
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
```
|
||||
|
||||
### Metaclass `__call__` returning bare `type`
|
||||
|
||||
When the metaclass `__call__` is annotated as returning `type`, we use that return type. This is
|
||||
stricter than mypy and pyright, which ignore the `-> type` annotation in this case.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
class Singleton(type):
|
||||
_instances: dict["Singleton", object] = {}
|
||||
|
||||
def __call__(cls, *args: Any, **kwargs: Any) -> type:
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super().__call__(*args, **kwargs)
|
||||
# error: [invalid-return-type]
|
||||
return cls._instances[cls]
|
||||
|
||||
class MyConfig(metaclass=Singleton):
|
||||
def __init__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
def get(self, key: str) -> str:
|
||||
return key
|
||||
|
||||
# The metaclass `__call__` returns `type`, so that's what we infer.
|
||||
reveal_type(MyConfig(1)) # revealed: type
|
||||
|
||||
# Instance methods are not available on `type`.
|
||||
# error: [unresolved-attribute]
|
||||
MyConfig(1).get("key")
|
||||
```
|
||||
|
||||
## Default
|
||||
|
||||
```py
|
||||
|
||||
@@ -7211,8 +7211,40 @@ impl<'db> Type<'db> {
|
||||
_ => (None, None, self),
|
||||
};
|
||||
|
||||
// As of now we do not model custom `__call__` on meta-classes, so the code below
|
||||
// only deals with interplay between `__new__` and `__init__` methods.
|
||||
// Check for a custom `__call__` on the metaclass. Following pyright's behavior:
|
||||
// 1. If the metaclass has a custom `__call__` with a declared return type, validate it first
|
||||
// 2. If there are argument errors, report them and return
|
||||
// 3. If the return type is not assignable to the instance type, skip `__new__`/`__init__`
|
||||
// 4. Otherwise, also validate `__new__`/`__init__`
|
||||
let metaclass_dunder_call = self_type.member_lookup_with_policy(
|
||||
db,
|
||||
"__call__".into(),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK
|
||||
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||
);
|
||||
|
||||
// Extract metaclass `__call__` info if it exists and has a declared return type.
|
||||
let metaclass_call_info =
|
||||
if let Place::Defined(Type::BoundMethod(metaclass_dunder_call), _, boundness, _) =
|
||||
metaclass_dunder_call.place
|
||||
{
|
||||
let signature = metaclass_dunder_call.function(db).signature(db);
|
||||
// Only use metaclass `__call__` if it has a declared return type.
|
||||
// If return type is unannotated, fall through to `__new__`/`__init__`.
|
||||
let has_declared_return = signature
|
||||
.overloads
|
||||
.iter()
|
||||
.any(|sig| sig.return_ty.is_some());
|
||||
if has_declared_return {
|
||||
Some((metaclass_dunder_call, boundness))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// The code below deals with interplay between `__new__` and `__init__` methods.
|
||||
// The logic is roughly as follows:
|
||||
// 1. If `__new__` is defined anywhere in the MRO (except for `object`, since it is always
|
||||
// present), we call it and analyze outcome. We then analyze `__init__` call, but only
|
||||
@@ -7223,10 +7255,8 @@ impl<'db> Type<'db> {
|
||||
// the way to `object` (single `self` argument call). This time it is correct to
|
||||
// fallback to `object.__init__`, since it will indeed check that no arguments are
|
||||
// passed.
|
||||
//
|
||||
// Note that we currently ignore `__new__` return type, since we do not yet support `Self`
|
||||
// and most builtin classes use it as return type annotation. We always return the instance
|
||||
// type.
|
||||
// 3. If `__new__` returns a type that is not assignable to the class instance type
|
||||
// (e.g., `int` or `int | A`), we use that return type instead of the instance type.
|
||||
|
||||
// Lookup `__new__` method in the MRO up to, but not including, `object`. Also, we must
|
||||
// avoid `__new__` on `type` since per descriptor protocol, if `__new__` is not defined on
|
||||
@@ -7251,42 +7281,105 @@ impl<'db> Type<'db> {
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK | MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
|
||||
);
|
||||
|
||||
// Infer the call argument types, using both `__new__` and `__init__` for type-context.
|
||||
let bindings = match (
|
||||
new_method.as_ref().map(|method| &method.place),
|
||||
&init_method.place,
|
||||
) {
|
||||
(Some(Place::Defined(new_method, ..)), Place::Undefined) => Some(
|
||||
new_method
|
||||
.bindings(db)
|
||||
.map(|binding| binding.with_bound_type(self_type)),
|
||||
),
|
||||
// Infer the call argument types using the appropriate bindings for type-context.
|
||||
// If metaclass `__call__` has a declared return type, use its bindings.
|
||||
// Otherwise, use combined `__new__` and `__init__` bindings.
|
||||
let bindings = if metaclass_call_info.is_some() {
|
||||
// Use metaclass `__call__` bindings for argument inference.
|
||||
metaclass_call_info
|
||||
.as_ref()
|
||||
.map(|(method, _)| Type::BoundMethod(*method).bindings(db))
|
||||
} else {
|
||||
// Use `__new__` and `__init__` bindings for argument inference.
|
||||
match (
|
||||
new_method.as_ref().map(|method| &method.place),
|
||||
&init_method.place,
|
||||
) {
|
||||
(Some(Place::Defined(new_method, ..)), Place::Undefined) => Some(
|
||||
new_method
|
||||
.bindings(db)
|
||||
.map(|binding| binding.with_bound_type(self_type)),
|
||||
),
|
||||
|
||||
(Some(Place::Undefined) | None, Place::Defined(init_method, ..)) => {
|
||||
Some(init_method.bindings(db))
|
||||
(Some(Place::Undefined) | None, Place::Defined(init_method, ..)) => {
|
||||
Some(init_method.bindings(db))
|
||||
}
|
||||
|
||||
(Some(Place::Defined(new_method, ..)), Place::Defined(init_method, ..)) => {
|
||||
let callable = UnionBuilder::new(db)
|
||||
.add(*new_method)
|
||||
.add(*init_method)
|
||||
.build();
|
||||
|
||||
let new_method_bindings = new_method
|
||||
.bindings(db)
|
||||
.map(|binding| binding.with_bound_type(self_type));
|
||||
|
||||
Some(Bindings::from_union(
|
||||
callable,
|
||||
[new_method_bindings, init_method.bindings(db)],
|
||||
))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
|
||||
(Some(Place::Defined(new_method, ..)), Place::Defined(init_method, ..)) => {
|
||||
let callable = UnionBuilder::new(db)
|
||||
.add(*new_method)
|
||||
.add(*init_method)
|
||||
.build();
|
||||
|
||||
let new_method_bindings = new_method
|
||||
.bindings(db)
|
||||
.map(|binding| binding.with_bound_type(self_type));
|
||||
|
||||
Some(Bindings::from_union(
|
||||
callable,
|
||||
[new_method_bindings, init_method.bindings(db)],
|
||||
))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let argument_types = infer_argument_types(bindings);
|
||||
|
||||
// If metaclass `__call__` exists with a declared return type, validate it first.
|
||||
// Following pyright's behavior:
|
||||
// - If there are argument errors, return the metaclass error
|
||||
// - If the return type is not assignable to the instance type, return the metaclass result
|
||||
// - Otherwise, continue to validate `__new__`/`__init__`
|
||||
if let Some((metaclass_dunder_call, boundness)) = metaclass_call_info {
|
||||
let bindings = Type::BoundMethod(metaclass_dunder_call).bindings(db);
|
||||
|
||||
let call_result = bindings
|
||||
.clone()
|
||||
.match_parameters(db, &argument_types)
|
||||
.check_types(db, &argument_types, tcx, &[]);
|
||||
|
||||
let metaclass_return_type = call_result
|
||||
.as_ref()
|
||||
.map_or_else(|err| err.1.return_type(db), |b| b.return_type(db));
|
||||
|
||||
// Get the instance type for comparison.
|
||||
let instance_ty = self
|
||||
.to_instance(db)
|
||||
.expect("type should be convertible to instance type");
|
||||
|
||||
// Check if we should skip `__new__`/`__init__` evaluation.
|
||||
// Skip if: return type is not assignable to instance, is Never, or contains Any.
|
||||
let skip_new_init = !metaclass_return_type.is_assignable_to(db, instance_ty)
|
||||
|| metaclass_return_type.is_never()
|
||||
|| matches!(metaclass_return_type, Type::Dynamic(DynamicType::Any));
|
||||
|
||||
// If there are argument errors or we should skip `__new__`/`__init__`, return metaclass result.
|
||||
if call_result.is_err() || skip_new_init {
|
||||
let call_result = call_result
|
||||
.map_err(CallDunderError::from)
|
||||
.and_then(|bindings| {
|
||||
if boundness == Definedness::PossiblyUndefined {
|
||||
Err(CallDunderError::PossiblyUnbound(Box::new(bindings)))
|
||||
} else {
|
||||
Ok(bindings)
|
||||
}
|
||||
});
|
||||
|
||||
return match call_result {
|
||||
Ok(bindings) => Ok(bindings.return_type(db)),
|
||||
Err(error) => Err(ConstructorCallError::MetaclassCall(
|
||||
error.fallback_return_type(db),
|
||||
error,
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
// Metaclass `__call__` succeeded and returns instance type.
|
||||
// Continue to validate `__new__`/`__init__` below.
|
||||
}
|
||||
|
||||
let new_call_outcome = new_method.and_then(|new_method| {
|
||||
match new_method.place.try_call_dunder_get(db, self_type) {
|
||||
Place::Defined(new_method, _, boundness, _) => {
|
||||
@@ -7307,6 +7400,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
});
|
||||
|
||||
// Call `__init__` if `__new__` was not called, or if `__init__` is explicitly defined.
|
||||
let init_call_outcome = if new_call_outcome.is_none() || !init_method.is_undefined() {
|
||||
let call_result = match init_ty
|
||||
.member_lookup_with_policy(
|
||||
@@ -7347,6 +7441,28 @@ impl<'db> Type<'db> {
|
||||
.to_instance(db)
|
||||
.expect("type should be convertible to instance type");
|
||||
|
||||
// Extract the return type from `__new__` if it was called successfully.
|
||||
// Filter out:
|
||||
// 1. Dynamic types (`Unknown`, `Any`) since they indicate the `__new__` method doesn't
|
||||
// have an explicit return type annotation (e.g., synthesized methods like tuple's
|
||||
// `__new__`) or is inherited from `Any`.
|
||||
// 2. Types containing unresolved type variables (like `Self`) since they need to be
|
||||
// resolved by the constructor machinery, not used directly.
|
||||
let new_return_type = new_call_outcome
|
||||
.as_ref()
|
||||
.and_then(|result| result.as_ref().ok())
|
||||
.map(|bindings| bindings.return_type(db))
|
||||
.map(|ty| ty.filter_union(db, |elem| !elem.is_dynamic()))
|
||||
.filter(|ty| !ty.is_dynamic() && !ty.has_typevar(db));
|
||||
|
||||
// Determine the return type based on `__new__` return type.
|
||||
// Only use the `__new__` return type if it returns something that is not a subclass of
|
||||
// the class being constructed (e.g., `int | A`). Otherwise, use the instance type so that
|
||||
// proper generic specialization can be applied.
|
||||
let return_ty = new_return_type
|
||||
.filter(|return_ty| !return_ty.is_assignable_to(db, instance_ty))
|
||||
.unwrap_or(instance_ty);
|
||||
|
||||
match (generic_origin, new_call_outcome, init_call_outcome) {
|
||||
// All calls are successful or not called at all
|
||||
(
|
||||
@@ -7388,26 +7504,25 @@ impl<'db> Type<'db> {
|
||||
generic_origin.apply_specialization(db, |_| specialization),
|
||||
)
|
||||
})
|
||||
.unwrap_or(instance_ty);
|
||||
// Use the `__new__` return type if available, otherwise use instance_ty.
|
||||
.unwrap_or(return_ty);
|
||||
Ok(specialized)
|
||||
}
|
||||
|
||||
(None, None | Some(Ok(_)), None | Some(Ok(_))) => Ok(instance_ty),
|
||||
(None, None | Some(Ok(_)), None | Some(Ok(_))) => Ok(return_ty),
|
||||
|
||||
(_, None | Some(Ok(_)), Some(Err(error))) => {
|
||||
// no custom `__new__` or it was called and succeeded, but `__init__` failed.
|
||||
Err(ConstructorCallError::Init(instance_ty, error))
|
||||
// No custom `__new__` or it was called and succeeded, but `__init__` failed.
|
||||
Err(ConstructorCallError::Init(return_ty, error))
|
||||
}
|
||||
(_, Some(Err(error)), None | Some(Ok(_))) => {
|
||||
// custom `__new__` was called and failed, but init is ok
|
||||
Err(ConstructorCallError::New(instance_ty, error))
|
||||
// Custom `__new__` was called and failed, but init is ok.
|
||||
Err(ConstructorCallError::New(return_ty, error))
|
||||
}
|
||||
(_, Some(Err(new_error)), Some(Err(init_error))) => {
|
||||
// custom `__new__` was called and failed, and `__init__` is also not ok
|
||||
// Custom `__new__` was called and failed, and `__init__` is also not ok.
|
||||
Err(ConstructorCallError::NewAndInit(
|
||||
instance_ty,
|
||||
new_error,
|
||||
init_error,
|
||||
return_ty, new_error, init_error,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -12040,6 +12155,7 @@ enum ConstructorCallError<'db> {
|
||||
Init(Type<'db>, CallDunderError<'db>),
|
||||
New(Type<'db>, DunderNewCallError<'db>),
|
||||
NewAndInit(Type<'db>, DunderNewCallError<'db>, CallDunderError<'db>),
|
||||
MetaclassCall(Type<'db>, CallDunderError<'db>),
|
||||
}
|
||||
|
||||
impl<'db> ConstructorCallError<'db> {
|
||||
@@ -12048,6 +12164,7 @@ impl<'db> ConstructorCallError<'db> {
|
||||
Self::Init(ty, _) => *ty,
|
||||
Self::New(ty, _) => *ty,
|
||||
Self::NewAndInit(ty, _, _) => *ty,
|
||||
Self::MetaclassCall(ty, _) => *ty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12109,6 +12226,30 @@ impl<'db> ConstructorCallError<'db> {
|
||||
}
|
||||
};
|
||||
|
||||
let report_metaclass_call_error =
|
||||
|call_dunder_error: &CallDunderError<'db>| match call_dunder_error {
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
unreachable!(
|
||||
"MethodNotAvailable should not occur when metaclass `__call__` was found"
|
||||
)
|
||||
}
|
||||
CallDunderError::PossiblyUnbound(bindings) => {
|
||||
if let Some(builder) = context
|
||||
.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__call__` on type `{}` may be missing.",
|
||||
context_expression_type.display(context.db()),
|
||||
));
|
||||
}
|
||||
|
||||
bindings.report_diagnostics(context, context_expression_node);
|
||||
}
|
||||
CallDunderError::CallError(_, bindings) => {
|
||||
bindings.report_diagnostics(context, context_expression_node);
|
||||
}
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::Init(_, init_call_dunder_error) => {
|
||||
report_init_error(init_call_dunder_error);
|
||||
@@ -12120,6 +12261,9 @@ impl<'db> ConstructorCallError<'db> {
|
||||
report_new_error(new_call_error);
|
||||
report_init_error(init_call_dunder_error);
|
||||
}
|
||||
Self::MetaclassCall(_, metaclass_call_error) => {
|
||||
report_metaclass_call_error(metaclass_call_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2568,7 +2568,15 @@ impl<'db> ClassLiteral<'db> {
|
||||
(CodeGeneratorKind::NamedTuple, "__new__") => {
|
||||
let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls"))
|
||||
.with_annotated_type(KnownClass::Type.to_instance(db));
|
||||
signature_from_fields(vec![cls_parameter], Some(Type::none(db)))
|
||||
// Use `Self` type variable as return type so that subclasses get the correct
|
||||
// return type when calling `__new__`. For example, if `IntBox` inherits from
|
||||
// `Box[int]` (a NamedTuple), then `IntBox(1)` should return `IntBox`, not `Box[int]`.
|
||||
let self_ty = Type::TypeVar(BoundTypeVarInstance::synthetic_self(
|
||||
db,
|
||||
instance_ty,
|
||||
BindingContext::Synthetic,
|
||||
));
|
||||
signature_from_fields(vec![cls_parameter], Some(self_ty))
|
||||
}
|
||||
(CodeGeneratorKind::NamedTuple, "_replace" | "__replace__") => {
|
||||
if name == "__replace__"
|
||||
|
||||
Reference in New Issue
Block a user