mirror of
https://github.com/astral-sh/ruff.git
synced 2025-12-23 09:19:39 +00:00
ty: make constructor calls use bindings
This commit is contained in:
parent
b4c2825afd
commit
ebb4b8bbb7
8 changed files with 545 additions and 489 deletions
|
|
@ -60,6 +60,8 @@ class Foo:
|
|||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `int`, found `Literal["x"]`"
|
||||
reveal_type(Foo("x")) # revealed: Foo
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 2, got 3"
|
||||
|
|
@ -88,6 +90,23 @@ reveal_type(Foo()) # revealed: Foo
|
|||
reveal_type(Foo(1, 2)) # revealed: Foo
|
||||
```
|
||||
|
||||
## `__new__` present but `__init__` missing
|
||||
|
||||
`object.__init__` allows arbitrary arguments when a custom `__new__` exists. This should not trigger
|
||||
`__init__` argument errors.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __new__(cls, x: int):
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
|
||||
Foo(1)
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 2, got 3"
|
||||
Foo(1, 2)
|
||||
```
|
||||
|
||||
## Conditional `__new__`
|
||||
|
||||
```py
|
||||
|
|
@ -130,6 +149,30 @@ reveal_type(Foo(1)) # revealed: Foo
|
|||
reveal_type(Foo()) # revealed: Foo
|
||||
```
|
||||
|
||||
## `__new__` defined as a staticmethod
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def __new__(cls, x: int):
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
```
|
||||
|
||||
## `__new__` defined as a classmethod
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
@classmethod
|
||||
def __new__(cls, x: int):
|
||||
return object.__new__(cls)
|
||||
|
||||
# error: [invalid-argument-type] "Argument to bound method `__new__` is incorrect: Expected `int`, found `<class 'Foo'>`"
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__new__`: expected 1, got 2"
|
||||
Foo(1)
|
||||
```
|
||||
|
||||
## A callable instance in place of `__new__`
|
||||
|
||||
### Bound
|
||||
|
|
@ -202,6 +245,35 @@ reveal_type(Foo()) # revealed: Foo
|
|||
reveal_type(Foo(1, 2)) # revealed: Foo
|
||||
```
|
||||
|
||||
## Generic constructor inference
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Box(Generic[T]):
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(Box(1)) # revealed: Box[int]
|
||||
```
|
||||
|
||||
## Union of constructors
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
class B:
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
def f(flag: bool):
|
||||
cls = A if flag else B
|
||||
reveal_type(cls(1)) # revealed: A | B
|
||||
```
|
||||
|
||||
## `__init__` present on a superclass
|
||||
|
||||
If the `__init__` method is defined on a superclass, we can still infer the signature of the
|
||||
|
|
@ -348,6 +420,23 @@ reveal_type(Foo(1)) # revealed: Foo
|
|||
reveal_type(Foo(1, 2)) # revealed: Foo
|
||||
```
|
||||
|
||||
### Conflicting parameter types
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __new__(cls, x: int):
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, x: str) -> None:
|
||||
self.x = x
|
||||
|
||||
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `str`, found `Literal[1]`"
|
||||
Foo(1)
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `int`, found `Literal["x"]`"
|
||||
Foo("x")
|
||||
```
|
||||
|
||||
### Incompatible signatures
|
||||
|
||||
```py
|
||||
|
|
|
|||
|
|
@ -150,6 +150,19 @@ reveal_type(f)
|
|||
reveal_type(f(1))
|
||||
```
|
||||
|
||||
### `functools.cached_property`
|
||||
|
||||
```py
|
||||
from functools import cached_property
|
||||
|
||||
class Foo:
|
||||
@cached_property
|
||||
def foo(self) -> str:
|
||||
return "a"
|
||||
|
||||
reveal_type(Foo().foo) # revealed: str
|
||||
```
|
||||
|
||||
## Lambdas as decorators
|
||||
|
||||
```py
|
||||
|
|
|
|||
|
|
@ -263,15 +263,13 @@ class C(Generic[T]):
|
|||
x: T
|
||||
|
||||
c: C[int] = C()
|
||||
# TODO: revealed: C[int]
|
||||
reveal_type(c) # revealed: C[Unknown]
|
||||
reveal_type(c) # revealed: C[int]
|
||||
```
|
||||
|
||||
The typevars of a fully specialized generic class should no longer be visible:
|
||||
|
||||
```py
|
||||
# TODO: revealed: int
|
||||
reveal_type(c.x) # revealed: Unknown
|
||||
reveal_type(c.x) # revealed: int
|
||||
```
|
||||
|
||||
If the type parameter is not specified explicitly, and there are no constraints that let us infer a
|
||||
|
|
|
|||
|
|
@ -226,15 +226,13 @@ class C[T]:
|
|||
x: T
|
||||
|
||||
c: C[int] = C()
|
||||
# TODO: revealed: C[int]
|
||||
reveal_type(c) # revealed: C[Unknown]
|
||||
reveal_type(c) # revealed: C[int]
|
||||
```
|
||||
|
||||
The typevars of a fully specialized generic class should no longer be visible:
|
||||
|
||||
```py
|
||||
# TODO: revealed: int
|
||||
reveal_type(c.x) # revealed: Unknown
|
||||
reveal_type(c.x) # revealed: int
|
||||
```
|
||||
|
||||
If the type parameter is not specified explicitly, and there are no constraints that let us infer a
|
||||
|
|
|
|||
|
|
@ -928,7 +928,9 @@ TMsg = TypeVar("TMsg", bound=Msg)
|
|||
class Builder(Generic[TMsg]):
|
||||
def build(self) -> Stream[TMsg]:
|
||||
stream: Stream[TMsg] = Stream()
|
||||
# TODO: no error
|
||||
# `Stream` is invariant, so `Stream[Msg]` is not a supertype of `Stream[TMsg]`;
|
||||
# therefore `_handler` is not compatible with `apply` here.
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-assignment]
|
||||
stream = stream.apply(self._handler)
|
||||
return stream
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use std::time::Duration;
|
|||
use bitflags::bitflags;
|
||||
use call::{CallDunderError, CallError, CallErrorKind};
|
||||
use context::InferContext;
|
||||
use diagnostic::{INVALID_CONTEXT_MANAGER, NOT_ITERABLE, POSSIBLY_MISSING_IMPLICIT_CALL};
|
||||
use diagnostic::{INVALID_CONTEXT_MANAGER, NOT_ITERABLE};
|
||||
use ruff_db::Instant;
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_db::files::File;
|
||||
|
|
@ -5988,11 +5988,11 @@ impl<'db> Type<'db> {
|
|||
},
|
||||
|
||||
Type::ClassLiteral(class) => match class.known(db) {
|
||||
// TODO: Ideally we'd use `try_call_constructor` for all constructor calls.
|
||||
// TODO: Ideally we'd use `constructor_bindings` for all constructor calls.
|
||||
// Currently we don't for a few special known types, either because their
|
||||
// constructors are defined with overloads, or because we want to special case
|
||||
// their return type beyond what typeshed provides (though this support could
|
||||
// likely be moved into the `try_call_constructor` path). Once we support
|
||||
// likely be moved into the `constructor_bindings` path). Once we support
|
||||
// overloads, re-evaluate the need for these arms.
|
||||
Some(KnownClass::Bool) => {
|
||||
// ```py
|
||||
|
|
@ -6365,20 +6365,22 @@ impl<'db> Type<'db> {
|
|||
.into()
|
||||
}
|
||||
|
||||
// Most class literal constructor calls are handled by `try_call_constructor` and
|
||||
// Most class literal constructor calls are handled by `constructor_bindings` and
|
||||
// not via getting the signature here. This signature can still be used in some
|
||||
// cases (e.g. evaluating callable subtyping). TODO improve this definition
|
||||
// (intersection of `__new__` and `__init__` signatures? and respect metaclass
|
||||
// `__call__`).
|
||||
_ => Binding::single(
|
||||
self,
|
||||
Signature::new_generic(
|
||||
class.generic_context(db),
|
||||
Parameters::gradual_form(),
|
||||
self.to_instance(db),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
_ => self.constructor_bindings(db).unwrap_or_else(|| {
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new_generic(
|
||||
class.generic_context(db),
|
||||
Parameters::gradual_form(),
|
||||
self.to_instance(db),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}),
|
||||
},
|
||||
|
||||
Type::SpecialForm(SpecialFormType::TypedDict) => {
|
||||
|
|
@ -6411,7 +6413,7 @@ impl<'db> Type<'db> {
|
|||
Binding::single(self, Signature::todo("functional `NamedTuple` syntax")).into()
|
||||
}
|
||||
|
||||
Type::GenericAlias(_) => {
|
||||
Type::GenericAlias(_) => self.constructor_bindings(db).unwrap_or_else(|| {
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
Binding::single(
|
||||
|
|
@ -6419,16 +6421,19 @@ impl<'db> Type<'db> {
|
|||
Signature::new(Parameters::gradual_form(), self.to_instance(db)),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}),
|
||||
|
||||
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||
SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).bindings(db),
|
||||
|
||||
// Most type[] constructor calls are handled by `try_call_constructor` and not via
|
||||
// Most type[] constructor calls are handled by `constructor_bindings` and not via
|
||||
// getting the signature here. This signature can still be used in some cases (e.g.
|
||||
// evaluating callable subtyping). TODO improve this definition (intersection of
|
||||
// `__new__` and `__init__` signatures? and respect metaclass `__call__`).
|
||||
SubclassOfInner::Class(class) => Type::from(class).bindings(db),
|
||||
// evaluating callable subtyping) or when constructor bindings are not
|
||||
// available. TODO improve this definition (intersection of `__new__` and
|
||||
// `__init__` signatures? and respect metaclass `__call__`).
|
||||
SubclassOfInner::Class(class) => self
|
||||
.constructor_bindings(db)
|
||||
.unwrap_or_else(|| Type::from(class).bindings(db)),
|
||||
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
|
|
@ -6540,6 +6545,231 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
// Build bindings for constructor calls by combining `__new__`/`__init__` signatures; return
|
||||
// `None` for cases that keep their manual call signatures.
|
||||
fn constructor_bindings(self, db: &'db dyn Db) -> Option<Bindings<'db>> {
|
||||
fn resolve_dunder_new_callable<'db>(
|
||||
db: &'db dyn Db,
|
||||
owner: Type<'db>,
|
||||
place: Place<'db>,
|
||||
) -> Option<(Type<'db>, Definedness)> {
|
||||
match place.try_call_dunder_get(db, owner) {
|
||||
Place::Defined(callable, _, definedness) => Some((callable, definedness)),
|
||||
Place::Undefined => None,
|
||||
}
|
||||
}
|
||||
fn bind_constructor_new<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings: Bindings<'db>,
|
||||
self_type: Type<'db>,
|
||||
) -> Bindings<'db> {
|
||||
bindings.map(|binding| {
|
||||
let mut binding = binding;
|
||||
// If descriptor binding produced a bound callable, bake that into the signature
|
||||
// first, then bind `cls` for constructor-call semantics (the call site omits `cls`).
|
||||
// Note: This intentionally preserves `type.__call__` behavior for `@classmethod __new__`,
|
||||
// which receives an extra implicit `cls` and errors at call sites.
|
||||
binding.bake_bound_type_into_overloads(db);
|
||||
binding.bound_type = Some(self_type);
|
||||
binding
|
||||
})
|
||||
}
|
||||
|
||||
let class = match self {
|
||||
Type::ClassLiteral(class) => ClassType::NonGeneric(class),
|
||||
Type::GenericAlias(alias) => ClassType::Generic(alias),
|
||||
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||
SubclassOfInner::Class(class) => class,
|
||||
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (class_literal, class_specialization) = class.class_literal(db);
|
||||
let class_generic_context = class_literal.generic_context(db);
|
||||
// This helper is called from multiple `Type` variants (not just the `ClassLiteral` arm in
|
||||
// `Type::bindings`), so manual-constructor exemptions must live here too.
|
||||
if class_literal.is_typed_dict(db)
|
||||
|| class::CodeGeneratorKind::TypedDict.matches(db, class_literal, class_specialization)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let known = class.known(db);
|
||||
if matches!(
|
||||
known,
|
||||
Some(
|
||||
KnownClass::Bool
|
||||
| KnownClass::Str
|
||||
| KnownClass::Type
|
||||
| KnownClass::Object
|
||||
| KnownClass::Property
|
||||
| KnownClass::Super
|
||||
| KnownClass::TypeAliasType
|
||||
| KnownClass::Deprecated
|
||||
)
|
||||
) {
|
||||
// Manual signatures for known constructors.
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!(known, Some(KnownClass::Tuple)) && !class.is_generic() {
|
||||
// Non-generic tuple constructors are defined via overloads.
|
||||
return None;
|
||||
}
|
||||
|
||||
// temporary special-casing for all subclasses of `enum.Enum`
|
||||
// until we support the functional syntax for creating enum classes
|
||||
if KnownClass::Enum
|
||||
.to_class_literal(db)
|
||||
.to_class_type(db)
|
||||
.is_some_and(|enum_class| class.is_subclass_of(db, enum_class))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// If we are trying to construct a non-specialized generic class, we should use the
|
||||
// constructor parameters to try to infer the class specialization. To do this, we need to
|
||||
// tweak our member lookup logic a bit. Normally, when looking up a class or instance
|
||||
// member, we first apply the class's default specialization, and apply that specialization
|
||||
// to the type of the member. To infer a specialization from the argument types, we need to
|
||||
// have the class's typevars still in the method signature when we attempt to call it. To
|
||||
// do this, we instead use the _identity_ specialization, which maps each of the class's
|
||||
// generic typevars to itself.
|
||||
let self_type = match self {
|
||||
Type::ClassLiteral(class) if class.generic_context(db).is_some() => {
|
||||
Type::from(class.identity_specialization(db))
|
||||
}
|
||||
_ => 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.
|
||||
// The logic is roughly as follows:
|
||||
// 1. If `__new__` is defined anywhere in the MRO (except for `object`, since it is always
|
||||
// present), we validate the constructor arguments against it. We then validate `__init__`,
|
||||
// but only if it is defined somewhere except `object`. This is because `object.__init__`
|
||||
// allows arbitrary arguments if and only if `__new__` is defined, but typeshed
|
||||
// defines `__init__` for `object` with no arguments.
|
||||
// 2. If `__new__` is not found, we call `__init__`. Here, we allow it to fallback all
|
||||
// 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.
|
||||
|
||||
// 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
|
||||
// a class, metaclass attribute would take precedence. But by avoiding `__new__` on
|
||||
// `object` we would inadvertently unhide `__new__` on `type`, which is not what we want.
|
||||
// An alternative might be to not skip `object.__new__` but instead mark it such that it's
|
||||
// easy to check if that's the one we found?
|
||||
// Note that `__new__` is a static method, so we must bind the `cls` argument when forming
|
||||
// constructor-call bindings.
|
||||
let new_method = self_type.lookup_dunder_new(db);
|
||||
|
||||
// Construct an instance type to look up `__init__`. We use `self_type` (possibly identity-
|
||||
// specialized) so the instance retains inferable class typevars during constructor checking.
|
||||
// TODO: we should use the actual return type of `__new__` to determine the instance type
|
||||
let init_ty = self_type.to_instance(db)?;
|
||||
|
||||
// Lookup the `__init__` instance method in the MRO, excluding `object` initially; we only
|
||||
// fall back to `object.__init__` in the `__new__`-absent case (see rules above).
|
||||
let init_method_no_object = init_ty.member_lookup_with_policy(
|
||||
db,
|
||||
"__init__".into(),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK | MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
|
||||
);
|
||||
|
||||
let mut missing_init_bindings = None;
|
||||
let (new_bindings, has_any_new) = match new_method.as_ref().map(|method| method.place) {
|
||||
Some(place) => match resolve_dunder_new_callable(db, self_type, place) {
|
||||
Some((new_callable, definedness)) => {
|
||||
let mut bindings =
|
||||
bind_constructor_new(db, new_callable.bindings(db), self_type);
|
||||
if definedness == Definedness::PossiblyUndefined {
|
||||
bindings.set_implicit_dunder_new_is_possibly_unbound();
|
||||
}
|
||||
(Some((bindings, new_callable)), true)
|
||||
}
|
||||
None => (None, false),
|
||||
},
|
||||
None => (None, false),
|
||||
};
|
||||
|
||||
// Only fall back to `object.__init__` when `__new__` is absent.
|
||||
let init_bindings = match (&init_method_no_object.place, has_any_new) {
|
||||
(Place::Defined(init_method, _, definedness), _) => {
|
||||
let mut bindings = init_method.bindings(db);
|
||||
if *definedness == Definedness::PossiblyUndefined {
|
||||
bindings.set_implicit_dunder_init_is_possibly_unbound();
|
||||
}
|
||||
Some((bindings, *init_method))
|
||||
}
|
||||
(Place::Undefined, false) => {
|
||||
let init_method_with_object = init_ty.member_lookup_with_policy(
|
||||
db,
|
||||
"__init__".into(),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
);
|
||||
match init_method_with_object.place {
|
||||
Place::Defined(init_method, _, definedness) => {
|
||||
let mut bindings = init_method.bindings(db);
|
||||
if definedness == Definedness::PossiblyUndefined {
|
||||
bindings.set_implicit_dunder_init_is_possibly_unbound();
|
||||
}
|
||||
Some((bindings, init_method))
|
||||
}
|
||||
Place::Undefined => {
|
||||
// If we are using vendored typeshed, it should be impossible to have missing
|
||||
// or unbound `__init__` method on a class, as all classes have `object` in MRO.
|
||||
// Thus the following may only trigger if a custom typeshed is used.
|
||||
// Custom/broken typeshed: no `__init__` available even after falling back
|
||||
// to `object`. Keep analysis going and surface the missing-implicit-call
|
||||
// lint via the builder.
|
||||
let mut bindings: Bindings<'db> = Binding::single(
|
||||
self_type,
|
||||
Signature::new(Parameters::gradual_form(), Some(init_ty)),
|
||||
)
|
||||
.into();
|
||||
bindings.set_implicit_dunder_init_is_possibly_unbound();
|
||||
missing_init_bindings = Some(bindings);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
(Place::Undefined, true) => None,
|
||||
};
|
||||
|
||||
let bindings = if let Some(bindings) = missing_init_bindings {
|
||||
bindings
|
||||
} else {
|
||||
match (new_bindings, init_bindings) {
|
||||
(Some((new_bindings, new_callable)), Some((init_bindings, init_callable))) => {
|
||||
let callable_type = UnionBuilder::new(db)
|
||||
.add(new_callable)
|
||||
.add(init_callable)
|
||||
.build();
|
||||
// Use both `__new__` and `__init__` bindings so argument inference/checking
|
||||
// happens under the combined constructor-call type context.
|
||||
// In ty unions of callables are checked as "all must accept".
|
||||
Bindings::from_union(callable_type, [new_bindings, init_bindings])
|
||||
}
|
||||
(Some((new_bindings, _)), None) => new_bindings,
|
||||
(None, Some((init_bindings, _))) => init_bindings,
|
||||
(None, None) => return None,
|
||||
}
|
||||
};
|
||||
|
||||
Some(
|
||||
bindings
|
||||
.with_generic_context(db, class_generic_context)
|
||||
.with_constructor_instance_type(init_ty),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calls `self`. Returns a [`CallError`] if `self` is (always or possibly) not callable, or if
|
||||
/// the arguments are not compatible with the formal parameters.
|
||||
///
|
||||
|
|
@ -7076,258 +7306,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given a class literal or non-dynamic `SubclassOf` type, try calling it (creating an instance)
|
||||
/// and return the resulting instance type.
|
||||
///
|
||||
/// The `infer_argument_types` closure should be invoked with the signatures of `__new__` and
|
||||
/// `__init__`, such that the argument types can be inferred with the correct type context.
|
||||
///
|
||||
/// Models `type.__call__` behavior.
|
||||
/// TODO: model metaclass `__call__`.
|
||||
///
|
||||
/// E.g., for the following code, infer the type of `Foo()`:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// pass
|
||||
///
|
||||
/// Foo()
|
||||
/// ```
|
||||
fn try_call_constructor<'ast>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
infer_argument_types: impl FnOnce(Option<Bindings<'db>>) -> CallArguments<'ast, 'db>,
|
||||
tcx: TypeContext<'db>,
|
||||
) -> Result<Type<'db>, ConstructorCallError<'db>> {
|
||||
debug_assert!(matches!(
|
||||
self,
|
||||
Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_)
|
||||
));
|
||||
|
||||
// If we are trying to construct a non-specialized generic class, we should use the
|
||||
// constructor parameters to try to infer the class specialization. To do this, we need to
|
||||
// tweak our member lookup logic a bit. Normally, when looking up a class or instance
|
||||
// member, we first apply the class's default specialization, and apply that specialization
|
||||
// to the type of the member. To infer a specialization from the argument types, we need to
|
||||
// have the class's typevars still in the method signature when we attempt to call it. To
|
||||
// do this, we instead use the _identity_ specialization, which maps each of the class's
|
||||
// generic typevars to itself.
|
||||
let (generic_origin, generic_context, self_type) = match self {
|
||||
Type::ClassLiteral(class) => match class.generic_context(db) {
|
||||
Some(generic_context) => (
|
||||
Some(class),
|
||||
Some(generic_context),
|
||||
// It is important that identity_specialization specializes the class with
|
||||
// _inferable_ typevars, so that our specialization inference logic will
|
||||
// try to find a specialization for them.
|
||||
Type::from(class.identity_specialization(db)),
|
||||
),
|
||||
_ => (None, None, self),
|
||||
},
|
||||
_ => (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.
|
||||
// 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
|
||||
// if it is defined somewhere except object. This is because `object.__init__`
|
||||
// allows arbitrary arguments if and only if `__new__` is defined, but typeshed
|
||||
// defines `__init__` for `object` with no arguments.
|
||||
// 2. If `__new__` is not found, we call `__init__`. Here, we allow it to fallback all
|
||||
// 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.
|
||||
|
||||
// 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
|
||||
// a class, metaclass attribute would take precedence. But by avoiding `__new__` on
|
||||
// `object` we would inadvertently unhide `__new__` on `type`, which is not what we want.
|
||||
// An alternative might be to not skip `object.__new__` but instead mark it such that it's
|
||||
// easy to check if that's the one we found?
|
||||
// Note that `__new__` is a static method, so we must inject the `cls` argument.
|
||||
let new_method = self_type.lookup_dunder_new(db);
|
||||
|
||||
// Construct an instance type that we can use to look up the `__init__` instance method.
|
||||
// This performs the same logic as `Type::to_instance`, except for generic class literals.
|
||||
// TODO: we should use the actual return type of `__new__` to determine the instance type
|
||||
let init_ty = self_type
|
||||
.to_instance(db)
|
||||
.expect("type should be convertible to instance type");
|
||||
|
||||
// Lookup the `__init__` instance method in the MRO.
|
||||
let init_method = init_ty.member_lookup_with_policy(
|
||||
db,
|
||||
"__init__".into(),
|
||||
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)),
|
||||
),
|
||||
|
||||
(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,
|
||||
};
|
||||
|
||||
let argument_types = infer_argument_types(bindings);
|
||||
|
||||
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) => {
|
||||
let argument_types = argument_types.with_self(Some(self_type));
|
||||
let result = new_method
|
||||
.bindings(db)
|
||||
.with_constructor_instance_type(init_ty)
|
||||
.match_parameters(db, &argument_types)
|
||||
.check_types(db, &argument_types, tcx, &[]);
|
||||
|
||||
if boundness == Definedness::PossiblyUndefined {
|
||||
Some(Err(DunderNewCallError::PossiblyUnbound(result.err())))
|
||||
} else {
|
||||
Some(result.map_err(DunderNewCallError::CallError))
|
||||
}
|
||||
}
|
||||
Place::Undefined => None,
|
||||
}
|
||||
});
|
||||
|
||||
let init_call_outcome = if new_call_outcome.is_none() || !init_method.is_undefined() {
|
||||
let call_result = match init_ty
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
"__init__".into(),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
)
|
||||
.place
|
||||
{
|
||||
Place::Undefined => Err(CallDunderError::MethodNotAvailable),
|
||||
Place::Defined(dunder_callable, _, boundness) => {
|
||||
let bindings = dunder_callable
|
||||
.bindings(db)
|
||||
.with_constructor_instance_type(init_ty);
|
||||
|
||||
bindings
|
||||
.match_parameters(db, &argument_types)
|
||||
.check_types(db, &argument_types, tcx, &[])
|
||||
.map_err(CallDunderError::from)
|
||||
.and_then(|bindings| {
|
||||
if boundness == Definedness::PossiblyUndefined {
|
||||
Err(CallDunderError::PossiblyUnbound(Box::new(bindings)))
|
||||
} else {
|
||||
Ok(bindings)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Some(call_result)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Note that we use `self` here, not `self_type`, so that if constructor argument inference
|
||||
// fails, we fail back to the default specialization.
|
||||
let instance_ty = self
|
||||
.to_instance(db)
|
||||
.expect("type should be convertible to instance type");
|
||||
|
||||
match (generic_origin, new_call_outcome, init_call_outcome) {
|
||||
// All calls are successful or not called at all
|
||||
(
|
||||
Some(generic_origin),
|
||||
new_call_outcome @ (None | Some(Ok(_))),
|
||||
init_call_outcome @ (None | Some(Ok(_))),
|
||||
) => {
|
||||
fn combine_specializations<'db>(
|
||||
db: &'db dyn Db,
|
||||
s1: Option<Specialization<'db>>,
|
||||
s2: Option<Specialization<'db>>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
match (s1, s2) {
|
||||
(None, None) => None,
|
||||
(Some(s), None) | (None, Some(s)) => Some(s),
|
||||
(Some(s1), Some(s2)) => Some(s1.combine(db, s2)),
|
||||
}
|
||||
}
|
||||
|
||||
let specialize_constructor = |outcome: Option<Bindings<'db>>| {
|
||||
let (_, binding) = outcome
|
||||
.as_ref()?
|
||||
.single_element()?
|
||||
.matching_overloads()
|
||||
.next()?;
|
||||
binding.specialization()?.restrict(db, generic_context?)
|
||||
};
|
||||
|
||||
let new_specialization =
|
||||
specialize_constructor(new_call_outcome.and_then(Result::ok));
|
||||
let init_specialization =
|
||||
specialize_constructor(init_call_outcome.and_then(Result::ok));
|
||||
let specialization =
|
||||
combine_specializations(db, new_specialization, init_specialization);
|
||||
let specialized = specialization
|
||||
.map(|specialization| {
|
||||
Type::instance(
|
||||
db,
|
||||
generic_origin.apply_specialization(db, |_| specialization),
|
||||
)
|
||||
})
|
||||
.unwrap_or(instance_ty);
|
||||
Ok(specialized)
|
||||
}
|
||||
|
||||
(None, None | Some(Ok(_)), None | Some(Ok(_))) => Ok(instance_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))
|
||||
}
|
||||
(_, Some(Err(error)), None | Some(Ok(_))) => {
|
||||
// custom `__new__` was called and failed, but init is ok
|
||||
Err(ConstructorCallError::New(instance_ty, error))
|
||||
}
|
||||
(_, Some(Err(new_error)), Some(Err(init_error))) => {
|
||||
// custom `__new__` was called and failed, and `__init__` is also not ok
|
||||
Err(ConstructorCallError::NewAndInit(
|
||||
instance_ty,
|
||||
new_error,
|
||||
init_error,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
|
|
@ -11872,107 +11850,6 @@ impl<'db> BoolError<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Represents possibly failure modes of implicit `__new__` calls.
|
||||
#[derive(Debug)]
|
||||
enum DunderNewCallError<'db> {
|
||||
/// The call to `__new__` failed.
|
||||
CallError(CallError<'db>),
|
||||
/// The `__new__` method could be unbound. If the call to the
|
||||
/// method has also failed, this variant also includes the
|
||||
/// corresponding `CallError`.
|
||||
PossiblyUnbound(Option<CallError<'db>>),
|
||||
}
|
||||
|
||||
/// Error returned if a class instantiation call failed
|
||||
#[derive(Debug)]
|
||||
enum ConstructorCallError<'db> {
|
||||
Init(Type<'db>, CallDunderError<'db>),
|
||||
New(Type<'db>, DunderNewCallError<'db>),
|
||||
NewAndInit(Type<'db>, DunderNewCallError<'db>, CallDunderError<'db>),
|
||||
}
|
||||
|
||||
impl<'db> ConstructorCallError<'db> {
|
||||
fn return_type(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::Init(ty, _) => *ty,
|
||||
Self::New(ty, _) => *ty,
|
||||
Self::NewAndInit(ty, _, _) => *ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn report_diagnostic(
|
||||
&self,
|
||||
context: &InferContext<'db, '_>,
|
||||
context_expression_type: Type<'db>,
|
||||
context_expression_node: ast::AnyNodeRef,
|
||||
) {
|
||||
let report_init_error = |call_dunder_error: &CallDunderError<'db>| match call_dunder_error {
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
|
||||
{
|
||||
// If we are using vendored typeshed, it should be impossible to have missing
|
||||
// or unbound `__init__` method on a class, as all classes have `object` in MRO.
|
||||
// Thus the following may only trigger if a custom typeshed is used.
|
||||
builder.into_diagnostic(format_args!(
|
||||
"`__init__` method is missing on type `{}`. \
|
||||
Make sure your `object` in typeshed has its definition.",
|
||||
context_expression_type.display(context.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
CallDunderError::PossiblyUnbound(bindings) => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__init__` 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);
|
||||
}
|
||||
};
|
||||
|
||||
let report_new_error = |error: &DunderNewCallError<'db>| match error {
|
||||
DunderNewCallError::PossiblyUnbound(call_error) => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__new__` on type `{}` may be missing.",
|
||||
context_expression_type.display(context.db()),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(CallError(_kind, bindings)) = call_error {
|
||||
bindings.report_diagnostics(context, context_expression_node);
|
||||
}
|
||||
}
|
||||
DunderNewCallError::CallError(CallError(_kind, bindings)) => {
|
||||
bindings.report_diagnostics(context, context_expression_node);
|
||||
}
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::Init(_, init_call_dunder_error) => {
|
||||
report_init_error(init_call_dunder_error);
|
||||
}
|
||||
Self::New(_, new_call_error) => {
|
||||
report_new_error(new_call_error);
|
||||
}
|
||||
Self::NewAndInit(_, new_call_error, init_call_dunder_error) => {
|
||||
report_new_error(new_call_error);
|
||||
report_init_error(init_call_dunder_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-exhaustive enumeration of relations that can exist between types.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum TypeRelation<'db> {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ use crate::types::function::{
|
|||
OverloadLiteral,
|
||||
};
|
||||
use crate::types::generics::{
|
||||
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
|
||||
GenericContext, InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
|
||||
};
|
||||
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
|
||||
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
|
||||
|
|
@ -67,6 +67,12 @@ pub(crate) struct Bindings<'db> {
|
|||
/// The type of the instance being constructed, if this signature is for a constructor.
|
||||
constructor_instance_type: Option<Type<'db>>,
|
||||
|
||||
/// Whether implicit `__new__` calls may be missing in constructor bindings.
|
||||
implicit_dunder_new_is_possibly_unbound: bool,
|
||||
|
||||
/// Whether implicit `__init__` calls may be missing in constructor bindings.
|
||||
implicit_dunder_init_is_possibly_unbound: bool,
|
||||
|
||||
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union
|
||||
/// type.
|
||||
elements: SmallVec<[CallableBinding<'db>; 1]>,
|
||||
|
|
@ -82,16 +88,26 @@ impl<'db> Bindings<'db> {
|
|||
where
|
||||
I: IntoIterator<Item = Bindings<'db>>,
|
||||
{
|
||||
let elements: SmallVec<_> = elements
|
||||
.into_iter()
|
||||
.flat_map(|s| s.elements.into_iter())
|
||||
.collect();
|
||||
let mut implicit_dunder_new_is_possibly_unbound = false;
|
||||
let mut implicit_dunder_init_is_possibly_unbound = false;
|
||||
let mut elements_acc = SmallVec::new();
|
||||
|
||||
for set in elements {
|
||||
implicit_dunder_new_is_possibly_unbound |= set.implicit_dunder_new_is_possibly_unbound;
|
||||
implicit_dunder_init_is_possibly_unbound |=
|
||||
set.implicit_dunder_init_is_possibly_unbound;
|
||||
elements_acc.extend(set.elements);
|
||||
}
|
||||
|
||||
let elements = elements_acc;
|
||||
assert!(!elements.is_empty());
|
||||
Self {
|
||||
callable_type,
|
||||
elements,
|
||||
argument_forms: ArgumentForms::new(0),
|
||||
constructor_instance_type: None,
|
||||
implicit_dunder_new_is_possibly_unbound,
|
||||
implicit_dunder_init_is_possibly_unbound,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,22 +130,59 @@ impl<'db> Bindings<'db> {
|
|||
binding.constructor_instance_type = Some(constructor_instance_type);
|
||||
for binding in &mut binding.overloads {
|
||||
binding.constructor_instance_type = Some(constructor_instance_type);
|
||||
binding.signature.return_ty = Some(constructor_instance_type);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_generic_context(
|
||||
mut self,
|
||||
db: &'db dyn Db,
|
||||
generic_context: Option<GenericContext<'db>>,
|
||||
) -> Self {
|
||||
let Some(generic_context) = generic_context else {
|
||||
return self;
|
||||
};
|
||||
for binding in &mut self.elements {
|
||||
for overload in &mut binding.overloads {
|
||||
overload.signature.generic_context = GenericContext::merge_optional(
|
||||
db,
|
||||
overload.signature.generic_context,
|
||||
Some(generic_context),
|
||||
);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn set_dunder_call_is_possibly_unbound(&mut self) {
|
||||
for binding in &mut self.elements {
|
||||
binding.dunder_call_is_possibly_unbound = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_implicit_dunder_new_is_possibly_unbound(&mut self) {
|
||||
self.implicit_dunder_new_is_possibly_unbound = true;
|
||||
}
|
||||
|
||||
pub(crate) fn set_implicit_dunder_init_is_possibly_unbound(&mut self) {
|
||||
self.implicit_dunder_init_is_possibly_unbound = true;
|
||||
}
|
||||
|
||||
pub(crate) fn argument_forms(&self) -> &[Option<ParameterForm>] {
|
||||
&self.argument_forms.values
|
||||
}
|
||||
|
||||
pub(crate) fn has_implicit_dunder_new_is_possibly_unbound(&self) -> bool {
|
||||
self.implicit_dunder_new_is_possibly_unbound
|
||||
}
|
||||
|
||||
pub(crate) fn has_implicit_dunder_init_is_possibly_unbound(&self) -> bool {
|
||||
self.implicit_dunder_init_is_possibly_unbound
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> std::slice::Iter<'_, CallableBinding<'db>> {
|
||||
self.elements.iter()
|
||||
}
|
||||
|
|
@ -139,6 +192,8 @@ impl<'db> Bindings<'db> {
|
|||
callable_type: self.callable_type,
|
||||
argument_forms: self.argument_forms,
|
||||
constructor_instance_type: self.constructor_instance_type,
|
||||
implicit_dunder_new_is_possibly_unbound: self.implicit_dunder_new_is_possibly_unbound,
|
||||
implicit_dunder_init_is_possibly_unbound: self.implicit_dunder_init_is_possibly_unbound,
|
||||
elements: self.elements.into_iter().map(f).collect(),
|
||||
}
|
||||
}
|
||||
|
|
@ -276,10 +331,47 @@ impl<'db> Bindings<'db> {
|
|||
self.constructor_instance_type
|
||||
}
|
||||
|
||||
// Constructor calls should combine `__new__`/`__init__` specializations instead of unioning.
|
||||
fn constructor_return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
let constructor_instance_type = self.constructor_instance_type?;
|
||||
let Some(class_specialization) = constructor_instance_type.class_specialization(db) else {
|
||||
return Some(constructor_instance_type);
|
||||
};
|
||||
let class_context = class_specialization.generic_context(db);
|
||||
|
||||
let mut combined: Option<Specialization<'db>> = None;
|
||||
for binding in &self.elements {
|
||||
// For constructors, use the first matching overload (declaration order) to avoid
|
||||
// merging incompatible constructor specializations.
|
||||
let Some((_, overload)) = binding.matching_overloads().next() else {
|
||||
continue;
|
||||
};
|
||||
let Some(specialization) = overload.specialization else {
|
||||
continue;
|
||||
};
|
||||
let Some(specialization) = specialization.restrict(db, class_context) else {
|
||||
continue;
|
||||
};
|
||||
combined = Some(match combined {
|
||||
None => specialization,
|
||||
Some(previous) => previous.combine(db, specialization),
|
||||
});
|
||||
}
|
||||
|
||||
// If constructor inference doesn't yield a specialization, fall back to the default
|
||||
// specialization to avoid leaking inferable typevars in the constructed instance.
|
||||
let specialization =
|
||||
combined.unwrap_or_else(|| class_context.default_specialization(db, None));
|
||||
Some(constructor_instance_type.apply_specialization(db, specialization))
|
||||
}
|
||||
|
||||
/// Returns the return type of the call. For successful calls, this is the actual return type.
|
||||
/// For calls with binding errors, this is a type that best approximates the return type. For
|
||||
/// types that are not callable, returns `Type::Unknown`.
|
||||
pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
if let Some(return_ty) = self.constructor_return_type(db) {
|
||||
return return_ty;
|
||||
}
|
||||
if let [binding] = self.elements.as_slice() {
|
||||
return binding.return_type();
|
||||
}
|
||||
|
|
@ -1451,6 +1543,8 @@ impl<'db> From<CallableBinding<'db>> for Bindings<'db> {
|
|||
elements: smallvec_inline![from],
|
||||
argument_forms: ArgumentForms::new(0),
|
||||
constructor_instance_type: None,
|
||||
implicit_dunder_new_is_possibly_unbound: false,
|
||||
implicit_dunder_init_is_possibly_unbound: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1474,6 +1568,8 @@ impl<'db> From<Binding<'db>> for Bindings<'db> {
|
|||
elements: smallvec_inline![callable_binding],
|
||||
argument_forms: ArgumentForms::new(0),
|
||||
constructor_instance_type: None,
|
||||
implicit_dunder_new_is_possibly_unbound: false,
|
||||
implicit_dunder_init_is_possibly_unbound: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1576,6 +1672,15 @@ impl<'db> CallableBinding<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn bake_bound_type_into_overloads(&mut self, db: &'db dyn Db) {
|
||||
let Some(bound_self) = self.bound_type.take() else {
|
||||
return;
|
||||
};
|
||||
for overload in &mut self.overloads {
|
||||
overload.signature = overload.signature.bind_self(db, Some(bound_self));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_bound_type(mut self, bound_type: Type<'db>) -> Self {
|
||||
self.bound_type = Some(bound_type);
|
||||
self
|
||||
|
|
@ -3677,7 +3782,11 @@ impl<'db> Binding<'db> {
|
|||
for (keywords_index, keywords_type) in keywords_arguments {
|
||||
matcher.match_keyword_variadic(db, keywords_index, keywords_type);
|
||||
}
|
||||
self.return_ty = self.signature.return_ty.unwrap_or(Type::unknown());
|
||||
// For constructor calls, we currently return the constructed instance type (not `__init__`'s `None`).
|
||||
self.return_ty = self
|
||||
.constructor_instance_type
|
||||
.or(self.signature.return_ty)
|
||||
.unwrap_or(Type::unknown());
|
||||
self.parameter_tys = vec![None; parameters.len()].into_boxed_slice();
|
||||
self.variadic_argument_matched_to_variadic_parameter =
|
||||
matcher.variadic_argument_matched_to_variadic_parameter;
|
||||
|
|
@ -3719,10 +3828,6 @@ impl<'db> Binding<'db> {
|
|||
self.return_ty
|
||||
}
|
||||
|
||||
pub(crate) fn specialization(&self) -> Option<Specialization<'db>> {
|
||||
self.specialization
|
||||
}
|
||||
|
||||
/// Returns the bound types for each parameter, in parameter source order, or `None` if no
|
||||
/// argument was matched to that parameter.
|
||||
pub(crate) fn parameter_types(&self) -> &[Option<Type<'db>>] {
|
||||
|
|
|
|||
|
|
@ -8458,6 +8458,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
callable_type: Type<'db>,
|
||||
tcx: TypeContext<'db>,
|
||||
) -> Type<'db> {
|
||||
fn report_missing_implicit_constructor_call<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
db: &'db dyn Db,
|
||||
callable_type: Type<'db>,
|
||||
call_expression: &ast::ExprCall,
|
||||
bindings: &Bindings<'db>,
|
||||
) {
|
||||
if bindings.has_implicit_dunder_new_is_possibly_unbound() {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__new__` on type `{}` may be missing.",
|
||||
callable_type.display(db),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if bindings.has_implicit_dunder_init_is_possibly_unbound() {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__init__` on type `{}` may be missing.",
|
||||
callable_type.display(db),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ast::ExprCall {
|
||||
range: _,
|
||||
node_index: _,
|
||||
|
|
@ -8622,106 +8652,42 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
|
||||
// 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 has_special_cased_constructor = matches!(
|
||||
class.known(self.db()),
|
||||
Some(
|
||||
KnownClass::Bool
|
||||
| KnownClass::Str
|
||||
| KnownClass::Type
|
||||
| KnownClass::Object
|
||||
| KnownClass::Property
|
||||
| KnownClass::Super
|
||||
| KnownClass::TypeAliasType
|
||||
| KnownClass::Deprecated
|
||||
)
|
||||
) || (
|
||||
// Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
|
||||
// but constructor calls to `tuple[int]`, `tuple[int, ...]`, `tuple[int, *tuple[str, ...]]` (etc.)
|
||||
// are handled by the default constructor-call logic (we synthesize a `__new__` method for them
|
||||
// in `ClassType::own_class_member()`).
|
||||
class.is_known(self.db(), KnownClass::Tuple) && !class.is_generic()
|
||||
) || CodeGeneratorKind::TypedDict.matches(
|
||||
self.db(),
|
||||
class.class_literal(self.db()).0,
|
||||
class.class_literal(self.db()).1,
|
||||
);
|
||||
|
||||
// temporary special-casing for all subclasses of `enum.Enum`
|
||||
// until we support the functional syntax for creating enum classes
|
||||
if !has_special_cased_constructor
|
||||
&& 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))
|
||||
{
|
||||
// Inference of correctly-placed `TypeVar`, `ParamSpec`, and `NewType` definitions
|
||||
// is done in `infer_legacy_typevar`, `infer_paramspec`, and
|
||||
// `infer_newtype_expression`, and doesn't use the full call-binding machinery. If
|
||||
// we reach here, it means that someone is trying to instantiate one of these in an
|
||||
// invalid context.
|
||||
match class.known(self.db()) {
|
||||
Some(KnownClass::TypeVar | KnownClass::ExtensionsTypeVar) => {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `TypeVar` definition must be a simple variable assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec) => {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_PARAMSPEC, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `ParamSpec` definition must be a simple variable assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(KnownClass::NewType) => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_NEWTYPE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `NewType` definition must be a simple variable assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let db = self.db();
|
||||
let infer_call_arguments = |bindings: Option<Bindings<'db>>| {
|
||||
if let Some(bindings) = bindings {
|
||||
let bindings = bindings.match_parameters(self.db(), &call_arguments);
|
||||
self.infer_all_argument_types(
|
||||
arguments,
|
||||
&mut call_arguments,
|
||||
&bindings,
|
||||
tcx,
|
||||
MultiInferenceState::Intersect,
|
||||
// Inference of correctly-placed `TypeVar`, `ParamSpec`, and `NewType` definitions
|
||||
// is done in `infer_legacy_typevar`, `infer_paramspec`, and
|
||||
// `infer_newtype_expression`, and doesn't use the full call-binding machinery. If
|
||||
// we reach here, it means that someone is trying to instantiate one of these in an
|
||||
// invalid context.
|
||||
match class.known(self.db()) {
|
||||
Some(KnownClass::TypeVar | KnownClass::ExtensionsTypeVar) => {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `TypeVar` definition must be a simple variable assignment",
|
||||
);
|
||||
} else {
|
||||
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
|
||||
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
|
||||
}
|
||||
|
||||
call_arguments
|
||||
};
|
||||
|
||||
return callable_type
|
||||
.try_call_constructor(db, infer_call_arguments, tcx)
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, callable_type, call_expression.into());
|
||||
err.return_type()
|
||||
});
|
||||
}
|
||||
Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec) => {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_PARAMSPEC, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `ParamSpec` definition must be a simple variable assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(KnownClass::NewType) => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_NEWTYPE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `NewType` definition must be a simple variable assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8729,6 +8695,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.bindings(self.db())
|
||||
.match_parameters(self.db(), &call_arguments);
|
||||
|
||||
report_missing_implicit_constructor_call(
|
||||
&self.context,
|
||||
self.db(),
|
||||
callable_type,
|
||||
call_expression,
|
||||
&bindings,
|
||||
);
|
||||
|
||||
let bindings_result =
|
||||
self.infer_and_check_argument_types(arguments, &mut call_arguments, &mut bindings, tcx);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue