diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 2ae5e0635a91d..8808451739f6d 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -4247,6 +4247,13 @@ impl<'db> Type<'db> { Some(KnownClass::Tuple) => { let object = Type::object(db); + // ```py + // class tuple: + // @overload + // def __new__(cls) -> tuple[()]: ... + // @overload + // def __new__(cls, iterable: Iterable[object]) -> tuple[object, ...]: ... + // ``` CallableBinding::from_overloads( self, [ @@ -4308,6 +4315,13 @@ impl<'db> Type<'db> { let instantiated = Type::instance(db, ClassType::from(alias)); let parameters = if alias.origin(db).is_known(db, KnownClass::Tuple) { + // ```py + // class tuple: + // @overload + // def __new__(cls: type[tuple[()]], iterable: tuple[()] = ()) -> tuple[()]: ... + // @overload + // def __new__[T](cls: type[tuple[T, ...]], iterable: tuple[T, ...]) -> tuple[T, ...]: ... + // ``` let spec = alias.specialization(db).tuple(db); let mut parameter = Parameter::positional_only(Some(Name::new_static("iterable"))) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 6d0810bab6d33..412502e2f9c30 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -973,13 +973,16 @@ impl<'db> Bindings<'db> { } Some(KnownClass::Tuple) if overload_index == 1 => { + // `tuple(range(42))` => `tuple[int, ...]` + // BUT `tuple((1, 2))` => `tuple[Literal[1], Literal[2]]` rather than `tuple[Literal[1, 2], ...]` if let [Some(argument)] = overload.parameter_types() { let overridden_return = argument.into_tuple().map(Type::Tuple).unwrap_or_else(|| { // Some awkward special handling is required here because of the fact // that calling `try_iterate()` on `Never` returns `Never`, // but `tuple[Never, ...]` eagerly simplifies to `tuple[()]`, - // which will cause us to emit false positives if we index into the tuple + // which will cause us to emit false positives if we index into the tuple. + // Using `tuple[Unknown, ...]` avoids these false positives. let specialization = if argument.is_never() { Type::unknown() } else { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 2f69f742af4fc..936a7fffa599d 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2427,7 +2427,7 @@ impl KnownClass { | Self::Float | Self::Enum | Self::ABCMeta - | KnownClass::Iterable + | Self::Iterable // Empty tuples are AlwaysFalse; non-empty tuples are AlwaysTrue | Self::NamedTuple // Evaluating `NotImplementedType` in a boolean context was deprecated in Python 3.9