Patch is_assignable_to to add partial support for SupportsIndex

This commit is contained in:
David Peter
2025-02-19 20:21:04 +01:00
parent c7d97c3cd5
commit 30383d4855
3 changed files with 46 additions and 130 deletions

View File

@@ -0,0 +1,15 @@
# Protocols
We do not support protocols yet, but to avoid false positives, we *partially* support some known
protocols.
## `typing.SupportsIndex`
```py
from typing import SupportsIndex, Literal
def _(some_int: int, some_literal_int: Literal[1], some_indexable: SupportsIndex):
a: SupportsIndex = some_int
b: SupportsIndex = some_literal_int
c: SupportsIndex = some_indexable
```

View File

@@ -812,6 +812,25 @@ impl<'db> Type<'db> {
true
}
// TODO: This is a workaround to avoid false positives (e.g. when checking function calls
// with `SupportsIndex` parameters), which should be removed when we understand protocols.
(lhs, Type::Instance(InstanceType { class }))
if class.is_known(db, KnownClass::SupportsIndex) =>
{
match lhs {
Type::Instance(InstanceType { class })
if matches!(
class.known(db),
Some(KnownClass::Int | KnownClass::SupportsIndex)
) =>
{
true
}
Type::IntLiteral(_) => true,
_ => false,
}
}
// TODO other types containing gradual forms (e.g. generics containing Any/Unknown)
_ => self.is_subtype_of(db, target),
}
@@ -1370,6 +1389,7 @@ impl<'db> Type<'db> {
| KnownClass::DefaultDict
| KnownClass::Deque
| KnownClass::OrderedDict
| KnownClass::SupportsIndex
| KnownClass::StdlibAlias
| KnownClass::TypeVar,
) => false,
@@ -2602,6 +2622,8 @@ pub enum KnownClass {
TypeVar,
TypeAliasType,
NoDefaultType,
// TODO: This can probably be removed when we have support for protocols
SupportsIndex,
// Collections
ChainMap,
Counter,
@@ -2652,6 +2674,7 @@ impl<'db> KnownClass {
Self::TypeVar => "TypeVar",
Self::TypeAliasType => "TypeAliasType",
Self::NoDefaultType => "_NoDefaultType",
Self::SupportsIndex => "SupportsIndex",
Self::ChainMap => "ChainMap",
Self::Counter => "Counter",
Self::DefaultDict => "defaultdict",
@@ -2733,9 +2756,11 @@ impl<'db> KnownClass {
| Self::MethodWrapperType
| Self::WrapperDescriptorType => KnownModule::Types,
Self::NoneType => KnownModule::Typeshed,
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::StdlibAlias => {
KnownModule::Typing
}
Self::SpecialForm
| Self::TypeVar
| Self::TypeAliasType
| Self::StdlibAlias
| Self::SupportsIndex => KnownModule::Typing,
Self::NoDefaultType => {
let python_version = Program::get(db).python_version(db);
@@ -2807,6 +2832,7 @@ impl<'db> KnownClass {
| Self::Deque
| Self::OrderedDict
| Self::StdlibAlias
| Self::SupportsIndex
| Self::BaseException
| Self::BaseExceptionGroup
| Self::TypeVar => false,
@@ -2852,6 +2878,7 @@ impl<'db> KnownClass {
"_Alias" => Self::StdlibAlias,
"_SpecialForm" => Self::SpecialForm,
"_NoDefaultType" => Self::NoDefaultType,
"SupportsIndex" => Self::SupportsIndex,
"_version_info" => Self::VersionInfo,
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
Self::EllipsisType
@@ -2904,7 +2931,7 @@ impl<'db> KnownClass {
| Self::MethodWrapperType
| Self::WrapperDescriptorType => module == self.canonical_module(db),
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType | Self::SupportsIndex => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
}