From 30383d4855f6f8204bccd254ad160f5e22e3fb70 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 19 Feb 2025 20:21:04 +0100 Subject: [PATCH] Patch is_assignable_to to add partial support for SupportsIndex --- .../resources/mdtest/protocols.md | 15 +++ crates/red_knot_python_semantic/src/types.rs | 35 ++++- crates/ruff_benchmark/benches/red_knot.rs | 126 ------------------ 3 files changed, 46 insertions(+), 130 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/protocols.md diff --git a/crates/red_knot_python_semantic/resources/mdtest/protocols.md b/crates/red_knot_python_semantic/resources/mdtest/protocols.md new file mode 100644 index 0000000000..fe14d0024f --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/protocols.md @@ -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 +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 1173d75d0a..fe4a9cb330 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -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) } } diff --git a/crates/ruff_benchmark/benches/red_knot.rs b/crates/ruff_benchmark/benches/red_knot.rs index b1c82c6c75..11ec12577c 100644 --- a/crates/ruff_benchmark/benches/red_knot.rs +++ b/crates/ruff_benchmark/benches/red_knot.rs @@ -67,104 +67,6 @@ static EXPECTED_DIAGNOSTICS: &[KeyDiagnosticFields] = &[ Cow::Borrowed("Module `collections.abc` has no member `Iterable`"), Severity::Error, ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(7642..7645), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(9126..9129), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(9962..9965), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(13339..13342), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(13789..13792), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(14052..14055), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(17164..17167), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(17710..17713), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(17789..17792), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(18535..18538), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(19311..19314), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(19507..19510), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(19689..19692), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(19783..19786), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), // We don't handle intersections in `is_assignable_to` yet ( DiagnosticId::lint("invalid-argument-type"), @@ -187,34 +89,6 @@ static EXPECTED_DIAGNOSTICS: &[KeyDiagnosticFields] = &[ Cow::Borrowed("Object of type `Unknown & ~AlwaysFalsy | @Todo & ~AlwaysFalsy` cannot be assigned to parameter 1 (`match`) of function `match_to_number`; expected type `Match`"), Severity::Error, ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(21451..21452), - Cow::Borrowed("Object of type `Literal[0]` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(21454..21457), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 4 (`end`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(21572..21573), - Cow::Borrowed("Object of type `Literal[0]` cannot be assigned to parameter 3 (`start`); expected type `SupportsIndex | None`"), - Severity::Error, - ), - ( - DiagnosticId::lint("invalid-argument-type"), - Some("/src/tomllib/_parser.py"), - Some(21575..21578), - Cow::Borrowed("Object of type `Unknown | int` cannot be assigned to parameter 4 (`end`); expected type `SupportsIndex | None`"), - Severity::Error, - ), ( DiagnosticId::lint("unused-ignore-comment"), Some("/src/tomllib/_parser.py"),