mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Proper assignability/subtyping checks for protocols with method members (#20165)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
bb9be263c7
commit
33b3d44ebd
5 changed files with 104 additions and 113 deletions
|
@ -170,10 +170,10 @@ reveal_type(len(ZeroOrOne())) # revealed: Literal[0, 1]
|
||||||
reveal_type(len(ZeroOrTrue())) # revealed: Literal[0, 1]
|
reveal_type(len(ZeroOrTrue())) # revealed: Literal[0, 1]
|
||||||
reveal_type(len(OneOrFalse())) # revealed: Literal[1, 0]
|
reveal_type(len(OneOrFalse())) # revealed: Literal[1, 0]
|
||||||
|
|
||||||
# TODO: Emit a diagnostic
|
# error: [invalid-argument-type] "Argument to function `len` is incorrect: Expected `Sized`, found `OneOrFoo`"
|
||||||
reveal_type(len(OneOrFoo())) # revealed: int
|
reveal_type(len(OneOrFoo())) # revealed: int
|
||||||
|
|
||||||
# TODO: Emit a diagnostic
|
# error: [invalid-argument-type] "Argument to function `len` is incorrect: Expected `Sized`, found `ZeroOrStr`"
|
||||||
reveal_type(len(ZeroOrStr())) # revealed: int
|
reveal_type(len(ZeroOrStr())) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -194,46 +194,6 @@ reveal_type(len(LiteralTrue())) # revealed: Literal[1]
|
||||||
reveal_type(len(LiteralFalse())) # revealed: Literal[0]
|
reveal_type(len(LiteralFalse())) # revealed: Literal[0]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Enums
|
|
||||||
|
|
||||||
```py
|
|
||||||
from enum import Enum, auto
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
class SomeEnum(Enum):
|
|
||||||
AUTO = auto()
|
|
||||||
INT = 2
|
|
||||||
STR = "4"
|
|
||||||
TUPLE = (8, "16")
|
|
||||||
INT_2 = 3_2
|
|
||||||
|
|
||||||
class Auto:
|
|
||||||
def __len__(self) -> Literal[SomeEnum.AUTO]:
|
|
||||||
return SomeEnum.AUTO
|
|
||||||
|
|
||||||
class Int:
|
|
||||||
def __len__(self) -> Literal[SomeEnum.INT]:
|
|
||||||
return SomeEnum.INT
|
|
||||||
|
|
||||||
class Str:
|
|
||||||
def __len__(self) -> Literal[SomeEnum.STR]:
|
|
||||||
return SomeEnum.STR
|
|
||||||
|
|
||||||
class Tuple:
|
|
||||||
def __len__(self) -> Literal[SomeEnum.TUPLE]:
|
|
||||||
return SomeEnum.TUPLE
|
|
||||||
|
|
||||||
class IntUnion:
|
|
||||||
def __len__(self) -> Literal[SomeEnum.INT, SomeEnum.INT_2]:
|
|
||||||
return SomeEnum.INT
|
|
||||||
|
|
||||||
reveal_type(len(Auto())) # revealed: int
|
|
||||||
reveal_type(len(Int())) # revealed: int
|
|
||||||
reveal_type(len(Str())) # revealed: int
|
|
||||||
reveal_type(len(Tuple())) # revealed: int
|
|
||||||
reveal_type(len(IntUnion())) # revealed: int
|
|
||||||
```
|
|
||||||
|
|
||||||
### Negative integers
|
### Negative integers
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -263,8 +223,8 @@ class SecondRequiredArgument:
|
||||||
# this is fine: the call succeeds at runtime since the second argument is optional
|
# this is fine: the call succeeds at runtime since the second argument is optional
|
||||||
reveal_type(len(SecondOptionalArgument())) # revealed: Literal[0]
|
reveal_type(len(SecondOptionalArgument())) # revealed: Literal[0]
|
||||||
|
|
||||||
# TODO: Emit a diagnostic
|
# error: [invalid-argument-type] "Argument to function `len` is incorrect: Expected `Sized`, found `SecondRequiredArgument`"
|
||||||
reveal_type(len(SecondRequiredArgument())) # revealed: Literal[1]
|
reveal_type(len(SecondRequiredArgument())) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
### No `__len__`
|
### No `__len__`
|
||||||
|
|
|
@ -1766,9 +1766,7 @@ class DefinitelyNotSubtype:
|
||||||
|
|
||||||
static_assert(is_subtype_of(NominalSubtype, P))
|
static_assert(is_subtype_of(NominalSubtype, P))
|
||||||
static_assert(not is_subtype_of(DefinitelyNotSubtype, P))
|
static_assert(not is_subtype_of(DefinitelyNotSubtype, P))
|
||||||
|
static_assert(not is_subtype_of(NotSubtype, P))
|
||||||
# TODO: should pass
|
|
||||||
static_assert(not is_subtype_of(NotSubtype, P)) # error: [static-assert-error]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A callable instance attribute is not sufficient for a type to satisfy a protocol with a method
|
A callable instance attribute is not sufficient for a type to satisfy a protocol with a method
|
||||||
|
@ -1924,27 +1922,22 @@ static_assert(is_assignable_to(NominalGeneric, LegacyClassScoped[int]))
|
||||||
# and there exist fully static materializations of `NewStyleClassScoped[Unknown]`
|
# and there exist fully static materializations of `NewStyleClassScoped[Unknown]`
|
||||||
# where `Nominal` would not be a subtype of the given materialization,
|
# where `Nominal` would not be a subtype of the given materialization,
|
||||||
# hence there is no subtyping relation:
|
# hence there is no subtyping relation:
|
||||||
#
|
static_assert(not is_subtype_of(NominalConcrete, NewStyleClassScoped))
|
||||||
# TODO: these should pass
|
static_assert(not is_subtype_of(NominalConcrete, LegacyClassScoped))
|
||||||
static_assert(not is_subtype_of(NominalConcrete, NewStyleClassScoped)) # error: [static-assert-error]
|
|
||||||
static_assert(not is_subtype_of(NominalConcrete, LegacyClassScoped)) # error: [static-assert-error]
|
|
||||||
|
|
||||||
# Similarly, `NominalGeneric` is implicitly `NominalGeneric[Unknown`]
|
# Similarly, `NominalGeneric` is implicitly `NominalGeneric[Unknown`]
|
||||||
#
|
static_assert(not is_subtype_of(NominalGeneric, NewStyleClassScoped[int]))
|
||||||
# TODO: these should pass
|
static_assert(not is_subtype_of(NominalGeneric, LegacyClassScoped[int]))
|
||||||
static_assert(not is_subtype_of(NominalGeneric, NewStyleClassScoped[int])) # error: [static-assert-error]
|
|
||||||
static_assert(not is_subtype_of(NominalGeneric, LegacyClassScoped[int])) # error: [static-assert-error]
|
|
||||||
|
|
||||||
static_assert(is_subtype_of(NominalConcrete, NewStyleClassScoped[int]))
|
static_assert(is_subtype_of(NominalConcrete, NewStyleClassScoped[int]))
|
||||||
static_assert(is_subtype_of(NominalConcrete, LegacyClassScoped[int]))
|
static_assert(is_subtype_of(NominalConcrete, LegacyClassScoped[int]))
|
||||||
static_assert(is_subtype_of(NominalGeneric[int], NewStyleClassScoped[int]))
|
static_assert(is_subtype_of(NominalGeneric[int], NewStyleClassScoped[int]))
|
||||||
static_assert(is_subtype_of(NominalGeneric[int], LegacyClassScoped[int]))
|
static_assert(is_subtype_of(NominalGeneric[int], LegacyClassScoped[int]))
|
||||||
|
|
||||||
# TODO: these should pass
|
static_assert(not is_assignable_to(NominalConcrete, NewStyleClassScoped[str]))
|
||||||
static_assert(not is_assignable_to(NominalConcrete, NewStyleClassScoped[str])) # error: [static-assert-error]
|
static_assert(not is_assignable_to(NominalConcrete, LegacyClassScoped[str]))
|
||||||
static_assert(not is_assignable_to(NominalConcrete, LegacyClassScoped[str])) # error: [static-assert-error]
|
static_assert(not is_subtype_of(NominalGeneric[int], NewStyleClassScoped[str]))
|
||||||
static_assert(not is_subtype_of(NominalGeneric[int], NewStyleClassScoped[str])) # error: [static-assert-error]
|
static_assert(not is_subtype_of(NominalGeneric[int], LegacyClassScoped[str]))
|
||||||
static_assert(not is_subtype_of(NominalGeneric[int], LegacyClassScoped[str])) # error: [static-assert-error]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And they can also have generic contexts scoped to the method:
|
And they can also have generic contexts scoped to the method:
|
||||||
|
@ -2219,24 +2212,24 @@ class Foo(Protocol):
|
||||||
static_assert(is_subtype_of(Callable[[int], str], Foo))
|
static_assert(is_subtype_of(Callable[[int], str], Foo))
|
||||||
static_assert(is_assignable_to(Callable[[int], str], Foo))
|
static_assert(is_assignable_to(Callable[[int], str], Foo))
|
||||||
|
|
||||||
# TODO: these should pass
|
static_assert(not is_subtype_of(Callable[[str], str], Foo))
|
||||||
static_assert(not is_subtype_of(Callable[[str], str], Foo)) # error: [static-assert-error]
|
static_assert(not is_assignable_to(Callable[[str], str], Foo))
|
||||||
static_assert(not is_assignable_to(Callable[[str], str], Foo)) # error: [static-assert-error]
|
static_assert(not is_subtype_of(Callable[[CallMeMaybe, int], str], Foo))
|
||||||
static_assert(not is_subtype_of(Callable[[CallMeMaybe, int], str], Foo)) # error: [static-assert-error]
|
static_assert(not is_assignable_to(Callable[[CallMeMaybe, int], str], Foo))
|
||||||
static_assert(not is_assignable_to(Callable[[CallMeMaybe, int], str], Foo)) # error: [static-assert-error]
|
|
||||||
|
|
||||||
def h(obj: Callable[[int], str], obj2: Foo, obj3: Callable[[str], str]):
|
def h(obj: Callable[[int], str], obj2: Foo, obj3: Callable[[str], str]):
|
||||||
obj2 = obj
|
obj2 = obj
|
||||||
|
|
||||||
# TODO: we should emit [invalid-assignment] here because the signature of `obj3` is not assignable
|
# error: [invalid-assignment] "Object of type `(str, /) -> str` is not assignable to `Foo`"
|
||||||
# to the declared type of `obj2`
|
|
||||||
obj2 = obj3
|
obj2 = obj3
|
||||||
|
|
||||||
def satisfies_foo(x: int) -> str:
|
def satisfies_foo(x: int) -> str:
|
||||||
return "foo"
|
return "foo"
|
||||||
|
|
||||||
static_assert(is_subtype_of(TypeOf[satisfies_foo], Foo))
|
|
||||||
static_assert(is_assignable_to(TypeOf[satisfies_foo], Foo))
|
static_assert(is_assignable_to(TypeOf[satisfies_foo], Foo))
|
||||||
|
|
||||||
|
# TODO: this should pass
|
||||||
|
static_assert(is_subtype_of(TypeOf[satisfies_foo], Foo)) # error: [static-assert-error]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nominal subtyping of protocols
|
## Nominal subtyping of protocols
|
||||||
|
|
|
@ -1658,12 +1658,15 @@ impl<'db> Type<'db> {
|
||||||
| Type::EnumLiteral(_),
|
| Type::EnumLiteral(_),
|
||||||
) => ConstraintSet::from(false),
|
) => ConstraintSet::from(false),
|
||||||
|
|
||||||
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
|
(Type::Callable(self_callable), Type::Callable(other_callable)) => visitor
|
||||||
self_callable.has_relation_to_impl(db, other_callable, relation, visitor)
|
.visit((self, target, relation), || {
|
||||||
}
|
self_callable.has_relation_to_impl(db, other_callable, relation, visitor)
|
||||||
|
}),
|
||||||
|
|
||||||
(_, Type::Callable(_)) => self.into_callable(db).when_some_and(|callable| {
|
(_, Type::Callable(_)) => visitor.visit((self, target, relation), || {
|
||||||
callable.has_relation_to_impl(db, target, relation, visitor)
|
self.into_callable(db).when_some_and(|callable| {
|
||||||
|
callable.has_relation_to_impl(db, target, relation, visitor)
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
(_, Type::ProtocolInstance(protocol)) => {
|
(_, Type::ProtocolInstance(protocol)) => {
|
||||||
|
@ -3265,6 +3268,13 @@ impl<'db> Type<'db> {
|
||||||
policy: InstanceFallbackShadowsNonDataDescriptor,
|
policy: InstanceFallbackShadowsNonDataDescriptor,
|
||||||
member_policy: MemberLookupPolicy,
|
member_policy: MemberLookupPolicy,
|
||||||
) -> PlaceAndQualifiers<'db> {
|
) -> PlaceAndQualifiers<'db> {
|
||||||
|
// TODO: this is a workaround for the fact that looking up the `__call__` attribute on the
|
||||||
|
// meta-type of a `Callable` type currently returns `Unbound`. We should fix this by inferring
|
||||||
|
// a more sophisticated meta-type for `Callable` types; that would allow us to remove this branch.
|
||||||
|
if name == "__call__" && matches!(self, Type::Callable(_) | Type::DataclassTransformer(_)) {
|
||||||
|
return Place::bound(self).into();
|
||||||
|
}
|
||||||
|
|
||||||
let (
|
let (
|
||||||
PlaceAndQualifiers {
|
PlaceAndQualifiers {
|
||||||
place: meta_attr,
|
place: meta_attr,
|
||||||
|
|
|
@ -68,7 +68,7 @@ macro_rules! type_property_test {
|
||||||
|
|
||||||
mod stable {
|
mod stable {
|
||||||
use super::union;
|
use super::union;
|
||||||
use crate::types::{CallableType, Type};
|
use crate::types::{CallableType, KnownClass, Type};
|
||||||
|
|
||||||
// Reflexivity: `T` is equivalent to itself.
|
// Reflexivity: `T` is equivalent to itself.
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
|
@ -205,6 +205,25 @@ mod stable {
|
||||||
all_fully_static_type_pairs_are_subtype_of_their_union, db,
|
all_fully_static_type_pairs_are_subtype_of_their_union, db,
|
||||||
forall fully_static_types s, t. s.is_subtype_of(db, union(db, [s, t])) && t.is_subtype_of(db, union(db, [s, t]))
|
forall fully_static_types s, t. s.is_subtype_of(db, union(db, [s, t])) && t.is_subtype_of(db, union(db, [s, t]))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Any type assignable to `Iterable[object]` should be considered iterable.
|
||||||
|
//
|
||||||
|
// Note that the inverse is not true, due to the fact that we recognize the old-style
|
||||||
|
// iteration protocol as well as the new-style iteration protocol: not all objects that
|
||||||
|
// we consider iterable are assignable to `Iterable[object]`.
|
||||||
|
//
|
||||||
|
// Note also that (like other property tests in this module),
|
||||||
|
// this invariant will only hold true for Liskov-compliant types assignable to `Iterable`.
|
||||||
|
// Since protocols can participate in nominal assignability/subtyping as well as
|
||||||
|
// structural assignability/subtyping, it is possible to construct types that a type
|
||||||
|
// checker must consider to be subtypes of `Iterable` even though they are not in fact
|
||||||
|
// iterable (as long as the user `type: ignore`s any type-checker errors stemming from
|
||||||
|
// the Liskov violation). All you need to do is to create a class that subclasses
|
||||||
|
// `Iterable` but assigns `__iter__ = None` in the class body (or similar).
|
||||||
|
type_property_test!(
|
||||||
|
all_type_assignable_to_iterable_are_iterable, db,
|
||||||
|
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object()])) => t.try_iterate(db).is_ok()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This module contains property tests that currently lead to many false positives.
|
/// This module contains property tests that currently lead to many false positives.
|
||||||
|
@ -217,8 +236,6 @@ mod stable {
|
||||||
mod flaky {
|
mod flaky {
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::types::{KnownClass, Type};
|
|
||||||
|
|
||||||
use super::{intersection, union};
|
use super::{intersection, union};
|
||||||
|
|
||||||
// Negating `T` twice is equivalent to `T`.
|
// Negating `T` twice is equivalent to `T`.
|
||||||
|
@ -313,25 +330,4 @@ mod flaky {
|
||||||
bottom_materialization_of_type_is_assigneble_to_type, db,
|
bottom_materialization_of_type_is_assigneble_to_type, db,
|
||||||
forall types t. t.bottom_materialization(db).is_assignable_to(db, t)
|
forall types t. t.bottom_materialization(db).is_assignable_to(db, t)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Any type assignable to `Iterable[object]` should be considered iterable.
|
|
||||||
//
|
|
||||||
// Note that the inverse is not true, due to the fact that we recognize the old-style
|
|
||||||
// iteration protocol as well as the new-style iteration protocol: not all objects that
|
|
||||||
// we consider iterable are assignable to `Iterable[object]`.
|
|
||||||
//
|
|
||||||
// Note also that (like other property tests in this module),
|
|
||||||
// this invariant will only hold true for Liskov-compliant types assignable to `Iterable`.
|
|
||||||
// Since protocols can participate in nominal assignability/subtyping as well as
|
|
||||||
// structural assignability/subtyping, it is possible to construct types that a type
|
|
||||||
// checker must consider to be subtypes of `Iterable` even though they are not in fact
|
|
||||||
// iterable (as long as the user `type: ignore`s any type-checker errors stemming from
|
|
||||||
// the Liskov violation). All you need to do is to create a class that subclasses
|
|
||||||
// `Iterable` but assigns `__iter__ = None` in the class body (or similar).
|
|
||||||
//
|
|
||||||
// Currently flaky due to <https://github.com/astral-sh/ty/issues/889>
|
|
||||||
type_property_test!(
|
|
||||||
all_type_assignable_to_iterable_are_iterable, db,
|
|
||||||
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object()])) => t.try_iterate(db).is_ok()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,24 @@ use itertools::Itertools;
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use super::TypeVarVariance;
|
|
||||||
use crate::semantic_index::place::ScopedPlaceId;
|
|
||||||
use crate::semantic_index::{SemanticIndex, place_table};
|
|
||||||
use crate::types::ClassType;
|
|
||||||
use crate::types::context::InferContext;
|
|
||||||
use crate::types::diagnostic::report_undeclared_protocol_member;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, FxOrderSet,
|
Db, FxOrderSet,
|
||||||
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
|
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
|
||||||
semantic_index::{definition::Definition, use_def_map},
|
semantic_index::{
|
||||||
|
SemanticIndex, definition::Definition, place::ScopedPlaceId, place_table, use_def_map,
|
||||||
|
},
|
||||||
types::{
|
types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
|
||||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction,
|
ClassType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||||
NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers,
|
InstanceFallbackShadowsNonDataDescriptor, IsDisjointVisitor, KnownFunction,
|
||||||
TypeRelation, VarianceInferable,
|
MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
|
||||||
|
TypeQualifiers, TypeRelation, TypeVarVariance, VarianceInferable,
|
||||||
constraints::{ConstraintSet, IteratorConstraintsExtension},
|
constraints::{ConstraintSet, IteratorConstraintsExtension},
|
||||||
|
context::InferContext,
|
||||||
|
diagnostic::report_undeclared_protocol_member,
|
||||||
signatures::{Parameter, Parameters},
|
signatures::{Parameter, Parameters},
|
||||||
|
todo_type,
|
||||||
|
visitor::any_over_type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -539,12 +540,36 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db>,
|
||||||
) -> ConstraintSet<'db> {
|
) -> ConstraintSet<'db> {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
// TODO: consider the types of the attribute on `other` for method members
|
ProtocolMemberKind::Method(method) => {
|
||||||
ProtocolMemberKind::Method(_) => ConstraintSet::from(matches!(
|
let Place::Type(attribute_type, Boundness::Bound) = other
|
||||||
other.to_meta_type(db).member(db, self.name).place,
|
.invoke_descriptor_protocol(
|
||||||
Place::Type(ty, Boundness::Bound)
|
db,
|
||||||
if ty.is_assignable_to(db, CallableType::single(db, Signature::dynamic(Type::any())))
|
self.name,
|
||||||
)),
|
Place::Unbound.into(),
|
||||||
|
InstanceFallbackShadowsNonDataDescriptor::No,
|
||||||
|
MemberLookupPolicy::default(),
|
||||||
|
)
|
||||||
|
.place
|
||||||
|
else {
|
||||||
|
return ConstraintSet::from(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let proto_member_as_bound_method = method.bind_self(db);
|
||||||
|
|
||||||
|
if any_over_type(db, proto_member_as_bound_method, &|t| {
|
||||||
|
matches!(t, Type::TypeVar(_))
|
||||||
|
}) {
|
||||||
|
// TODO: proper validation for generic methods on protocols
|
||||||
|
return ConstraintSet::from(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_type.has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
proto_member_as_bound_method,
|
||||||
|
relation,
|
||||||
|
visitor,
|
||||||
|
)
|
||||||
|
}
|
||||||
// TODO: consider the types of the attribute on `other` for property members
|
// TODO: consider the types of the attribute on `other` for property members
|
||||||
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
||||||
other.member(db, self.name).place,
|
other.member(db, self.name).place,
|
||||||
|
@ -687,6 +712,13 @@ fn cached_protocol_interface<'db>(
|
||||||
{
|
{
|
||||||
ProtocolMemberKind::Method(callable)
|
ProtocolMemberKind::Method(callable)
|
||||||
}
|
}
|
||||||
|
Type::FunctionLiteral(function)
|
||||||
|
if function.is_staticmethod(db) || function.is_classmethod(db) =>
|
||||||
|
{
|
||||||
|
ProtocolMemberKind::Other(todo_type!(
|
||||||
|
"classmethod and staticmethod protocol members"
|
||||||
|
))
|
||||||
|
}
|
||||||
Type::FunctionLiteral(function) if bound_on_class.is_yes() => {
|
Type::FunctionLiteral(function) if bound_on_class.is_yes() => {
|
||||||
ProtocolMemberKind::Method(function.into_callable_type(db))
|
ProtocolMemberKind::Method(function.into_callable_type(db))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue