diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index bfc76cbdbd..cdbabc9d80 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -183,7 +183,7 @@ static_assert(is_assignable_to(Meta, type[Unknown])) ## Tuple types ```py -from knot_extensions import static_assert, is_assignable_to +from knot_extensions import static_assert, is_assignable_to, AlwaysTruthy, AlwaysFalsy from typing import Literal, Any static_assert(is_assignable_to(tuple[()], tuple[()])) @@ -198,6 +198,14 @@ static_assert(is_assignable_to(tuple[()], tuple)) static_assert(is_assignable_to(tuple[int, str], tuple)) static_assert(is_assignable_to(tuple[Any], tuple)) +# TODO: It is not yet clear if we want the following two assertions to hold. +# See https://github.com/astral-sh/ruff/issues/15528 for more details. The +# short version is: We either need to special-case enforcement of the Liskov +# substitution principle on `__bool__` and `__len__` for tuple subclasses, +# or we need to negate these assertions. +static_assert(is_assignable_to(tuple[()], AlwaysFalsy)) +static_assert(is_assignable_to(tuple[int], AlwaysTruthy)) + static_assert(not is_assignable_to(tuple[()], tuple[int])) static_assert(not is_assignable_to(tuple[int], tuple[str])) static_assert(not is_assignable_to(tuple[int], tuple[int, str])) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4f89feb577..4a98680434 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1071,9 +1071,13 @@ impl<'db> Type<'db> { // This special case is required because the left-hand side tuple might be a // gradual type, so we can not rely on subtyping. This allows us to assign e.g. // `tuple[Any, int]` to `tuple`. - (Type::Tuple(_), _) => KnownClass::Tuple - .to_instance(db) - .is_assignable_to(db, target), + (Type::Tuple(_), _) + if KnownClass::Tuple + .to_instance(db) + .is_assignable_to(db, target) => + { + true + } // `type[Any]` is assignable to any `type[...]` type, because `type[Any]` can // materialize to any `type[...]` type.