diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/callable.md b/crates/ty_python_semantic/resources/mdtest/narrow/callable.md index 9762dc6e6e..07f225b051 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/callable.md @@ -45,8 +45,6 @@ def h(x: Callable[..., int] | None): ## Narrowing from object ```py -from typing import Callable - def f(x: object): if callable(x): reveal_type(x) # revealed: Top[(...) -> object] diff --git a/crates/ty_python_semantic/resources/mdtest/type_display/callable.md b/crates/ty_python_semantic/resources/mdtest/type_display/callable.md new file mode 100644 index 0000000000..907912cfb0 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/type_display/callable.md @@ -0,0 +1,38 @@ +# Display of callable types + +We parenthesize callable types when they appear inside more complex types, to disambiguate: + +```py +from typing import Callable + +def f(x: Callable[[], str] | Callable[[int], str]): + reveal_type(x) # revealed: (() -> str) | ((int, /) -> str) +``` + +We don't parenthesize display of an overloaded callable, since it is already wrapped in +`Overload[...]`: + +```py +from typing import overload +from ty_extensions import CallableTypeOf + +@overload +def f(x: int) -> bool: ... +@overload +def f(x: str) -> str: ... +def f(x: int | str) -> bool | str: + return bool(x) if isinstance(x, int) else str(x) + +def _(flag: bool, c: CallableTypeOf[f]): + x = c if flag else True + reveal_type(x) # revealed: Overload[(x: int) -> bool, (x: str) -> str] | Literal[True] +``` + +And we don't parenthesize the top callable, since it is wrapped in `Top[...]`: + +```py +from ty_extensions import Top + +def f(x: Top[Callable[..., str]] | Callable[[int], int]): + reveal_type(x) # revealed: Top[(...) -> str] | ((int, /) -> int) +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md index a87fda36f2..056c8ff900 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md @@ -43,7 +43,7 @@ def _(top_callable: Top[Callable[[Any], None]]): reveal_type(top_callable) # revealed: (Never, /) -> None ``` -The invariant position is replaced with an unresolved type variable. +The invariant position cannot simplify, and is represented with the `Top` special form. ```py def _(top_list: Top[list[Any]]): @@ -70,8 +70,13 @@ def _(bottom_callable: Bottom[Callable[[Any, Unknown], None]]): reveal_type(bottom_callable) # revealed: (object, object, /) -> None ``` -The invariant position is replaced in the same way as the top materialization, with an unresolved -type variable. +The invariant position is represented with the `Bottom` special form. + +There is an argument that `Bottom[list[Any]]` should simplify to `Never`, since it is the infinite +intersection of all possible materializations of `list[Any]`, and (due to invariance) these +materializations are disjoint types. But currently we do not make this simplification: there doesn't +seem to be any compelling need for it, and allowing more gradual types to materialize to `Never` has +undesirable implications for mutual assignability of seemingly-unrelated gradual types. ```py def _(bottom_list: Bottom[list[Any]]): @@ -182,8 +187,8 @@ def _(top: Top[C3], bottom: Bottom[C3]) -> None: For callables with gradual parameters (the `...` form), the top materialization preserves the gradual form since we cannot know what parameters are required. The bottom materialization -simplifies to the bottom callable `(*args: object, **kwargs: object) -> Never` since this is the -most specific type that is a subtype of all possible callable materializations. +simplifies to the bottom parameters `(*args: object, **kwargs: object)` since this is the most +specific type that is a subtype of all possible parameter materializations. ```toml [environment] @@ -220,6 +225,58 @@ static_assert(is_equivalent_to(Bottom[Callable[..., Never]], EquivalentToBottom) static_assert(not is_equivalent_to(Top[Callable[..., object]], Callable[..., object])) ``` +Gradual parameters can be top- and bottom-materialized even if the return type is not `Any`: + +```py +type GradualParams = Callable[..., int] + +def _(top: Top[GradualParams], bottom: Bottom[GradualParams]) -> None: + reveal_type(top) # revealed: Top[(...) -> int] + + reveal_type(bottom) # revealed: (*args: object, **kwargs: object) -> int +``` + +Materializing an overloaded callable materializes each overload separately. + +```py +from typing import overload +from ty_extensions import CallableTypeOf + +@overload +def f(x: int) -> Any: ... +@overload +def f(*args: Any, **kwargs: Any) -> str: ... +def f(*args: object, **kwargs: object) -> object: + pass + +def _(top: Top[CallableTypeOf[f]], bottom: Bottom[CallableTypeOf[f]]): + reveal_type(top) # revealed: Overload[(x: int) -> object, Top[(...) -> str]] + reveal_type(bottom) # revealed: Overload[(x: int) -> Never, (*args: object, **kwargs: object) -> str] +``` + +The top callable can be represented in a `ParamSpec`: + +```py +def takes_paramspec[**P](f: Callable[P, None]) -> Callable[P, None]: + return f + +def _(top: Top[Callable[..., None]]): + revealed = takes_paramspec(top) + reveal_type(revealed) # revealed: Top[(...) -> None] +``` + +The top callable is not a subtype of `(*object, **object) -> object`: + +```py +type TopCallable = Top[Callable[..., Any]] + +@staticmethod +def takes_objects(*args: object, **kwargs: object) -> object: + pass + +static_assert(not is_subtype_of(TopCallable, CallableTypeOf[takes_objects])) +``` + ## Tuple All positions in a tuple are covariant. diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 391822f4cf..c37875af30 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1903,7 +1903,6 @@ impl<'db> Type<'db> { db, CallableSignature::from_overloads(method.signatures(db)), CallableTypeKind::Regular, - false, ))), Type::WrapperDescriptor(wrapper_descriptor) => { @@ -1911,7 +1910,6 @@ impl<'db> Type<'db> { db, CallableSignature::from_overloads(wrapper_descriptor.signatures(db)), CallableTypeKind::Regular, - false, ))) } @@ -12331,7 +12329,6 @@ impl<'db> BoundMethodType<'db> { .map(|signature| signature.bind_self(db, Some(self_instance))), ), CallableTypeKind::FunctionLike, - false, ) } @@ -12451,12 +12448,6 @@ pub struct CallableType<'db> { pub(crate) signatures: CallableSignature<'db>, kind: CallableTypeKind, - - /// Whether this callable is a top materialization (e.g., `Top[Callable[..., object]]`). - /// - /// Bottom materializations of gradual callables are simplified to the bottom callable - /// `(*args: object, **kwargs: object) -> Never`, so this is always false for them. - is_top_materialization: bool, } pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( @@ -12501,7 +12492,6 @@ impl<'db> CallableType<'db> { db, CallableSignature::single(signature), CallableTypeKind::Regular, - false, ) } @@ -12510,7 +12500,6 @@ impl<'db> CallableType<'db> { db, CallableSignature::single(signature), CallableTypeKind::FunctionLike, - false, ) } @@ -12522,7 +12511,6 @@ impl<'db> CallableType<'db> { db, CallableSignature::single(Signature::new(parameters, None)), CallableTypeKind::ParamSpecValue, - false, ) } @@ -12552,7 +12540,6 @@ impl<'db> CallableType<'db> { db, self.signatures(db).bind_self(db, self_type), self.kind(db), - self.is_top_materialization(db), ) } @@ -12561,7 +12548,6 @@ impl<'db> CallableType<'db> { db, self.signatures(db).apply_self(db, self_type), self.kind(db), - self.is_top_materialization(db), ) } @@ -12570,12 +12556,7 @@ impl<'db> CallableType<'db> { /// Specifically, this represents a callable type with a single signature: /// `(*args: object, **kwargs: object) -> Never`. pub(crate) fn bottom(db: &'db dyn Db) -> CallableType<'db> { - Self::new( - db, - CallableSignature::bottom(), - CallableTypeKind::Regular, - false, - ) + Self::new(db, CallableSignature::bottom(), CallableTypeKind::Regular) } /// Return a "normalized" version of this `Callable` type. @@ -12586,7 +12567,6 @@ impl<'db> CallableType<'db> { db, self.signatures(db).normalized_impl(db, visitor), self.kind(db), - self.is_top_materialization(db), ) } @@ -12601,7 +12581,6 @@ impl<'db> CallableType<'db> { self.signatures(db) .recursive_type_normalized_impl(db, div, nested)?, self.kind(db), - self.is_top_materialization(db), )) } @@ -12612,43 +12591,11 @@ impl<'db> CallableType<'db> { tcx: TypeContext<'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { - if let TypeMapping::Materialize(materialization_kind) = type_mapping { - // Top materializations are fully static types already, - // so materializing them further does nothing. - if self.is_top_materialization(db) { - return self; - } - - // If we're materializing a callable with gradual parameters: - // - For Top materialization: wrap in `Top[...]` to preserve the gradual nature - // - For Bottom materialization: simplify to the bottom callable since - // `Bottom[Callable[..., R]]` is equivalent to `(*args: object, **kwargs: object) -> Bottom[R]` - if self.signatures(db).has_gradual_parameters() { - match materialization_kind { - MaterializationKind::Top => { - return CallableType::new( - db, - self.signatures(db) - .materialize_return_types(db, *materialization_kind), - self.kind(db), - true, - ); - } - MaterializationKind::Bottom => { - // Bottom materialization of a gradual callable simplifies to the - // bottom callable: (*args: object, **kwargs: object) -> Never - return CallableType::bottom(db); - } - } - } - } - CallableType::new( db, self.signatures(db) .apply_type_mapping_impl(db, type_mapping, tcx, visitor), self.kind(db), - self.is_top_materialization(db), ) } @@ -12679,23 +12626,6 @@ impl<'db> CallableType<'db> { return ConstraintSet::from(false); } - // Handle top materialization: - // - `Top[Callable[..., R]]` is a supertype of all callables with return type subtype of R. - // - // For Top, we only need to compare return types because Top parameters are a supertype - // of all possible parameters. Bottom materializations are simplified to the bottom - // callable directly, so they use normal signature comparison. - if other.is_top_materialization(db) { - return self.signatures(db).return_types_have_relation_to( - db, - other.signatures(db), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ); - } - self.signatures(db).has_relation_to_impl( db, other.signatures(db), @@ -12720,11 +12650,6 @@ impl<'db> CallableType<'db> { return ConstraintSet::from(true); } - // Callables with different top materialization status are not equivalent - if self.is_top_materialization(db) != other.is_top_materialization(db) { - return ConstraintSet::from(false); - } - ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || { self.signatures(db) .is_equivalent_to_impl(db, other.signatures(db), inferable, visitor) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 343d1a57dc..63255b3f43 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1622,21 +1622,6 @@ impl<'db> CallableBinding<'db> { ) .entered(); - // If the callable is a top materialization (e.g., `Top[Callable[..., object]]`), any call - // should fail because we don't know the actual signature. The type IS callable (it passes - // `callable()`), but it represents an infinite union of all possible callable types, so - // there's no valid set of arguments. - if let Type::Callable(callable) = self.signature_type - && callable.is_top_materialization(db) - { - for overload in &mut self.overloads { - overload - .errors - .push(BindingError::CalledTopCallable(self.signature_type)); - } - return None; - } - tracing::trace!( target: "ty_python_semantic::types::call::bind", matching_overload_index = ?self.matching_overload_index(), @@ -3716,6 +3701,11 @@ impl<'db> Binding<'db> { self.return_ty, &mut self.errors, ); + if self.signature.parameters().is_top() { + self.errors + .push(BindingError::CalledTopCallable(self.signature_type)); + return; + } // If this overload is generic, first see if we can infer a specialization of the function // from the arguments that were passed in. @@ -4741,6 +4731,5 @@ fn asynccontextmanager_return_type<'db>(db: &'db dyn Db, func_ty: Type<'db>) -> db, CallableSignature::single(new_signature), CallableTypeKind::FunctionLike, - false, ))) } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 5daaf077fc..669670c60d 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1056,7 +1056,6 @@ impl<'db> ClassType<'db> { db, getitem_signature, CallableTypeKind::FunctionLike, - false, )); Member::definitely_declared(getitem_type) }) @@ -1224,7 +1223,6 @@ impl<'db> ClassType<'db> { db, dunder_new_signature.bind_self(db, Some(instance_ty)), CallableTypeKind::FunctionLike, - false, ); if returns_non_subclass { @@ -1295,7 +1293,6 @@ impl<'db> ClassType<'db> { db, synthesized_dunder_init_signature, CallableTypeKind::FunctionLike, - false, )) } else { None @@ -2128,7 +2125,6 @@ impl<'db> ClassLiteral<'db> { db, callable_ty.signatures(db), CallableTypeKind::FunctionLike, - callable_ty.is_top_materialization(db), )), Type::Union(union) => { union.map(db, |element| into_function_like_callable(db, *element)) @@ -2773,7 +2769,6 @@ impl<'db> ClassLiteral<'db> { Some(Type::none(db)), )), CallableTypeKind::FunctionLike, - false, ))); } @@ -2800,7 +2795,6 @@ impl<'db> ClassLiteral<'db> { db, CallableSignature::from_overloads(overloads), CallableTypeKind::FunctionLike, - false, ))) } (CodeGeneratorKind::TypedDict, "__getitem__") => { @@ -2828,7 +2822,6 @@ impl<'db> ClassLiteral<'db> { db, CallableSignature::from_overloads(overloads), CallableTypeKind::FunctionLike, - false, ))) } (CodeGeneratorKind::TypedDict, "__delitem__") => { @@ -2860,7 +2853,6 @@ impl<'db> ClassLiteral<'db> { Some(Type::none(db)), )), CallableTypeKind::FunctionLike, - false, ))); } @@ -2886,7 +2878,6 @@ impl<'db> ClassLiteral<'db> { db, CallableSignature::from_overloads(overloads), CallableTypeKind::FunctionLike, - false, ))) } (CodeGeneratorKind::TypedDict, "get") => { @@ -2994,7 +2985,6 @@ impl<'db> ClassLiteral<'db> { db, CallableSignature::from_overloads(overloads), CallableTypeKind::FunctionLike, - false, ))) } (CodeGeneratorKind::TypedDict, "pop") => { @@ -3054,7 +3044,6 @@ impl<'db> ClassLiteral<'db> { db, CallableSignature::from_overloads(overloads), CallableTypeKind::FunctionLike, - false, ))) } (CodeGeneratorKind::TypedDict, "setdefault") => { @@ -3083,7 +3072,6 @@ impl<'db> ClassLiteral<'db> { db, CallableSignature::from_overloads(overloads), CallableTypeKind::FunctionLike, - false, ))) } (CodeGeneratorKind::TypedDict, "update") => { diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 5f577ac12f..05360811ce 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -1582,7 +1582,6 @@ impl<'db> CallableType<'db> { DisplayCallableType { signatures: self.signatures(db), kind: self.kind(db), - is_top_materialization: self.is_top_materialization(db), db, settings, } @@ -1592,20 +1591,12 @@ impl<'db> CallableType<'db> { pub(crate) struct DisplayCallableType<'a, 'db> { signatures: &'a CallableSignature<'db>, kind: CallableTypeKind, - is_top_materialization: bool, db: &'db dyn Db, settings: DisplaySettings<'db>, } impl<'db> FmtDetailed<'db> for DisplayCallableType<'_, 'db> { fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { - // If this callable is a top materialization, wrap it in Top[...] - if self.is_top_materialization { - f.with_type(Type::SpecialForm(SpecialFormType::Top)) - .write_str("Top")?; - f.write_char('[')?; - } - match self.signatures.overloads.as_slice() { [signature] => { if matches!(self.kind, CallableTypeKind::ParamSpecValue) { @@ -1639,9 +1630,6 @@ impl<'db> FmtDetailed<'db> for DisplayCallableType<'_, 'db> { } } - if self.is_top_materialization { - f.write_char(']')?; - } Ok(()) } } @@ -1701,6 +1689,10 @@ impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> { // When we exit this function, write a marker signaling we're ending a signature let mut f = f.with_detail(TypeDetail::SignatureEnd); + if self.parameters.is_top() { + f.write_str("Top[")?; + } + // If we're multiline printing and a name hasn't been emitted, try to // remember what the name was by checking if we have a definition if self.settings.multiline @@ -1722,7 +1714,13 @@ impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> { f.write_str(" -> ")?; return_ty .display_with(self.db, self.settings.singleline()) - .fmt_detailed(&mut f) + .fmt_detailed(&mut f)?; + + if self.parameters.is_top() { + f.write_str("]")?; + } + + Ok(()) } } @@ -1831,9 +1829,10 @@ impl<'db> FmtDetailed<'db> for DisplayParameters<'_, 'db> { f.write_char('/')?; } } - ParametersKind::Gradual => { + ParametersKind::Gradual | ParametersKind::Top => { // We represent gradual form as `...` in the signature, internally the parameters still - // contain `(*args, **kwargs)` parameters. + // contain `(*args, **kwargs)` parameters. (Top parameters are displayed the same + // as gradual parameters, we just wrap the entire signature in `Top[]`.) f.write_str("...")?; } ParametersKind::ParamSpec(typevar) => { @@ -2250,9 +2249,12 @@ impl<'db> FmtDetailed<'db> for DisplayMaybeParenthesizedType<'db> { f.write_char(')') }; match self.ty { - // Callable types with a top materialization are displayed as `Top[(...) -> T]`, - // which is already unambiguous and doesn't need additional parentheses. - Type::Callable(callable) if !callable.is_top_materialization(self.db) => { + Type::Callable(callable) + if callable.signatures(self.db).overloads.len() == 1 + && !callable.signatures(self.db).overloads[0] + .parameters() + .is_top() => + { write_parentheses(f) } Type::KnownBoundMethod(_) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 540e8b373f..40e4e953ef 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1094,7 +1094,7 @@ impl<'db> FunctionType<'db> { } else { CallableTypeKind::FunctionLike }; - CallableType::new(db, self.signature(db), kind, false) + CallableType::new(db, self.signature(db), kind) } /// Convert the `FunctionType` into a [`BoundMethodType`]. diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index e253f4d133..f499ee032a 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -2310,7 +2310,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { db, callable.signatures(db), kind, - callable.is_top_materialization(db), ))), Type::Union(union) => union .try_map(db, |element| propagate_callable_kind(db, *element, kind)), diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 8d45b5a42a..3c02a3aed8 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -990,6 +990,5 @@ fn protocol_bind_self<'db>( db, callable.signatures(db).bind_self(db, self_type), CallableTypeKind::Regular, - callable.is_top_materialization(db), ) } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 37574df023..d2e4236c21 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -189,20 +189,24 @@ impl<'db> CallableSignature<'db> { type_mapping.update_signature_generic_context(db, context) }), definition: signature.definition, - parameters: Parameters::new( - db, - prefix_parameters - .iter() - .map(|param| { - param.apply_type_mapping_impl( - db, - type_mapping, - tcx, - visitor, - ) - }) - .chain(signature.parameters().iter().cloned()), - ), + parameters: if signature.parameters().is_top() { + signature.parameters().clone() + } else { + Parameters::new( + db, + prefix_parameters + .iter() + .map(|param| { + param.apply_type_mapping_impl( + db, + type_mapping, + tcx, + visitor, + ) + }) + .chain(signature.parameters().iter().cloned()), + ) + }, return_ty: self_signature.return_ty.map(|ty| { ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor) }), @@ -428,7 +432,6 @@ impl<'db> CallableSignature<'db> { |signature| Signature::new(signature.parameters().clone(), None), )), CallableTypeKind::ParamSpecValue, - false, )); let param_spec_matches = ConstraintSet::constrain_typevar( db, @@ -462,7 +465,6 @@ impl<'db> CallableSignature<'db> { |signature| Signature::new(signature.parameters().clone(), None), )), CallableTypeKind::ParamSpecValue, - false, )); let param_spec_matches = ConstraintSet::constrain_typevar( db, @@ -571,56 +573,6 @@ impl<'db> CallableSignature<'db> { } } } - - /// Returns `true` if any signature in this callable has gradual parameters (`...`). - pub(crate) fn has_gradual_parameters(&self) -> bool { - self.overloads.iter().any(|sig| sig.parameters.is_gradual()) - } - - /// Materialize only the return types of all signatures, preserving parameters as-is. - /// - /// This is used when wrapping gradual callables in `Top[...]`. We want to preserve the gradual - /// parameters but materialize the return types (which are in covariant position). - pub(crate) fn materialize_return_types( - &self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Self { - Self::from_overloads( - self.overloads - .iter() - .map(|sig| sig.materialize_return_type(db, materialization_kind)), - ) - } - - /// Check whether the return types of this callable have the given relation to the return - /// types of another callable. - pub(crate) fn return_types_have_relation_to( - &self, - db: &'db dyn Db, - other: &Self, - inferable: InferableTypeVars<'_, 'db>, - relation: TypeRelation<'db>, - relation_visitor: &HasRelationToVisitor<'db>, - disjointness_visitor: &IsDisjointVisitor<'db>, - ) -> ConstraintSet<'db> { - // For each overload in self, the return type must have the relation to - // the return type of some overload in other. - self.overloads.iter().when_all(db, |self_sig| { - let self_return_ty = self_sig.return_ty.unwrap_or(Type::unknown()); - other.overloads.iter().when_any(db, |other_sig| { - let other_return_ty = other_sig.return_ty.unwrap_or(Type::unknown()); - self_return_ty.has_relation_to_impl( - db, - other_return_ty, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) - }) - } } impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> { @@ -780,27 +732,7 @@ impl<'db> Signature<'db> { /// Return the "bottom" signature, subtype of all other fully-static signatures. pub(crate) fn bottom() -> Self { - Self::new(Parameters::object(), Some(Type::Never)) - } - - /// Materialize only the return type, preserving parameters as-is. - pub(crate) fn materialize_return_type( - &self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Self { - Self { - generic_context: self.generic_context, - definition: self.definition, - parameters: self.parameters.clone(), - return_ty: self.return_ty.map(|ty| { - ty.materialize( - db, - materialization_kind, - &ApplyTypeMappingVisitor::default(), - ) - }), - } + Self::new(Parameters::bottom(), Some(Type::Never)) } pub(crate) fn with_inherited_generic_context( @@ -887,12 +819,9 @@ impl<'db> Signature<'db> { .generic_context .map(|context| type_mapping.update_signature_generic_context(db, context)), definition: self.definition, - parameters: self.parameters.apply_type_mapping_impl( - db, - &type_mapping.flip(), - tcx, - visitor, - ), + parameters: self + .parameters + .apply_type_mapping_impl(db, type_mapping, tcx, visitor), return_ty: self .return_ty .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)), @@ -1187,7 +1116,6 @@ impl<'db> Signature<'db> { .map(|signature| Signature::new(signature.parameters().clone(), None)), ), CallableTypeKind::ParamSpecValue, - false, )); let param_spec_matches = ConstraintSet::constrain_typevar(db, self_bound_typevar, Type::Never, upper); @@ -1404,6 +1332,14 @@ impl<'db> Signature<'db> { return ConstraintSet::from(true); } + // The top signature is supertype of (and assignable from) all other signatures. It is a + // subtype of no signature except itself, and assignable only to the gradual signature. + if other.parameters.is_top() { + return ConstraintSet::from(true); + } else if self.parameters.is_top() && !other.parameters.is_gradual() { + return ConstraintSet::from(false); + } + // If either of the parameter lists is gradual (`...`), then it is assignable to and from // any other parameter list, but not a subtype or supertype of any other parameter list. if self.parameters.is_gradual() || other.parameters.is_gradual() { @@ -1439,7 +1375,6 @@ impl<'db> Signature<'db> { db, CallableSignature::single(Signature::new(other.parameters.clone(), None)), CallableTypeKind::ParamSpecValue, - false, )); let param_spec_matches = ConstraintSet::constrain_typevar( db, @@ -1456,7 +1391,6 @@ impl<'db> Signature<'db> { db, CallableSignature::single(Signature::new(self.parameters.clone(), None)), CallableTypeKind::ParamSpecValue, - false, )); let param_spec_matches = ConstraintSet::constrain_typevar( db, @@ -1815,6 +1749,10 @@ pub(crate) enum ParametersKind<'db> { /// [the typing specification]: https://typing.python.org/en/latest/spec/callables.html#meaning-of-in-callable Gradual, + /// Represents the "top" parameters: top materialization of Gradual parameters, or infinite + /// union of all possible parameter signatures. + Top, + /// Represents a parameter list containing a `ParamSpec` as the only parameter. /// /// Note that this is distinct from a parameter list _containing_ a `ParamSpec` which is @@ -1894,6 +1832,10 @@ impl<'db> Parameters<'db> { matches!(self.kind, ParametersKind::Gradual) } + pub(crate) const fn is_top(&self) -> bool { + matches!(self.kind, ParametersKind::Top) + } + pub(crate) const fn as_paramspec(&self) -> Option> { match self.kind { ParametersKind::ParamSpec(bound_typevar) => Some(bound_typevar), @@ -1963,8 +1905,9 @@ impl<'db> Parameters<'db> { } } - /// Return parameters that represents `(*args: object, **kwargs: object)`. - pub(crate) fn object() -> Self { + /// Return parameters that represents `(*args: object, **kwargs: object)`, the bottom signature + /// (accepts any call, so subtype of all other signatures.) + pub(crate) fn bottom() -> Self { Self { value: vec![ Parameter::variadic(Name::new_static("args")).with_annotated_type(Type::object()), @@ -1975,6 +1918,25 @@ impl<'db> Parameters<'db> { } } + /// Return the "top" parameters (infinite union of all possible parameters), which cannot + /// accept any call, since there is no possible call that satisfies all possible parameter + /// signatures. This is not `(*Never, **Never)`, which is equivalent to no parameters at all + /// and still accepts the empty call `()`; it has to be represented instead as a special + /// `ParametersKind`. + pub(crate) fn top() -> Self { + Self { + // We always emit `called-top-callable` for any call to the top callable (based on the + // `kind` below), so we otherwise give it the most permissive signature`(*object, + // **object)`, so that we avoid emitting any other errors about arity mismatches. + value: vec![ + Parameter::variadic(Name::new_static("args")).with_annotated_type(Type::object()), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(Type::object()), + ], + kind: ParametersKind::Top, + } + } + /// Returns the bound `ParamSpec` type variable if the parameters contain a `ParamSpec`. pub(crate) fn find_paramspec_from_args_kwargs<'a>( &'a self, @@ -2131,11 +2093,29 @@ impl<'db> Parameters<'db> { tcx: TypeContext<'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { + if let TypeMapping::Materialize(materialization_kind) = type_mapping + && self.kind == ParametersKind::Gradual + { + match materialization_kind { + MaterializationKind::Bottom => { + // The bottom materialization of the `...` parameters is `(*object, **object)`, + // which accepts any call and is thus a subtype of all other parameters. + return Parameters::bottom(); + } + MaterializationKind::Top => { + return Parameters::top(); + } + } + } + + // Parameters are in contravariant position, so we need to flip the type mapping. + let type_mapping = type_mapping.flip(); + Self { value: self .value .iter() - .map(|param| param.apply_type_mapping_impl(db, type_mapping, tcx, visitor)) + .map(|param| param.apply_type_mapping_impl(db, &type_mapping, tcx, visitor)) .collect(), kind: self.kind, }