mirror of https://github.com/astral-sh/ruff
fix `ProtocolInstanceType::is_equivalent_to` and update test assertions
This commit is contained in:
parent
ca10890fa0
commit
8ab0bd7bd4
|
|
@ -230,7 +230,7 @@ And it is also an error to use `Protocol` in type expressions:
|
|||
def f(
|
||||
x: Protocol, # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
||||
y: type[Protocol], # TODO: should emit `[invalid-type-form]` here too
|
||||
) -> None:
|
||||
):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
# TODO: should be `type[Unknown]`
|
||||
|
|
@ -266,9 +266,7 @@ class Bar(typing_extensions.Protocol):
|
|||
|
||||
static_assert(typing_extensions.is_protocol(Foo))
|
||||
static_assert(typing_extensions.is_protocol(Bar))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(Foo, Bar)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(Foo, Bar))
|
||||
```
|
||||
|
||||
The same goes for `typing.runtime_checkable` and `typing_extensions.runtime_checkable`:
|
||||
|
|
@ -284,9 +282,7 @@ class RuntimeCheckableBar(typing_extensions.Protocol):
|
|||
|
||||
static_assert(typing_extensions.is_protocol(RuntimeCheckableFoo))
|
||||
static_assert(typing_extensions.is_protocol(RuntimeCheckableBar))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(RuntimeCheckableFoo, RuntimeCheckableBar)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(RuntimeCheckableFoo, RuntimeCheckableBar))
|
||||
|
||||
# These should not error because the protocols are decorated with `@runtime_checkable`
|
||||
isinstance(object(), RuntimeCheckableFoo)
|
||||
|
|
@ -488,21 +484,20 @@ class HasX(Protocol):
|
|||
class Foo:
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(Foo, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(Foo, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(Foo, HasX))
|
||||
static_assert(is_assignable_to(Foo, HasX))
|
||||
|
||||
class FooSub(Foo): ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(FooSub, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(FooSub, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(FooSub, HasX))
|
||||
static_assert(is_assignable_to(FooSub, HasX))
|
||||
|
||||
class Bar:
|
||||
x: str
|
||||
|
||||
static_assert(not is_subtype_of(Bar, HasX))
|
||||
static_assert(not is_assignable_to(Bar, HasX))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(Bar, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(Bar, HasX)) # error: [static-assert-error]
|
||||
|
||||
class Baz:
|
||||
y: int
|
||||
|
|
@ -524,14 +519,16 @@ class A:
|
|||
def x(self) -> int:
|
||||
return 42
|
||||
|
||||
static_assert(not is_subtype_of(A, HasX))
|
||||
static_assert(not is_assignable_to(A, HasX))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(A, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(A, HasX)) # error: [static-assert-error]
|
||||
|
||||
class B:
|
||||
x: Final = 42
|
||||
|
||||
static_assert(not is_subtype_of(A, HasX))
|
||||
static_assert(not is_assignable_to(A, HasX))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(A, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(A, HasX)) # error: [static-assert-error]
|
||||
|
||||
class IntSub(int): ...
|
||||
|
||||
|
|
@ -541,8 +538,10 @@ class C:
|
|||
# due to invariance, a type is only a subtype of `HasX`
|
||||
# if its `x` attribute is of type *exactly* `int`:
|
||||
# a subclass of `int` does not satisfy the interface
|
||||
static_assert(not is_subtype_of(C, HasX))
|
||||
static_assert(not is_assignable_to(C, HasX))
|
||||
#
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(C, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(C, HasX)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
All attributes on frozen dataclasses and namedtuples are immutable, so instances of these classes
|
||||
|
|
@ -556,22 +555,23 @@ from typing import NamedTuple
|
|||
class MutableDataclass:
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(MutableDataclass, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(MutableDataclass, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(MutableDataclass, HasX))
|
||||
static_assert(is_assignable_to(MutableDataclass, HasX))
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ImmutableDataclass:
|
||||
x: int
|
||||
|
||||
static_assert(not is_subtype_of(ImmutableDataclass, HasX))
|
||||
static_assert(not is_assignable_to(ImmutableDataclass, HasX))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(ImmutableDataclass, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(ImmutableDataclass, HasX)) # error: [static-assert-error]
|
||||
|
||||
class NamedTupleWithX(NamedTuple):
|
||||
x: int
|
||||
|
||||
static_assert(not is_subtype_of(NamedTupleWithX, HasX))
|
||||
static_assert(not is_assignable_to(NamedTupleWithX, HasX))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(NamedTupleWithX, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(NamedTupleWithX, HasX)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
However, a type with a read-write property `x` *does* satisfy the `HasX` protocol. The `HasX`
|
||||
|
|
@ -590,9 +590,8 @@ class XProperty:
|
|||
def x(self, x: int) -> None:
|
||||
self._x = x**2
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XProperty, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XProperty, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XProperty, HasX))
|
||||
static_assert(is_assignable_to(XProperty, HasX))
|
||||
```
|
||||
|
||||
Attribute members on protocol classes are allowed to have default values, just like instance
|
||||
|
|
@ -717,9 +716,8 @@ from typing import Protocol
|
|||
|
||||
class UniversalSet(Protocol): ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_assignable_to(object, UniversalSet)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(object, UniversalSet)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(object, UniversalSet))
|
||||
static_assert(is_subtype_of(object, UniversalSet))
|
||||
```
|
||||
|
||||
Which means that `UniversalSet` here is in fact an equivalent type to `object`:
|
||||
|
|
@ -727,8 +725,7 @@ Which means that `UniversalSet` here is in fact an equivalent type to `object`:
|
|||
```py
|
||||
from knot_extensions import is_equivalent_to
|
||||
|
||||
# TODO: this should pass
|
||||
static_assert(is_equivalent_to(UniversalSet, object)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(UniversalSet, object))
|
||||
```
|
||||
|
||||
`object` is a subtype of certain other protocols too. Since all fully static types (whether nominal
|
||||
|
|
@ -739,17 +736,16 @@ means that these protocols are also equivalent to `UniversalSet` and `object`:
|
|||
class SupportsStr(Protocol):
|
||||
def __str__(self) -> str: ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_equivalent_to(SupportsStr, UniversalSet)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(SupportsStr, object)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(SupportsStr, UniversalSet))
|
||||
static_assert(is_equivalent_to(SupportsStr, object))
|
||||
|
||||
class SupportsClass(Protocol):
|
||||
__class__: type
|
||||
@property
|
||||
def __class__(self) -> type: ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_equivalent_to(SupportsClass, UniversalSet)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(SupportsClass, SupportsStr)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(SupportsClass, object)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(SupportsClass, UniversalSet))
|
||||
static_assert(is_equivalent_to(SupportsClass, SupportsStr))
|
||||
static_assert(is_equivalent_to(SupportsClass, object))
|
||||
```
|
||||
|
||||
If a protocol contains members that are not defined on `object`, then that protocol will (like all
|
||||
|
|
@ -786,8 +782,7 @@ class HasX(Protocol):
|
|||
class AlsoHasX(Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: this should pass
|
||||
static_assert(is_equivalent_to(HasX, AlsoHasX)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(HasX, AlsoHasX))
|
||||
```
|
||||
|
||||
And unions containing equivalent protocols are recognised as equivalent, even when the order is not
|
||||
|
|
@ -803,8 +798,7 @@ class AlsoHasY(Protocol):
|
|||
class A: ...
|
||||
class B: ...
|
||||
|
||||
# TODO: this should pass
|
||||
static_assert(is_equivalent_to(A | HasX | B | HasY, B | AlsoHasY | AlsoHasX | A)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(A | HasX | B | HasY, B | AlsoHasY | AlsoHasX | A))
|
||||
```
|
||||
|
||||
## Intersections of protocols
|
||||
|
|
@ -882,9 +876,9 @@ from knot_extensions import is_subtype_of, is_assignable_to, static_assert, Type
|
|||
class HasX(Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
# TODO: this should pass
|
||||
static_assert(is_subtype_of(TypeOf[module], HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(TypeOf[module], HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(TypeOf[module], HasX))
|
||||
|
||||
class ExplicitProtocolSubtype(HasX, Protocol):
|
||||
y: int
|
||||
|
|
@ -896,9 +890,8 @@ class ImplicitProtocolSubtype(Protocol):
|
|||
x: int
|
||||
y: str
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(ImplicitProtocolSubtype, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(ImplicitProtocolSubtype, HasX)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(ImplicitProtocolSubtype, HasX))
|
||||
static_assert(is_assignable_to(ImplicitProtocolSubtype, HasX))
|
||||
|
||||
class Meta(type):
|
||||
x: int
|
||||
|
|
@ -933,23 +926,24 @@ def f(obj: ClassVarXProto):
|
|||
class InstanceAttrX:
|
||||
x: int
|
||||
|
||||
static_assert(not is_assignable_to(InstanceAttrX, ClassVarXProto))
|
||||
static_assert(not is_subtype_of(InstanceAttrX, ClassVarXProto))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(InstanceAttrX, ClassVarXProto)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(InstanceAttrX, ClassVarXProto)) # error: [static-assert-error]
|
||||
|
||||
class PropertyX:
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return 42
|
||||
|
||||
static_assert(not is_assignable_to(PropertyX, ClassVarXProto))
|
||||
static_assert(not is_subtype_of(PropertyX, ClassVarXProto))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(PropertyX, ClassVarXProto)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(PropertyX, ClassVarXProto)) # error: [static-assert-error]
|
||||
|
||||
class ClassVarX:
|
||||
x: ClassVar[int] = 42
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_assignable_to(ClassVarX, ClassVarXProto)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(ClassVarX, ClassVarXProto)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(ClassVarX, ClassVarXProto))
|
||||
static_assert(is_subtype_of(ClassVarX, ClassVarXProto))
|
||||
```
|
||||
|
||||
This is mentioned by the
|
||||
|
|
@ -976,18 +970,16 @@ class HasXProperty(Protocol):
|
|||
class XAttr:
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XAttr, HasXProperty))
|
||||
static_assert(is_assignable_to(XAttr, HasXProperty))
|
||||
|
||||
class XReadProperty:
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return 42
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XReadProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XReadProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XReadProperty, HasXProperty))
|
||||
static_assert(is_assignable_to(XReadProperty, HasXProperty))
|
||||
|
||||
class XReadWriteProperty:
|
||||
@property
|
||||
|
|
@ -997,22 +989,20 @@ class XReadWriteProperty:
|
|||
@x.setter
|
||||
def x(self, val: int) -> None: ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XReadWriteProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XReadWriteProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XReadWriteProperty, HasXProperty))
|
||||
static_assert(is_assignable_to(XReadWriteProperty, HasXProperty))
|
||||
|
||||
class XClassVar:
|
||||
x: ClassVar[int] = 42
|
||||
|
||||
static_assert(is_subtype_of(XClassVar, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XClassVar, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XClassVar, HasXProperty))
|
||||
static_assert(is_assignable_to(XClassVar, HasXProperty))
|
||||
|
||||
class XFinal:
|
||||
x: Final = 42
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XFinal, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XFinal, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XFinal, HasXProperty))
|
||||
static_assert(is_assignable_to(XFinal, HasXProperty))
|
||||
```
|
||||
|
||||
A read-only property on a protocol, unlike a mutable attribute, is covariant: `XSub` in the below
|
||||
|
|
@ -1025,9 +1015,8 @@ class MyInt(int): ...
|
|||
class XSub:
|
||||
x: MyInt
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XSub, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XSub, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XSub, HasXProperty))
|
||||
static_assert(is_assignable_to(XSub, HasXProperty))
|
||||
```
|
||||
|
||||
A read/write property on a protocol, where the getter returns the same type that the setter takes,
|
||||
|
|
@ -1043,17 +1032,17 @@ class HasMutableXProperty(Protocol):
|
|||
class XAttr:
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XAttr, HasXProperty))
|
||||
static_assert(is_assignable_to(XAttr, HasXProperty))
|
||||
|
||||
class XReadProperty:
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return 42
|
||||
|
||||
static_assert(not is_subtype_of(XReadProperty, HasXProperty))
|
||||
static_assert(not is_assignable_to(XReadProperty, HasXProperty))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(XReadProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(XReadProperty, HasXProperty)) # error: [static-assert-error]
|
||||
|
||||
class XReadWriteProperty:
|
||||
@property
|
||||
|
|
@ -1063,15 +1052,15 @@ class XReadWriteProperty:
|
|||
@x.setter
|
||||
def x(self, val: int) -> None: ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XReadWriteProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XReadWriteProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XReadWriteProperty, HasXProperty))
|
||||
static_assert(is_assignable_to(XReadWriteProperty, HasXProperty))
|
||||
|
||||
class XSub:
|
||||
x: MyInt
|
||||
|
||||
static_assert(not is_subtype_of(XSub, HasXProperty))
|
||||
static_assert(not is_assignable_to(XSub, HasXProperty))
|
||||
# TODO: should pass
|
||||
static_assert(not is_subtype_of(XSub, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(XSub, HasXProperty)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
A protocol with a read/write property `x` is exactly equivalent to a protocol with a mutable
|
||||
|
|
@ -1083,16 +1072,13 @@ from knot_extensions import is_equivalent_to
|
|||
class HasMutableXAttr(Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: this should pass
|
||||
static_assert(is_equivalent_to(HasMutableXAttr, HasMutableXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(HasMutableXAttr, HasMutableXProperty))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(HasMutableXAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(HasMutableXAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(HasMutableXAttr, HasXProperty))
|
||||
static_assert(is_assignable_to(HasMutableXAttr, HasXProperty))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(HasMutableXProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(HasMutableXProperty, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(HasMutableXProperty, HasXProperty))
|
||||
static_assert(is_assignable_to(HasMutableXProperty, HasXProperty))
|
||||
```
|
||||
|
||||
A read/write property on a protocol, where the setter accepts a subtype of the type returned by the
|
||||
|
|
@ -1119,9 +1105,8 @@ class HasAsymmetricXProperty(Protocol):
|
|||
class XAttr:
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XAttr, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XAttr, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XAttr, HasAsymmetricXProperty))
|
||||
static_assert(is_assignable_to(XAttr, HasAsymmetricXProperty))
|
||||
```
|
||||
|
||||
The end conclusion of this is that the getter-returned type of a property is always covariant and
|
||||
|
|
@ -1132,9 +1117,8 @@ regular mutable attribute, where the implied getter-returned and setter-accepted
|
|||
class XAttrSub:
|
||||
x: MyInt
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XAttrSub, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XAttrSub, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XAttrSub, HasAsymmetricXProperty))
|
||||
static_assert(is_assignable_to(XAttrSub, HasAsymmetricXProperty))
|
||||
|
||||
class MyIntSub(MyInt):
|
||||
pass
|
||||
|
|
@ -1142,8 +1126,9 @@ class MyIntSub(MyInt):
|
|||
class XAttrSubSub:
|
||||
x: MyIntSub
|
||||
|
||||
static_assert(not is_subtype_of(XAttrSubSub, HasAsymmetricXProperty))
|
||||
static_assert(not is_assignable_to(XAttrSubSub, HasAsymmetricXProperty))
|
||||
# TODO: should pass
|
||||
static_assert(not is_subtype_of(XAttrSubSub, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(XAttrSubSub, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
An asymmetric property on a protocol can also be satisfied by an asymmetric property on a nominal
|
||||
|
|
@ -1159,9 +1144,8 @@ class XAsymmetricProperty:
|
|||
@x.setter
|
||||
def x(self, x: int) -> None: ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XAsymmetricProperty, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XAsymmetricProperty, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XAsymmetricProperty, HasAsymmetricXProperty))
|
||||
static_assert(is_assignable_to(XAsymmetricProperty, HasAsymmetricXProperty))
|
||||
```
|
||||
|
||||
A custom descriptor attribute on the nominal class will also suffice:
|
||||
|
|
@ -1176,9 +1160,8 @@ class Descriptor:
|
|||
class XCustomDescriptor:
|
||||
x: Descriptor = Descriptor()
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(XCustomDescriptor, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(XCustomDescriptor, HasAsymmetricXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(XCustomDescriptor, HasAsymmetricXProperty))
|
||||
static_assert(is_assignable_to(XCustomDescriptor, HasAsymmetricXProperty))
|
||||
```
|
||||
|
||||
Moreover, a read-only property on a protocol can be satisfied by a nominal class that defines a
|
||||
|
|
@ -1191,19 +1174,20 @@ class HasGetAttr:
|
|||
def __getattr__(self, attr: str) -> int:
|
||||
return 42
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(HasGetAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(HasGetAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(HasGetAttr, HasXProperty))
|
||||
static_assert(is_assignable_to(HasGetAttr, HasXProperty))
|
||||
|
||||
static_assert(not is_subtype_of(HasGetAttr, HasMutableXAttr))
|
||||
static_assert(not is_subtype_of(HasGetAttr, HasMutableXAttr))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(HasGetAttr, HasMutableXAttr)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(HasGetAttr, HasMutableXAttr)) # error: [static-assert-error]
|
||||
|
||||
class HasGetAttrWithUnsuitableReturn:
|
||||
def __getattr__(self, attr: str) -> tuple[int, int]:
|
||||
return (1, 2)
|
||||
|
||||
static_assert(not is_subtype_of(HasGetAttrWithUnsuitableReturn, HasXProperty))
|
||||
static_assert(not is_assignable_to(HasGetAttrWithUnsuitableReturn, HasXProperty))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(HasGetAttrWithUnsuitableReturn, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(HasGetAttrWithUnsuitableReturn, HasXProperty)) # error: [static-assert-error]
|
||||
|
||||
class HasGetAttrAndSetAttr:
|
||||
def __getattr__(self, attr: str) -> MyInt:
|
||||
|
|
@ -1211,9 +1195,10 @@ class HasGetAttrAndSetAttr:
|
|||
|
||||
def __setattr__(self, attr: str, value: int) -> None: ...
|
||||
|
||||
static_assert(is_subtype_of(HasGetAttrAndSetAttr, HasXProperty))
|
||||
static_assert(is_assignable_to(HasGetAttrAndSetAttr, HasXProperty))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(HasGetAttrAndSetAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(HasGetAttrAndSetAttr, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
||||
```
|
||||
|
|
@ -1314,9 +1299,12 @@ class FalsyFooSubclass(FalsyFoo, Protocol):
|
|||
y: str
|
||||
|
||||
def g(a: Truthy, b: FalsyFoo, c: FalsyFooSubclass):
|
||||
reveal_type(bool(a)) # revealed: Literal[True]
|
||||
reveal_type(bool(b)) # revealed: Literal[False]
|
||||
reveal_type(bool(c)) # revealed: Literal[False]
|
||||
# TODO should be `Literal[True]
|
||||
reveal_type(bool(a)) # revealed: bool
|
||||
# TODO should be `Literal[False]
|
||||
reveal_type(bool(b)) # revealed: bool
|
||||
# TODO should be `Literal[False]
|
||||
reveal_type(bool(c)) # revealed: bool
|
||||
```
|
||||
|
||||
It is not sufficient for a protocol to have a callable `__bool__` instance member that returns
|
||||
|
|
@ -1363,12 +1351,12 @@ from knot_extensions import is_subtype_of, is_assignable_to
|
|||
class NominalWithX:
|
||||
x: int = 42
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_assignable_to(NominalWithX, FullyStatic)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(NominalWithX, NotFullyStatic)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalWithX, FullyStatic)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(NominalWithX, FullyStatic))
|
||||
static_assert(is_assignable_to(NominalWithX, NotFullyStatic))
|
||||
static_assert(is_subtype_of(NominalWithX, FullyStatic))
|
||||
|
||||
static_assert(not is_subtype_of(NominalWithX, NotFullyStatic))
|
||||
# TODO: this should pass
|
||||
static_assert(not is_subtype_of(NominalWithX, NotFullyStatic)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
Empty protocols are fully static; this follows from the fact that an empty protocol is equivalent to
|
||||
|
|
|
|||
|
|
@ -138,12 +138,8 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
}
|
||||
|
||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let members = self.protocol_members(db);
|
||||
let object = KnownClass::Object.to_instance(db);
|
||||
if members
|
||||
.iter()
|
||||
.all(|member| !object.member(db, member).symbol.is_unbound())
|
||||
{
|
||||
if object.satisfies_protocol(db, self) {
|
||||
return object;
|
||||
}
|
||||
match self.0 {
|
||||
|
|
@ -180,7 +176,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.protocol_members(db).set_eq(other.protocol_members(db))
|
||||
self.normalized(db) == other.normalized(db)
|
||||
}
|
||||
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
|
|
|
|||
Loading…
Reference in New Issue