From 30902497db60378b1c05acc517f4c715dbf0e3cc Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 7 Jan 2026 09:18:39 -0800 Subject: [PATCH] [ty] Make signature return and parameter types non-optional (#22425) ## Summary Fixes https://github.com/astral-sh/ty/issues/2363 Fixes https://github.com/astral-sh/ty/issues/2013 And several other bugs with the same root cause. And makes any similar bugs impossible by construction. Previously we distinguished "no annotation" (Rust `None`) from "explicitly annotated with something of type `Unknown`" (which is not an error, and results in the annotation being of Rust type `Some(Type::DynamicType(Unknown))`), even though semantically these should be treated the same. This was a bit of a bug magnet, because it was easy to forget to make this `None` -> `Unknown` translation everywhere we needed to. And in fact we did fail to do it in the case of materializing a callable, leading to a top-materialized callable still having (rust) `None` return type, which should have instead materialized to `object`. This also fixes several other bugs related to not handling un-annotated return types correctly: 1. We previously considered the return type of an unannotated `async def` to be `Unknown`, where it should be `CoroutineType[Any, Any, Unknown]`. 2. We previously failed to infer a ParamSpec if the return type of the callable we are inferring against was not annotated. 3. We previously wrongly returned `Unknown` from `some_dict.get("key", None)` if the value type of `some_dict` included a callable type with un-annotated return type. We now make signature return types and annotated parameter types required, and we eagerly insert `Unknown` if there's no annotation. Most of the diff is just a bunch of mechanical code changes where we construct these types, and simplifications where we use them. One exception is type display: when a callable type has un-annotated parameters, we want to display them as un-annotated, but if it has a parameter explicitly annotated with something of `Unknown` type, we want to display that parameter as `x: Unknown` (it would be confusing if it looked like your annotation just disappeared entirely). Fortunately, we already have a mechanism in place for handling this: the `inferred_annotation` flag, which suppresses display of an annotation. Previously we used it only for `self` and `cls` parameters with an inferred annotated type -- but we now also set it for any un-annotated parameter, for which we infer `Unknown` type. We also need to normalize `inferred_annotation`, since it's display-only and shouldn't impact type equivalence. (This is technically a previously-existing bug, it just never came up when it only affected self types -- now it comes up because we have tests asserting that `def f(x)` and `def g(x: Unknown)` are equivalent.) ## Test Plan Added mdtests. --- crates/ty_ide/src/completion.rs | 8 +- crates/ty_ide/src/signature_help.rs | 9 +- .../resources/mdtest/async.md | 12 + .../mdtest/generics/pep695/paramspec.md | 17 + .../type_properties/is_assignable_to.md | 23 + crates/ty_python_semantic/src/place.rs | 4 +- .../reachability_constraints.rs | 4 +- crates/ty_python_semantic/src/types.rs | 117 +++-- .../ty_python_semantic/src/types/call/bind.rs | 95 ++-- crates/ty_python_semantic/src/types/class.rs | 87 ++-- .../ty_python_semantic/src/types/display.rs | 47 +- .../ty_python_semantic/src/types/generics.rs | 12 +- .../src/types/ide_support.rs | 15 +- .../src/types/infer/builder.rs | 11 +- .../types/infer/builder/type_expression.rs | 2 +- .../types/property_tests/type_generation.rs | 26 +- .../src/types/protocol_class.rs | 2 +- .../src/types/signatures.rs | 433 +++++++++--------- 18 files changed, 472 insertions(+), 452 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 3236f1aaba..156cb4c37d 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -420,7 +420,7 @@ impl<'db> Completion<'db> { } } - fn argument(name: &str, ty: Option>, documentation: Option<&str>) -> Self { + fn argument(name: &str, ty: Type<'db>, documentation: Option<&str>) -> Self { let insert = Some(format!("{name}=").into_boxed_str()); let documentation = documentation.map(|d| Docstring::new(d.to_owned())); @@ -428,7 +428,7 @@ impl<'db> Completion<'db> { name: name.into(), qualified: None, insert, - ty, + ty: Some(ty), kind: Some(CompletionKind::Variable), module_name: None, import: None, @@ -1134,7 +1134,7 @@ fn add_class_arg_completions<'db>( }; if !is_set("metaclass") { - let ty = Some(KnownClass::Type.to_subclass_of(model.db())); + let ty = KnownClass::Type.to_subclass_of(model.db()); completions.add(Completion::argument("metaclass", ty, None)); } @@ -1148,7 +1148,7 @@ fn add_class_arg_completions<'db>( // // See https://peps.python.org/pep-0728/ if is_typed_dict && !is_set("total") { - let ty = Some(KnownClass::Bool.to_instance(model.db())); + let ty = KnownClass::Bool.to_instance(model.db()); completions.add(Completion::argument("total", ty, None)); } } diff --git a/crates/ty_ide/src/signature_help.rs b/crates/ty_ide/src/signature_help.rs index ee0da0f9d4..3c5e277694 100644 --- a/crates/ty_ide/src/signature_help.rs +++ b/crates/ty_ide/src/signature_help.rs @@ -34,8 +34,8 @@ pub struct ParameterDetails<'db> { pub name: String, /// The parameter label in the signature (e.g., "param1: str") pub label: String, - /// The annotated type of the parameter, if any - pub ty: Option>, + /// The annotated type of the parameter. If no annotation was provided, this is `Unknown`. + pub ty: Type<'db>, /// Documentation specific to the parameter, typically extracted from the /// function's docstring pub documentation: Option, @@ -237,7 +237,7 @@ fn create_parameters_from_offsets<'db>( docstring: Option<&Docstring>, parameter_names: &[String], parameter_kinds: &[ParameterKind], - parameter_types: &[Option>], + parameter_types: &[Type<'db>], ) -> Vec> { // Extract parameter documentation from the function's docstring if available. let param_docs = if let Some(docstring) = docstring { @@ -264,12 +264,11 @@ fn create_parameters_from_offsets<'db>( parameter_kinds.get(i), Some(ParameterKind::PositionalOnly { .. }) ); - let ty = parameter_types.get(i).copied().flatten(); ParameterDetails { name: param_name.to_string(), label, - ty, + ty: parameter_types[i], documentation: param_docs.get(param_name).cloned(), is_positional_only, } diff --git a/crates/ty_python_semantic/resources/mdtest/async.md b/crates/ty_python_semantic/resources/mdtest/async.md index 9fad1a2506..d0b9b4b464 100644 --- a/crates/ty_python_semantic/resources/mdtest/async.md +++ b/crates/ty_python_semantic/resources/mdtest/async.md @@ -117,3 +117,15 @@ def _(): result = yield from retrieve().__await__() reveal_type(result) # revealed: int ``` + +## Un-annotated async functions + +An `async def` with no annotated return type is still known to return `CoroutineType` of `Unknown`, +not just `Unknown`: + +```py +async def f(): + pass + +reveal_type(f()) # revealed: CoroutineType[Any, Any, Unknown] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md index 03883122ee..9117e09e27 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md @@ -783,3 +783,20 @@ class Container[**P]: # error: [invalid-argument-type] "Argument to bound method `method` is incorrect: Expected `(**P@Container) -> None`, found `(**Q@try_assign) -> None`" return self.method(f) ``` + +## `ParamSpec` inference with un-annotated return type + +Regression test for an issue where `ParamSpec` inference failed when the callable we were inferring +from did not have an annotated return type. + +```py +from typing import Callable + +def infer_paramspec[**P](func: Callable[P, None]) -> Callable[P, None]: + return func + +def f(x: int, y: str): + pass + +reveal_type(infer_paramspec(f)) # revealed: (x: int, y: str) -> None +``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index cfb7b5d6e0..c64e659ed1 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -736,6 +736,29 @@ static_assert(is_assignable_to(Intersection[LiteralString, Not[Literal[""]]], No static_assert(is_assignable_to(Intersection[LiteralString, Not[Literal["", "a"]]], Not[AlwaysFalsy])) ``` +## Callable types with Unknown/missing return type + +See , a property test failure involving +`~type & ~((...) -> Unknown)` not being assignable to `~type`. Since `~type & ~Callable` is a subset +of `~type`, the intersection should be assignable to `~type`. + +The root cause was that we failed to properly materialize a `Callable[..., Unknown]` type when the +`Unknown` return type originated from a missing annotation. + +```py +from ty_extensions import static_assert, is_assignable_to, Intersection, Not, Unknown, CallableTypeOf +from typing import Callable + +# `Callable[..., Unknown]` has explicit Unknown return type +static_assert(is_assignable_to(Intersection[Not[type], Not[Callable[..., Unknown]]], Not[type])) + +# Function with no return annotation (has implicit Unknown return type internally) +def no_return_annotation(*args, **kwargs): ... + +# `CallableTypeOf[no_return_annotation]` has `returns: None` internally (no annotation) +static_assert(is_assignable_to(Intersection[Not[type], Not[CallableTypeOf[no_return_annotation]]], Not[type])) +``` + ## Intersections with non-fully-static negated elements A type can be _assignable_ to an intersection containing negated elements only if the _bottom_ diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 7734d3a75f..02ccbbac57 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -1687,10 +1687,10 @@ mod implicit_globals { [Parameter::positional_only(Some(Name::new_static("format"))) .with_annotated_type(KnownClass::Int.to_instance(db))], ), - Some(KnownClass::Dict.to_specialized_instance( + KnownClass::Dict.to_specialized_instance( db, [KnownClass::Str.to_instance(db), Type::any()], - )), + ), ); Place::Defined( DefinedPlace::new(Type::function_like_callable(db, signature)) diff --git a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs index ef6fb07481..8e5ae7e683 100644 --- a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs @@ -899,9 +899,7 @@ impl ReachabilityConstraints { let (no_overloads_return_never, all_overloads_return_never) = overloads_iterator .fold((true, true), |(none, all), overload| { let overload_returns_never = - overload.return_ty.is_some_and(|return_type| { - return_type.is_equivalent_to(db, Type::Never) - }); + overload.return_ty.is_equivalent_to(db, Type::Never); ( none && !overload_returns_never, diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 39a993e27a..391cbf40f8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1925,7 +1925,7 @@ impl<'db> Type<'db> { SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => { Some(CallableTypes::one(CallableType::single( db, - Signature::new(Parameters::unknown(), Some(Type::from(subclass_of_ty))), + Signature::new(Parameters::unknown(), Type::from(subclass_of_ty)), ))) } }, @@ -1968,7 +1968,7 @@ impl<'db> Type<'db> { [Parameter::positional_only(None) .with_annotated_type(newtype.base(db).instance_type(db))], ), - Some(Type::NewTypeInstance(newtype)), + Type::NewTypeInstance(newtype), ), ))) } @@ -3811,7 +3811,7 @@ impl<'db> Type<'db> { [Parameter::positional_only(Some(Name::new_static("func"))) .with_annotated_type(Type::object())], ), - None, + Type::unknown(), ), ) .into(), @@ -3836,7 +3836,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()), ], ), - Some(KnownClass::ConstraintSet.to_instance(db)), + KnownClass::ConstraintSet.to_instance(db), ), ) .into(), @@ -3851,7 +3851,7 @@ impl<'db> Type<'db> { .type_form() .with_annotated_type(Type::any())], ), - Some(KnownClass::Bool.to_instance(db)), + KnownClass::Bool.to_instance(db), ), ) .into() @@ -3878,7 +3878,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()), ], ), - Some(Type::TypeVar(val_ty)), + Type::TypeVar(val_ty), ), ) .into() @@ -3897,7 +3897,7 @@ impl<'db> Type<'db> { // errors instead of `type-assertion-failure` errors. .with_annotated_type(Type::any())], ), - Some(Type::none(db)), + Type::none(db), ), ) .into() @@ -3916,7 +3916,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()), ], ), - Some(Type::any()), + Type::any(), ), ) .into(), @@ -3932,7 +3932,7 @@ impl<'db> Type<'db> { [Parameter::positional_only(Some(Name::new_static("cls"))) .with_annotated_type(Type::none(db))], ), - None, + Type::unknown(), ), // def dataclass(cls: type[_T], /) -> type[_T]: ... Signature::new( @@ -3941,7 +3941,7 @@ impl<'db> Type<'db> { [Parameter::positional_only(Some(Name::new_static("cls"))) .with_annotated_type(KnownClass::Type.to_instance(db))], ), - None, + Type::unknown(), ), // TODO: make this overload Python-version-dependent @@ -3994,7 +3994,7 @@ impl<'db> Type<'db> { .with_default_type(Type::BooleanLiteral(false)), ], ), - None, + Type::unknown(), ), ], ) @@ -4029,7 +4029,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()) .with_default_type(Type::BooleanLiteral(false))], ), - Some(KnownClass::Bool.to_instance(db)), + KnownClass::Bool.to_instance(db), ), ) .into() @@ -4053,7 +4053,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::object()) .with_default_type(Type::string_literal(db, ""))], ), - Some(KnownClass::Str.to_instance(db)), + KnownClass::Str.to_instance(db), ), Signature::new( Parameters::new( @@ -4083,7 +4083,7 @@ impl<'db> Type<'db> { .with_default_type(Type::string_literal(db, "strict")), ], ), - Some(KnownClass::Str.to_instance(db)), + KnownClass::Str.to_instance(db), ), ], ) @@ -4110,7 +4110,7 @@ impl<'db> Type<'db> { [Parameter::positional_only(Some(Name::new_static("o"))) .with_annotated_type(Type::any())], ), - Some(type_instance), + type_instance, ), Signature::new( Parameters::new( @@ -4132,7 +4132,7 @@ impl<'db> Type<'db> { ), ], ), - Some(type_instance), + type_instance, ), ], ) @@ -4145,11 +4145,8 @@ impl<'db> Type<'db> { // def __init__(self) -> None: ... // def __new__(cls) -> Self: ... // ``` - Binding::single( - self, - Signature::new(Parameters::empty(), Some(Type::object())), - ) - .into() + Binding::single(self, Signature::new(Parameters::empty(), Type::object())) + .into() } Some(KnownClass::Enum) => { @@ -4179,7 +4176,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()), ], ), - Some(KnownClass::Super.to_instance(db)), + KnownClass::Super.to_instance(db), ), Signature::new( Parameters::new( @@ -4187,12 +4184,9 @@ impl<'db> Type<'db> { [Parameter::positional_only(Some(Name::new_static("t"))) .with_annotated_type(Type::any())], ), - Some(KnownClass::Super.to_instance(db)), - ), - Signature::new( - Parameters::empty(), - Some(KnownClass::Super.to_instance(db)), + KnownClass::Super.to_instance(db), ), + Signature::new(Parameters::empty(), KnownClass::Super.to_instance(db)), ], ) .into() @@ -4234,7 +4228,7 @@ impl<'db> Type<'db> { .with_default_type(Type::IntLiteral(1)), ], ), - Some(KnownClass::Deprecated.to_instance(db)), + KnownClass::Deprecated.to_instance(db), ), ) .into() @@ -4276,7 +4270,7 @@ impl<'db> Type<'db> { .with_default_type(Type::empty_tuple(db)), ], ), - None, + Type::unknown(), ), ) .into() @@ -4288,7 +4282,7 @@ impl<'db> Type<'db> { db, [Parameter::positional_only(None).with_annotated_type(Type::any())], ), - Some(Type::any()), + Type::any(), ); let setter_signature = Signature::new( Parameters::new( @@ -4298,14 +4292,14 @@ impl<'db> Type<'db> { Parameter::positional_only(None).with_annotated_type(Type::any()), ], ), - Some(Type::none(db)), + Type::none(db), ); let deleter_signature = Signature::new( Parameters::new( db, [Parameter::positional_only(None).with_annotated_type(Type::any())], ), - Some(Type::any()), + Type::any(), ); Binding::single( @@ -4349,7 +4343,7 @@ impl<'db> Type<'db> { .with_default_type(Type::none(db)), ], ), - None, + Type::unknown(), ), ) .into() @@ -4372,7 +4366,7 @@ impl<'db> Type<'db> { CallableBinding::from_overloads( self, [ - Signature::new(Parameters::empty(), Some(Type::empty_tuple(db))), + Signature::new(Parameters::empty(), Type::empty_tuple(db)), Signature::new_generic( Some(GenericContext::from_typevar_instances(db, [element_ty])), Parameters::new( @@ -4387,7 +4381,7 @@ impl<'db> Type<'db> { ), )], ), - Some(Type::homogeneous_tuple(db, Type::TypeVar(element_ty))), + Type::homogeneous_tuple(db, Type::TypeVar(element_ty)), ), ], ) @@ -4404,7 +4398,7 @@ impl<'db> Type<'db> { Signature::new_generic( class.generic_context(db), Parameters::gradual_form(), - self.to_instance(db), + self.to_instance(db).unwrap_or(Type::unknown()), ), ) .into(), @@ -4430,7 +4424,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()), ], ), - None, + Type::unknown(), ), ) .into() @@ -4445,7 +4439,10 @@ impl<'db> Type<'db> { // TODO check call vs signatures of `__new__` and/or `__init__` Binding::single( self, - Signature::new(Parameters::gradual_form(), self.to_instance(db)), + Signature::new( + Parameters::gradual_form(), + self.to_instance(db).unwrap_or(Type::unknown()), + ), ) .into() } @@ -4463,7 +4460,10 @@ impl<'db> Type<'db> { // TODO check call vs signatures of `__new__` and/or `__init__` SubclassOfInner::TypeVar(_) => Binding::single( self, - Signature::new(Parameters::gradual_form(), self.to_instance(db)), + Signature::new( + Parameters::gradual_form(), + self.to_instance(db).unwrap_or(Type::unknown()), + ), ) .into(), }, @@ -4530,11 +4530,8 @@ impl<'db> Type<'db> { // Intersect with `Any` for the return type to reflect the fact that the `dataclass()` // decorator adds methods to the class let returns = IntersectionType::from_elements(db, [typevar_meta, Type::any()]); - let signature = Signature::new_generic( - Some(context), - Parameters::new(db, parameters), - Some(returns), - ); + let signature = + Signature::new_generic(Some(context), Parameters::new(db, parameters), returns); Binding::single(self, signature).into() } @@ -4551,7 +4548,7 @@ impl<'db> Type<'db> { [Parameter::positional_only(None) .with_annotated_type(newtype.base(db).instance_type(db))], ), - Some(Type::NewTypeInstance(newtype)), + Type::NewTypeInstance(newtype), ), ) .into(), @@ -10387,7 +10384,7 @@ impl<'db> CallableType<'db> { ) -> CallableType<'db> { CallableType::new( db, - CallableSignature::single(Signature::new(parameters, None)), + CallableSignature::single(Signature::new(parameters, Type::unknown())), CallableTypeKind::ParamSpecValue, ) } @@ -10990,7 +10987,7 @@ impl<'db> KnownBoundMethodType<'db> { .with_annotated_type(KnownClass::Type.to_instance(db)), ], ), - None, + Type::unknown(), ), Signature::new( Parameters::new( @@ -11006,7 +11003,7 @@ impl<'db> KnownBoundMethodType<'db> { .with_default_type(Type::none(db)), ], ), - None, + Type::unknown(), ), ] .into_iter(), @@ -11025,7 +11022,7 @@ impl<'db> KnownBoundMethodType<'db> { .with_annotated_type(Type::object()), ], ), - None, + Type::unknown(), ))) } KnownBoundMethodType::StrStartswith(_) => { @@ -11058,7 +11055,7 @@ impl<'db> KnownBoundMethodType<'db> { .with_default_type(Type::none(db)), ], ), - Some(KnownClass::Bool.to_instance(db)), + KnownClass::Bool.to_instance(db), ))) } @@ -11078,7 +11075,7 @@ impl<'db> KnownBoundMethodType<'db> { .with_annotated_type(Type::any()), ], ), - Some(KnownClass::ConstraintSet.to_instance(db)), + KnownClass::ConstraintSet.to_instance(db), ))) } @@ -11086,7 +11083,7 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetNever => { Either::Right(std::iter::once(Signature::new( Parameters::empty(), - Some(KnownClass::ConstraintSet.to_instance(db)), + KnownClass::ConstraintSet.to_instance(db), ))) } @@ -11103,7 +11100,7 @@ impl<'db> KnownBoundMethodType<'db> { .with_annotated_type(Type::any()), ], ), - Some(KnownClass::ConstraintSet.to_instance(db)), + KnownClass::ConstraintSet.to_instance(db), ))) } @@ -11114,7 +11111,7 @@ impl<'db> KnownBoundMethodType<'db> { [Parameter::positional_only(Some(Name::new_static("other"))) .with_annotated_type(KnownClass::ConstraintSet.to_instance(db))], ), - Some(KnownClass::ConstraintSet.to_instance(db)), + KnownClass::ConstraintSet.to_instance(db), ))) } @@ -11130,7 +11127,7 @@ impl<'db> KnownBoundMethodType<'db> { )) .with_default_type(Type::none(db))], ), - Some(KnownClass::Bool.to_instance(db)), + KnownClass::Bool.to_instance(db), ))) } @@ -11143,10 +11140,10 @@ impl<'db> KnownBoundMethodType<'db> { .with_annotated_type(KnownClass::ConstraintSet.to_instance(db)), ], ), - Some(UnionType::from_elements( + UnionType::from_elements( db, [KnownClass::Specialization.to_instance(db), Type::none(db)], - )), + ), ))) } } @@ -11193,7 +11190,7 @@ impl WrapperDescriptorKind { .with_annotated_type(type_instance), ], ), - None, + Type::unknown(), ), Signature::new( Parameters::new( @@ -11211,7 +11208,7 @@ impl WrapperDescriptorKind { .with_default_type(none), ], ), - None, + Type::unknown(), ), ] } @@ -11237,7 +11234,7 @@ impl WrapperDescriptorKind { .with_annotated_type(object), ], ), - None, + Type::unknown(), ))) } } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 152d2070bf..545de4809e 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1747,9 +1747,8 @@ impl<'db> CallableBinding<'db> { let mut is_argument_assignable_to_any_overload = false; 'overload: for overload in &self.overloads { for parameter_index in &overload.argument_matches[argument_index].parameters { - let parameter_type = overload.signature.parameters()[*parameter_index] - .annotated_type() - .unwrap_or(Type::unknown()); + let parameter_type = + overload.signature.parameters()[*parameter_index].annotated_type(); if argument_type .when_assignable_to(db, parameter_type, overload.inferable_typevars) .is_always_satisfied(db) @@ -1996,9 +1995,8 @@ impl<'db> CallableBinding<'db> { for ¶meter_index in &overload.argument_matches[argument_index].parameters { // TODO: For an unannotated `self` / `cls` parameter, the type should be // `typing.Self` / `type[typing.Self]` - let current_parameter_type = overload.signature.parameters()[parameter_index] - .annotated_type() - .unwrap_or(Type::unknown()); + let current_parameter_type = + overload.signature.parameters()[parameter_index].annotated_type(); let first_parameter_type = &mut first_parameter_types[parameter_index]; if let Some(first_parameter_type) = first_parameter_type { if !first_parameter_type @@ -2109,9 +2107,8 @@ impl<'db> CallableBinding<'db> { } // TODO: For an unannotated `self` / `cls` parameter, the type should be // `typing.Self` / `type[typing.Self]` - let mut parameter_type = overload.signature.parameters()[*parameter_index] - .annotated_type() - .unwrap_or(Type::unknown()); + let mut parameter_type = + overload.signature.parameters()[*parameter_index].annotated_type(); if let Some(specialization) = overload.specialization { parameter_type = parameter_type.apply_specialization(db, specialization); @@ -3011,7 +3008,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { let return_with_tcx = self .constructor_instance_type - .or(self.signature.return_ty) + .or(Some(self.signature.return_ty)) .zip(self.call_expression_tcx.annotation); self.inferable_typevars = generic_context.inferable_typevars(self.db); @@ -3051,13 +3048,8 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { for (parameter_index, variadic_argument_type) in self.argument_matches[argument_index].iter() { - let parameter = ¶meters[parameter_index]; - let Some(expected_type) = parameter.annotated_type() else { - continue; - }; - let specialization_result = builder.infer_map( - expected_type, + parameters[parameter_index].annotated_type(), variadic_argument_type.unwrap_or(argument_type), |(identity, variance, inferred_ty)| { // Avoid widening the inferred type if it is already assignable to the @@ -3092,10 +3084,9 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { // Attempt to promote any literal types assigned to the specialization. let maybe_promote = |identity, typevar, ty: Type<'db>| { - let Some(return_ty) = self.constructor_instance_type.or(self.signature.return_ty) - else { - return ty; - }; + let return_ty = self + .constructor_instance_type + .unwrap_or(self.signature.return_ty); let mut combined_tcx = TypeContext::default(); let mut variance_in_return = TypeVarVariance::Bivariant; @@ -3188,30 +3179,29 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { ) { let parameters = self.signature.parameters(); let parameter = ¶meters[parameter_index]; - if let Some(mut expected_ty) = parameter.annotated_type() { - if let Some(specialization) = self.specialization { - argument_type = argument_type.apply_specialization(self.db, specialization); - expected_ty = expected_ty.apply_specialization(self.db, specialization); - } - // This is one of the few places where we want to check if there's _any_ specialization - // where assignability holds; normally we want to check that assignability holds for - // _all_ specializations. - // TODO: Soon we will go further, and build the actual specializations from the - // constraint set that we get from this assignability check, instead of inferring and - // building them in an earlier separate step. - if argument_type - .when_assignable_to(self.db, expected_ty, self.inferable_typevars) - .is_never_satisfied(self.db) - { - let positional = matches!(argument, Argument::Positional | Argument::Synthetic) - && !parameter.is_variadic(); - self.errors.push(BindingError::InvalidArgumentType { - parameter: ParameterContext::new(parameter, parameter_index, positional), - argument_index: adjusted_argument_index, - expected_ty, - provided_ty: argument_type, - }); - } + let mut expected_ty = parameter.annotated_type(); + if let Some(specialization) = self.specialization { + argument_type = argument_type.apply_specialization(self.db, specialization); + expected_ty = expected_ty.apply_specialization(self.db, specialization); + } + // This is one of the few places where we want to check if there's _any_ specialization + // where assignability holds; normally we want to check that assignability holds for + // _all_ specializations. + // TODO: Soon we will go further, and build the actual specializations from the + // constraint set that we get from this assignability check, instead of inferring and + // building them in an earlier separate step. + if argument_type + .when_assignable_to(self.db, expected_ty, self.inferable_typevars) + .is_never_satisfied(self.db) + { + let positional = matches!(argument, Argument::Positional | Argument::Synthetic) + && !parameter.is_variadic(); + self.errors.push(BindingError::InvalidArgumentType { + parameter: ParameterContext::new(parameter, parameter_index, positional), + argument_index: adjusted_argument_index, + expected_ty, + provided_ty: argument_type, + }); } // We still update the actual type of the parameter in this binding to match the // argument, even if the argument type is not assignable to the expected parameter @@ -3328,10 +3318,11 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { return false; }; - if !self.signature.parameters()[*parameter_index] - .annotated_type() - .is_some_and(|ty| matches!(ty, Type::TypeVar(typevar) if typevar.is_paramspec(self.db))) - { + let Type::TypeVar(typevar) = self.signature.parameters()[*parameter_index].annotated_type() + else { + return false; + }; + if !typevar.is_paramspec(self.db) { return false; } @@ -3692,7 +3683,7 @@ impl<'db> Binding<'db> { for (keywords_index, keywords_type) in keywords_arguments { matcher.match_keyword_variadic(db, keywords_index, keywords_type); } - self.return_ty = self.signature.return_ty.unwrap_or(Type::unknown()); + self.return_ty = self.signature.return_ty; self.parameter_tys = vec![None; parameters.len()].into_boxed_slice(); self.variadic_argument_matched_to_variadic_parameter = matcher.variadic_argument_matched_to_variadic_parameter; @@ -4723,9 +4714,9 @@ fn asynccontextmanager_return_type<'db>(db: &'db dyn Db, func_ty: Type<'db>) -> .exactly_one() .ok()?; let signature = &binding.signature; - let return_ty = signature.return_ty?; - let yield_ty = return_ty + let yield_ty = signature + .return_ty .try_iterate_with_mode(db, EvaluationMode::Async) .ok()? .homogeneous_element_type(db); @@ -4741,7 +4732,7 @@ fn asynccontextmanager_return_type<'db>(db: &'db dyn Db, func_ty: Type<'db>) -> }); let new_return_ty = Type::from(context_manager).to_instance(db)?; - let new_signature = Signature::new(signature.parameters().clone(), Some(new_return_ty)); + let new_signature = Signature::new(signature.parameters().clone(), new_return_ty); Some(Type::Callable(CallableType::new( db, diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 6546c46955..bfbaf704cd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -858,7 +858,7 @@ impl<'db> ClassType<'db> { let index_parameter = Parameter::positional_only(Some(Name::new_static("index"))) .with_annotated_type(index_annotation); let parameters = Parameters::new(db, [self_parameter, index_parameter]); - Signature::new(parameters, Some(return_annotation)) + Signature::new(parameters, return_annotation) } let (class_literal, specialization) = self.class_literal(db); @@ -895,7 +895,7 @@ impl<'db> ClassType<'db> { ); let synthesized_dunder_method = - Type::function_like_callable(db, Signature::new(parameters, Some(return_type))); + Type::function_like_callable(db, Signature::new(parameters, return_type)); Member::definitely_declared(synthesized_dunder_method) } @@ -1133,7 +1133,7 @@ impl<'db> ClassType<'db> { let synthesized_dunder = Type::function_like_callable( db, - Signature::new_generic(inherited_generic_context, parameters, None), + Signature::new_generic(inherited_generic_context, parameters, Type::unknown()), ); Member::definitely_declared(synthesized_dunder) @@ -1214,14 +1214,12 @@ impl<'db> ClassType<'db> { // Step 3: If the return type of the `__new__` evaluates to a type that is not a subclass of this class, // then we should ignore the `__init__` and just return the `__new__` method. let returns_non_subclass = dunder_new_signature.overloads.iter().any(|signature| { - signature.return_ty.is_some_and(|return_ty| { - !return_ty.is_assignable_to( - db, - self_ty - .to_instance(db) - .expect("ClassType should be instantiable"), - ) - }) + !signature.return_ty.is_assignable_to( + db, + self_ty + .to_instance(db) + .expect("ClassType should be instantiable"), + ) }); let instance_ty = Type::instance(db, self); @@ -1270,7 +1268,7 @@ impl<'db> ClassType<'db> { .parameters() .get_positional(0) .filter(|parameter| !parameter.inferred_annotation) - .and_then(Parameter::annotated_type) + .map(Parameter::annotated_type) .filter(|ty| { ty.as_typevar() .is_none_or(|bound_typevar| !bound_typevar.typevar(db).is_self(db)) @@ -1285,7 +1283,7 @@ impl<'db> ClassType<'db> { Signature::new_generic( generic_context, signature.parameters().clone(), - Some(return_type), + return_type, ) .with_definition(signature.definition()) .bind_self(db, Some(instance_ty)) @@ -1349,7 +1347,7 @@ impl<'db> ClassType<'db> { Signature::new_generic( class_generic_context, Parameters::empty(), - Some(correct_return_type), + correct_return_type, ), )) } @@ -2335,7 +2333,7 @@ impl<'db> ClassLiteral<'db> { db, [Parameter::positional_only(Some(Name::new_static("self")))], ), - Some(field.declared_ty), + field.declared_ty, ); let property_getter = Type::single_callable(db, property_getter_signature); let property = PropertyInstanceType::new(db, Some(property_getter), None); @@ -2432,7 +2430,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(instance_ty), ], ), - Some(KnownClass::Bool.to_instance(db)), + KnownClass::Bool.to_instance(db), ); return Some(Type::function_like_callable(db, signature)); @@ -2491,7 +2489,7 @@ impl<'db> ClassLiteral<'db> { let instance_ty = Type::instance(db, self.apply_optional_specialization(db, specialization)); - let signature_from_fields = |mut parameters: Vec<_>, return_ty: Option>| { + let signature_from_fields = |mut parameters: Vec<_>, return_ty: Type<'db>| { for (field_name, field) in self.fields(db, specialization, field_policy) { let (init, mut default_ty, kw_only, alias) = match &field.kind { FieldKind::NamedTuple { default_ty } => (true, *default_ty, None, None), @@ -2544,9 +2542,7 @@ impl<'db> ClassLiteral<'db> { if let Some(value_param) = overload.signature.parameters().get_positional(2) { - value_types = value_types.add( - value_param.annotated_type().unwrap_or_else(Type::unknown), - ); + value_types = value_types.add(value_param.annotated_type()); } else if overload.signature.parameters().is_gradual() { value_types = value_types.add(Type::unknown()); } @@ -2618,12 +2614,12 @@ impl<'db> ClassLiteral<'db> { let self_parameter = Parameter::positional_or_keyword(Name::new_static("self")) // TODO: could be `Self`. .with_annotated_type(instance_ty); - signature_from_fields(vec![self_parameter], Some(Type::none(db))) + signature_from_fields(vec![self_parameter], Type::none(db)) } (CodeGeneratorKind::NamedTuple, "__new__") => { let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls")) .with_annotated_type(KnownClass::Type.to_instance(db)); - signature_from_fields(vec![cls_parameter], Some(Type::none(db))) + signature_from_fields(vec![cls_parameter], Type::none(db)) } (CodeGeneratorKind::NamedTuple, "_replace" | "__replace__") => { if name == "__replace__" @@ -2642,7 +2638,7 @@ impl<'db> ClassLiteral<'db> { )); let self_parameter = Parameter::positional_or_keyword(Name::new_static("self")) .with_annotated_type(self_ty); - signature_from_fields(vec![self_parameter], Some(self_ty)) + signature_from_fields(vec![self_parameter], self_ty) } (CodeGeneratorKind::NamedTuple, "_fields") => { // Synthesize a precise tuple type for _fields using literal string types. @@ -2669,7 +2665,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(instance_ty), ], ), - Some(KnownClass::Bool.to_instance(db)), + KnownClass::Bool.to_instance(db), ); Some(Type::function_like_callable(db, signature)) @@ -2686,7 +2682,7 @@ impl<'db> ClassLiteral<'db> { [Parameter::positional_or_keyword(Name::new_static("self")) .with_annotated_type(instance_ty)], ), - Some(KnownClass::Int.to_instance(db)), + KnownClass::Int.to_instance(db), ); Some(Type::function_like_callable(db, signature)) @@ -2762,7 +2758,7 @@ impl<'db> ClassLiteral<'db> { let self_parameter = Parameter::positional_or_keyword(Name::new_static("self")) .with_annotated_type(instance_ty); - signature_from_fields(vec![self_parameter], Some(instance_ty)) + signature_from_fields(vec![self_parameter], instance_ty) } (CodeGeneratorKind::DataclassLike(_), "__setattr__") => { if has_dataclass_param(DataclassFlags::FROZEN) { @@ -2776,7 +2772,7 @@ impl<'db> ClassLiteral<'db> { Parameter::positional_or_keyword(Name::new_static("value")), ], ), - Some(Type::Never), + Type::Never, ); return Some(Type::function_like_callable(db, signature)); @@ -2821,7 +2817,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(Type::any()), ], ), - Some(Type::none(db)), + Type::none(db), )), CallableTypeKind::FunctionLike, ))); @@ -2842,7 +2838,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(field.declared_ty), ], ), - Some(Type::none(db)), + Type::none(db), ) }); @@ -2869,7 +2865,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(key_type), ], ), - Some(field.declared_ty), + field.declared_ty, ) }); @@ -2905,7 +2901,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(Type::Never), ], ), - Some(Type::none(db)), + Type::none(db), )), CallableTypeKind::FunctionLike, ))); @@ -2925,7 +2921,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(key_type), ], ), - Some(Type::none(db)), + Type::none(db), ) }); @@ -2959,11 +2955,11 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(key_type), ], ), - Some(if field.is_required() { + if field.is_required() { field.declared_ty } else { UnionType::from_elements(db, [field.declared_ty, Type::none(db)]) - }), + }, ); let t_default = BoundTypeVarInstance::synthetic( @@ -2985,14 +2981,14 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(Type::TypeVar(t_default)), ], ), - Some(if field.is_required() { + if field.is_required() { field.declared_ty } else { UnionType::from_elements( db, [field.declared_ty, Type::TypeVar(t_default)], ) - }), + }, ); [get_sig, get_with_default_sig] @@ -3009,10 +3005,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(KnownClass::Str.to_instance(db)), ], ), - Some(UnionType::from_elements( - db, - [Type::unknown(), Type::none(db)], - )), + UnionType::from_elements(db, [Type::unknown(), Type::none(db)]), ) })) .chain(std::iter::once({ @@ -3035,10 +3028,10 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(Type::TypeVar(t_default)), ], ), - Some(UnionType::from_elements( + UnionType::from_elements( db, [Type::unknown(), Type::TypeVar(t_default)], - )), + ), ) })); @@ -3072,7 +3065,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(key_type), ], ), - Some(field.declared_ty), + field.declared_ty, ); // `.pop()` with a default value @@ -3095,10 +3088,10 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(Type::TypeVar(t_default)), ], ), - Some(UnionType::from_elements( + UnionType::from_elements( db, [field.declared_ty, Type::TypeVar(t_default)], - )), + ), ); [pop_sig, pop_with_default_sig] @@ -3128,7 +3121,7 @@ impl<'db> ClassLiteral<'db> { .with_annotated_type(field.declared_ty), ], ), - Some(field.declared_ty), + field.declared_ty, ) }); @@ -3150,7 +3143,7 @@ impl<'db> ClassLiteral<'db> { Parameter::keyword_variadic(Name::new_static("kwargs")), ], ), - Some(Type::none(db)), + Type::none(db), ); Some(Type::function_like_callable(db, signature)) diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 5195ec1cdb..693f27dcc7 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -1680,7 +1680,7 @@ impl<'db> Signature<'db> { pub(crate) struct DisplaySignature<'a, 'db> { definition: Option>, parameters: &'a Parameters<'db>, - return_ty: Option>, + return_ty: Type<'db>, db: &'db dyn Db, settings: DisplaySettings<'db>, } @@ -1727,9 +1727,8 @@ impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> { .fmt_detailed(&mut f)?; // Return type - let return_ty = self.return_ty.unwrap_or_else(Type::unknown); f.write_str(" -> ")?; - return_ty + self.return_ty .display_with(self.db, self.settings.singleline()) .fmt_detailed(&mut f)?; @@ -1897,17 +1896,16 @@ impl<'db> FmtDetailed<'db> for DisplayParameter<'_, 'db> { fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if let Some(name) = self.param.display_name() { f.write_str(&name)?; - if let Some(annotated_type) = self.param.annotated_type() { - if self.param.should_annotation_be_displayed() { - f.write_str(": ")?; - annotated_type - .display_with(self.db, self.settings.clone()) - .fmt_detailed(f)?; - } + if self.param.should_annotation_be_displayed() { + let annotated_type = self.param.annotated_type(); + f.write_str(": ")?; + annotated_type + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; } // Default value can only be specified if `name` is given. if let Some(default_type) = self.param.default_type() { - if self.param.annotated_type().is_some() { + if self.param.should_annotation_be_displayed() { f.write_str(" = ")?; } else { f.write_str("=")?; @@ -1940,10 +1938,13 @@ impl<'db> FmtDetailed<'db> for DisplayParameter<'_, 'db> { _ => f.write_str("...")?, } } - } else if let Some(ty) = self.param.annotated_type() { + } else { // This case is specifically for the `Callable` signature where name and default value - // cannot be provided. - ty.display_with(self.db, self.settings.clone()) + // cannot be provided. For unnamed parameters we always display the type, to ensure we + // have something visible in the parameter slot. + self.param + .annotated_type() + .display_with(self.db, self.settings.clone()) .fmt_detailed(f)?; } Ok(()) @@ -2646,9 +2647,12 @@ mod tests { parameters: impl IntoIterator>, return_ty: Option>, ) -> String { - Signature::new(Parameters::new(db, parameters), return_ty) - .display(db) - .to_string() + Signature::new( + Parameters::new(db, parameters), + return_ty.unwrap_or(Type::unknown()), + ) + .display(db) + .to_string() } fn display_signature_multiline<'db>( @@ -2656,9 +2660,12 @@ mod tests { parameters: impl IntoIterator>, return_ty: Option>, ) -> String { - Signature::new(Parameters::new(db, parameters), return_ty) - .display_with(db, super::DisplaySettings::default().multiline()) - .to_string() + Signature::new( + Parameters::new(db, parameters), + return_ty.unwrap_or(Type::unknown()), + ) + .display_with(db, super::DisplaySettings::default().multiline()) + .to_string() } #[test] diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 270ab6fa3c..e813ad3b0b 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -416,21 +416,19 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, definition: Definition<'db>, parameters: &Parameters<'db>, - return_type: Option>, + return_type: Type<'db>, ) -> Option { // Find all of the legacy typevars mentioned in the function signature. let mut variables = FxOrderSet::default(); for param in parameters { - if let Some(ty) = param.annotated_type() { - ty.find_legacy_typevars(db, Some(definition), &mut variables); - } + param + .annotated_type() + .find_legacy_typevars(db, Some(definition), &mut variables); if let Some(ty) = param.default_type() { ty.find_legacy_typevars(db, Some(definition), &mut variables); } } - if let Some(ty) = return_type { - ty.find_legacy_typevars(db, Some(definition), &mut variables); - } + return_type.find_legacy_typevars(db, Some(definition), &mut variables); if variables.is_empty() { return None; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index daf00cb2b5..c3cd69cb37 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -456,8 +456,8 @@ pub struct CallSignatureDetails<'db> { /// Parameter kinds, useful to determine correct autocomplete suggestions. pub parameter_kinds: Vec>, - /// Parameter kinds, useful to determine correct autocomplete suggestions. - pub parameter_types: Vec>>, + /// Annotated types of parameters. If no annotation was provided, this is `Unknown`. + pub parameter_types: Vec>, /// The definition where this callable was originally defined (useful for /// extracting docstrings). @@ -521,12 +521,11 @@ pub fn call_signature_details<'db>( let display_details = signature.display(model.db()).to_string_parts(); let parameter_label_offsets = display_details.parameter_ranges; let parameter_names = display_details.parameter_names; - let (parameter_kinds, parameter_types): (Vec, Vec>) = - signature - .parameters() - .iter() - .map(|param| (param.kind().clone(), param.annotated_type())) - .unzip(); + let (parameter_kinds, parameter_types): (Vec, Vec) = signature + .parameters() + .iter() + .map(|param| (param.kind().clone(), param.annotated_type())) + .unzip(); CallSignatureDetails { definition: signature.definition(), diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 5a1d41eeb3..3a6e36914f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -7068,7 +7068,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // the expected type passed should be the "raw" type, // i.e. type variables in the return type are non-inferable, // and the return types of async functions are not wrapped in `CoroutineType[...]`. - TypeContext::new(func.last_definition_raw_signature(self.db()).return_ty) + TypeContext::new(Some( + func.last_definition_raw_signature(self.db()).return_ty, + )) }) .unwrap_or_default() } else { @@ -7483,18 +7485,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; let mut parameter_type = - overload.signature.parameters()[*parameter_index].annotated_type()?; + overload.signature.parameters()[*parameter_index].annotated_type(); // If this is a generic call, attempt to specialize the parameter type using the // declared type context, if provided. if let Some(generic_context) = overload.signature.generic_context - && let Some(return_ty) = overload.signature.return_ty && let Some(declared_return_ty) = call_expression_tcx.annotation { let mut builder = SpecializationBuilder::new(db, generic_context.inferable_typevars(db)); - let _ = builder.infer(return_ty, declared_return_ty); + let _ = builder.infer(overload.signature.return_ty, declared_return_ty); let specialization = builder.build(generic_context); parameter_type = parameter_type.apply_specialization(db, specialization); @@ -8771,7 +8772,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // TODO: Useful inference of a lambda's return type will require a different approach, // which does the inference of the body expression based on arguments at each call site, // rather than eagerly computing a return type without knowing the argument types. - Type::function_like_callable(self.db(), Signature::new(parameters, Some(Type::unknown()))) + Type::function_like_callable(self.db(), Signature::new(parameters, Type::unknown())) } fn infer_call_expression( diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 933b2cbe56..e56cf0feea 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -1176,7 +1176,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let callable_type = if let (Some(parameters), Some(return_type), true) = (parameters, return_type, correct_argument_number) { - Type::single_callable(db, Signature::new(parameters, Some(return_type))) + Type::single_callable(db, Signature::new(parameters, return_type)) } else { Type::Callable(CallableType::unknown(db)) }; diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 6bfb229c53..4e789bb94c 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -59,7 +59,7 @@ pub(crate) enum Ty { }, Callable { params: CallableParams, - returns: Option>, + returns: Box, }, /// `unittest.mock.Mock` is interesting because it is a nominal instance type /// where the class has `Any` in its MRO @@ -91,9 +91,7 @@ impl CallableParams { Parameter::keyword_variadic(param.name.unwrap()) } }; - if let Some(annotated_ty) = param.annotated_ty { - parameter = parameter.with_annotated_type(annotated_ty.into_type(db)); - } + parameter = parameter.with_annotated_type(param.annotated_ty.into_type(db)); if let Some(default_ty) = param.default_ty { parameter = parameter.with_default_type(default_ty.into_type(db)); } @@ -108,7 +106,7 @@ impl CallableParams { pub(crate) struct Param { kind: ParamKind, name: Option, - annotated_ty: Option, + annotated_ty: Ty, default_ty: Option, } @@ -237,10 +235,7 @@ impl Ty { } Ty::Callable { params, returns } => Type::single_callable( db, - Signature::new( - params.into_parameters(db), - returns.map(|ty| ty.into_type(db)), - ), + Signature::new(params.into_parameters(db), returns.into_type(db)), ), } } @@ -374,7 +369,7 @@ fn arbitrary_type(g: &mut Gen, size: u32, fully_static: bool) -> Ty { 0 if !fully_static => CallableParams::GradualForm, _ => CallableParams::List(arbitrary_parameter_list(g, size, fully_static)), }, - returns: arbitrary_annotation(g, size - 1, fully_static).map(Box::new), + returns: Box::new(arbitrary_type(g, size - 1, fully_static)), }, _ => unreachable!(), } @@ -433,7 +428,7 @@ fn arbitrary_parameter_list(g: &mut Gen, size: u32, fully_static: bool) -> Vec

Vec

Option { - if fully_static { - Some(arbitrary_type(g, size, true)) - } else { - arbitrary_optional_type(g, size, false) - } -} - fn arbitrary_optional_type(g: &mut Gen, size: u32, fully_static: bool) -> Option { match u32::arbitrary(g) % 2 { 0 => None, diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 453ddd071b..3262769459 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -217,7 +217,7 @@ impl<'db> ProtocolInterface<'db> { db, [Parameter::positional_only(Some(Name::new_static("self")))], ), - Some(ty.normalized(db)), + ty.normalized(db), ); let property_getter = Type::single_callable(db, property_getter_signature); let property = PropertyInstanceType::new(db, Some(property_getter), None); diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index a58bf764c5..6d982c0cf6 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -18,9 +18,7 @@ use smallvec::{SmallVec, smallvec_inline}; use super::{DynamicType, Type, TypeVarVariance, definition_expression_type, semantic_index}; use crate::semantic_index::definition::Definition; -use crate::types::constraints::{ - ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension, -}; +use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_context}; use crate::types::infer::{infer_deferred_types, infer_scope_types}; use crate::types::relation::{ @@ -177,9 +175,12 @@ impl<'db> CallableSignature<'db> { )), ]), ), - return_ty: self_signature - .return_ty - .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)), + return_ty: self_signature.return_ty.apply_type_mapping_impl( + db, + type_mapping, + tcx, + visitor, + ), })) } Type::Callable(callable) @@ -209,9 +210,12 @@ impl<'db> CallableSignature<'db> { .chain(signature.parameters().iter().cloned()), ) }, - return_ty: self_signature.return_ty.map(|ty| { - ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor) - }), + return_ty: self_signature.return_ty.apply_type_mapping_impl( + db, + type_mapping, + tcx, + visitor, + ), }), )) } @@ -327,9 +331,7 @@ impl<'db> CallableSignature<'db> { ) } - pub(crate) fn is_single_paramspec( - &self, - ) -> Option<(BoundTypeVarInstance<'db>, Option>)> { + pub(crate) fn is_single_paramspec(&self) -> Option<(BoundTypeVarInstance<'db>, Type<'db>)> { Self::signatures_is_single_paramspec(&self.overloads) } @@ -338,7 +340,7 @@ impl<'db> CallableSignature<'db> { /// along with the return type of the signature. fn signatures_is_single_paramspec( signatures: &[Signature<'db>], - ) -> Option<(BoundTypeVarInstance<'db>, Option>)> { + ) -> Option<(BoundTypeVarInstance<'db>, Type<'db>)> { // TODO: This might need updating once we support `Concatenate` let [signature] = signatures else { return None; @@ -396,8 +398,37 @@ impl<'db> CallableSignature<'db> { Type::TypeVar(other_bound_typevar), Type::TypeVar(other_bound_typevar), ); - let return_types_match = self_return_type.zip(other_return_type).when_some_and( - |(self_return_type, other_return_type)| { + let return_types_match = self_return_type.has_relation_to_impl( + db, + other_return_type, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ); + return param_spec_matches.and(db, || return_types_match); + } + + (Some((self_bound_typevar, self_return_type)), None) => { + let upper = Type::Callable(CallableType::new( + db, + CallableSignature::from_overloads(other_signatures.iter().map( + |signature| { + Signature::new(signature.parameters().clone(), Type::unknown()) + }, + )), + CallableTypeKind::ParamSpecValue, + )); + let param_spec_matches = ConstraintSet::constrain_typevar( + db, + self_bound_typevar, + Type::Never, + upper, + ); + let return_types_match = other_signatures + .iter() + .map(|signature| signature.return_ty) + .when_any(db, |other_return_type| { self_return_type.has_relation_to_impl( db, other_return_type, @@ -406,74 +437,39 @@ impl<'db> CallableSignature<'db> { relation_visitor, disjointness_visitor, ) - }, - ); - return param_spec_matches.and(db, || return_types_match); - } - - (Some((self_bound_typevar, self_return_type)), None) => { - let upper = - Type::Callable(CallableType::new( - db, - CallableSignature::from_overloads(other_signatures.iter().map( - |signature| Signature::new(signature.parameters().clone(), None), - )), - CallableTypeKind::ParamSpecValue, - )); - let param_spec_matches = ConstraintSet::constrain_typevar( - db, - self_bound_typevar, - Type::Never, - upper, - ); - let return_types_match = self_return_type.when_some_and(|self_return_type| { - other_signatures - .iter() - .filter_map(|signature| signature.return_ty) - .when_any(db, |other_return_type| { - self_return_type.has_relation_to_impl( - db, - other_return_type, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) - }); + }); return param_spec_matches.and(db, || return_types_match); } (None, Some((other_bound_typevar, other_return_type))) => { - let lower = - Type::Callable(CallableType::new( - db, - CallableSignature::from_overloads(self_signatures.iter().map( - |signature| Signature::new(signature.parameters().clone(), None), - )), - CallableTypeKind::ParamSpecValue, - )); + let lower = Type::Callable(CallableType::new( + db, + CallableSignature::from_overloads(self_signatures.iter().map( + |signature| { + Signature::new(signature.parameters().clone(), Type::unknown()) + }, + )), + CallableTypeKind::ParamSpecValue, + )); let param_spec_matches = ConstraintSet::constrain_typevar( db, other_bound_typevar, lower, Type::object(), ); - let return_types_match = other_return_type.when_some_and(|other_return_type| { - self_signatures - .iter() - .filter_map(|signature| signature.return_ty) - .when_any(db, |self_return_type| { - self_return_type.has_relation_to_impl( - db, - other_return_type, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) - }); + let return_types_match = self_signatures + .iter() + .map(|signature| signature.return_ty) + .when_any(db, |self_return_type| { + self_return_type.has_relation_to_impl( + db, + other_return_type, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }); return param_spec_matches.and(db, || return_types_match); } @@ -601,8 +597,8 @@ pub struct Signature<'db> { /// We may get invalid signatures, though, and need to handle them without panicking. parameters: Parameters<'db>, - /// Annotated return type, if any. - pub(crate) return_ty: Option>, + /// Return type. If no annotation was provided, this is `Unknown`. + pub(crate) return_ty: Type<'db>, } pub(super) fn walk_signature<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( @@ -616,17 +612,13 @@ pub(super) fn walk_signature<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( // By default we usually don't visit the type of the default value, // as it isn't relevant to most things for parameter in &signature.parameters { - if let Some(ty) = parameter.annotated_type() { - visitor.visit_type(db, ty); - } - } - if let Some(return_ty) = &signature.return_ty { - visitor.visit_type(db, *return_ty); + visitor.visit_type(db, parameter.annotated_type()); } + visitor.visit_type(db, signature.return_ty); } impl<'db> Signature<'db> { - pub(crate) fn new(parameters: Parameters<'db>, return_ty: Option>) -> Self { + pub(crate) fn new(parameters: Parameters<'db>, return_ty: Type<'db>) -> Self { Self { generic_context: None, definition: None, @@ -638,7 +630,7 @@ impl<'db> Signature<'db> { pub(crate) fn new_generic( generic_context: Option>, parameters: Parameters<'db>, - return_ty: Option>, + return_ty: Type<'db>, ) -> Self { Self { generic_context, @@ -654,7 +646,7 @@ impl<'db> Signature<'db> { generic_context: None, definition: None, parameters: Parameters::gradual_form(), - return_ty: Some(signature_type), + return_ty: signature_type, } } @@ -666,7 +658,7 @@ impl<'db> Signature<'db> { generic_context: None, definition: None, parameters: Parameters::todo(), - return_ty: Some(signature_type), + return_ty: signature_type, } } @@ -687,7 +679,8 @@ impl<'db> Signature<'db> { let return_ty = function_node .returns .as_ref() - .map(|returns| function_signature_expression_type(db, definition, returns.as_ref())); + .map(|returns| function_signature_expression_type(db, definition, returns.as_ref())) + .unwrap_or_else(Type::unknown); let legacy_generic_context = GenericContext::from_function_params(db, definition, ¶meters, return_ty); let full_generic_context = GenericContext::merge_pep695_and_legacy( @@ -705,21 +698,19 @@ impl<'db> Signature<'db> { } pub(super) fn wrap_coroutine_return_type(self, db: &'db dyn Db) -> Self { - let return_ty = self.return_ty.map(|return_ty| { - KnownClass::CoroutineType - .to_specialized_instance(db, [Type::any(), Type::any(), return_ty]) - }); + let return_ty = KnownClass::CoroutineType + .to_specialized_instance(db, [Type::any(), Type::any(), self.return_ty]); Self { return_ty, ..self } } /// Returns the signature which accepts any parameters and returns an `Unknown` type. pub(crate) fn unknown() -> Self { - Self::new(Parameters::unknown(), Some(Type::unknown())) + Self::new(Parameters::unknown(), Type::unknown()) } /// Return the "bottom" signature, subtype of all other fully-static signatures. pub(crate) fn bottom() -> Self { - Self::new(Parameters::bottom(), Some(Type::Never)) + Self::new(Parameters::bottom(), Type::Never) } pub(crate) fn with_inherited_generic_context( @@ -756,9 +747,7 @@ impl<'db> Signature<'db> { .iter() .map(|param| param.normalized_impl(db, visitor)), ), - return_ty: self - .return_ty - .map(|return_ty| return_ty.normalized_impl(db, visitor)), + return_ty: self.return_ty.normalized_impl(db, visitor), } } @@ -768,16 +757,13 @@ impl<'db> Signature<'db> { div: Type<'db>, nested: bool, ) -> Option { - let return_ty = match self.return_ty { - Some(return_ty) if nested => { - Some(return_ty.recursive_type_normalized_impl(db, div, true)?) - } - Some(return_ty) => Some( - return_ty - .recursive_type_normalized_impl(db, div, true) - .unwrap_or(div), - ), - None => None, + let return_ty = if nested { + self.return_ty + .recursive_type_normalized_impl(db, div, true)? + } else { + self.return_ty + .recursive_type_normalized_impl(db, div, true) + .unwrap_or(div) }; let parameters = { let mut parameters = Vec::with_capacity(self.parameters.len()); @@ -811,7 +797,7 @@ impl<'db> Signature<'db> { .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)), + .apply_type_mapping_impl(db, type_mapping, tcx, visitor), } } @@ -823,16 +809,18 @@ impl<'db> Signature<'db> { visitor: &FindLegacyTypeVarsVisitor<'db>, ) { for param in &self.parameters { - if let Some(ty) = param.annotated_type() { - ty.find_legacy_typevars_impl(db, binding_context, typevars, visitor); - } + param.annotated_type().find_legacy_typevars_impl( + db, + binding_context, + typevars, + visitor, + ); if let Some(ty) = param.default_type() { ty.find_legacy_typevars_impl(db, binding_context, typevars, visitor); } } - if let Some(ty) = self.return_ty { - ty.find_legacy_typevars_impl(db, binding_context, typevars, visitor); - } + self.return_ty + .find_legacy_typevars_impl(db, binding_context, typevars, visitor); } /// Return the parameters in this signature. @@ -852,11 +840,11 @@ impl<'db> Signature<'db> { ) { if let Some(first_parameter) = self.parameters.value.first_mut() && first_parameter.is_positional() - && first_parameter.annotated_type.is_none() + && first_parameter.annotated_type.is_unknown() + && first_parameter.inferred_annotation && let Some(self_type) = self_type() { - first_parameter.annotated_type = Some(self_type); - first_parameter.inferred_annotation = true; + first_parameter.annotated_type = self_type; // If we've added an implicit `self` annotation, we might need to update the // signature's generic context, too. (The generic context should include any synthetic @@ -919,8 +907,7 @@ impl<'db> Signature<'db> { TypeContext::default(), &ApplyTypeMappingVisitor::default(), ); - return_ty = return_ty - .map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default())); + return_ty = return_ty.apply_type_mapping(db, &self_mapping, TypeContext::default()); } Self { generic_context: self @@ -943,9 +930,9 @@ impl<'db> Signature<'db> { TypeContext::default(), &ApplyTypeMappingVisitor::default(), ); - let return_ty = self - .return_ty - .map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default())); + let return_ty = + self.return_ty + .apply_type_mapping(db, &self_mapping, TypeContext::default()); Self { generic_context: self.generic_context, definition: self.definition, @@ -1006,16 +993,6 @@ impl<'db> Signature<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { let mut result = ConstraintSet::from(true); - let mut check_types = |self_type: Option>, other_type: Option>| { - let self_type = self_type.unwrap_or(Type::unknown()); - let other_type = other_type.unwrap_or(Type::unknown()); - !result - .intersect( - db, - self_type.is_equivalent_to_impl(db, other_type, inferable, visitor), - ) - .is_never_satisfied(db) - }; if self.parameters.is_gradual() != other.parameters.is_gradual() { return ConstraintSet::from(false); @@ -1025,6 +1002,15 @@ impl<'db> Signature<'db> { return ConstraintSet::from(false); } + let mut check_types = |self_type: Type<'db>, other_type: Type<'db>| { + !result + .intersect( + db, + self_type.is_equivalent_to_impl(db, other_type, inferable, visitor), + ) + .is_never_satisfied(db) + }; + if !check_types(self.return_ty, other.return_ty) { return result; } @@ -1096,29 +1082,24 @@ impl<'db> Signature<'db> { { let upper = Type::Callable(CallableType::new( db, - CallableSignature::from_overloads( - other - .overloads - .iter() - .map(|signature| Signature::new(signature.parameters().clone(), None)), - ), + CallableSignature::from_overloads(other.overloads.iter().map(|signature| { + Signature::new(signature.parameters().clone(), Type::unknown()) + })), CallableTypeKind::ParamSpecValue, )); let param_spec_matches = ConstraintSet::constrain_typevar(db, self_bound_typevar, Type::Never, upper); - let return_types_match = self.return_ty.when_some_and(|self_return_type| { - other - .overloads - .iter() - .filter_map(|signature| signature.return_ty) - .when_any(db, |other_return_type| { - self_return_type.when_constraint_set_assignable_to( - db, - other_return_type, - inferable, - ) - }) - }); + let return_types_match = other + .overloads + .iter() + .map(|signature| signature.return_ty) + .when_any(db, |other_return_type| { + self.return_ty.when_constraint_set_assignable_to( + db, + other_return_type, + inferable, + ) + }); return param_spec_matches.and(db, || return_types_match); } @@ -1258,10 +1239,8 @@ impl<'db> Signature<'db> { } let mut result = ConstraintSet::from(true); - let mut check_types = |type1: Option>, type2: Option>| { - let type1 = type1.unwrap_or(Type::unknown()); - let type2 = type2.unwrap_or(Type::unknown()); + let mut check_types = |type1: Type<'db>, type2: Type<'db>| { match (type1, type2) { // This is a special case where the _same_ components of two different `ParamSpec` // type variables are assignable to each other when they're both in an inferable @@ -1310,11 +1289,11 @@ impl<'db> Signature<'db> { && self .parameters .variadic() - .is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) + .is_some_and(|(_, param)| param.annotated_type().is_object()) && self .parameters .keyword_variadic() - .is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) + .is_some_and(|(_, param)| param.annotated_type().is_object()) { return ConstraintSet::from(true); } @@ -1360,7 +1339,10 @@ impl<'db> Signature<'db> { (Some(self_bound_typevar), None) => { let upper = Type::Callable(CallableType::new( db, - CallableSignature::single(Signature::new(other.parameters.clone(), None)), + CallableSignature::single(Signature::new( + other.parameters.clone(), + Type::unknown(), + )), CallableTypeKind::ParamSpecValue, )); let param_spec_matches = ConstraintSet::constrain_typevar( @@ -1376,7 +1358,10 @@ impl<'db> Signature<'db> { (None, Some(other_bound_typevar)) => { let lower = Type::Callable(CallableType::new( db, - CallableSignature::single(Signature::new(self.parameters.clone(), None)), + CallableSignature::single(Signature::new( + self.parameters.clone(), + Type::unknown(), + )), CallableTypeKind::ParamSpecValue, )); let param_spec_matches = ConstraintSet::constrain_typevar( @@ -1585,10 +1570,9 @@ impl<'db> Signature<'db> { // Type of the variadic keyword parameter in `self`. // - // This is a nested option where the outer option represents the presence of a keyword - // variadic parameter in `self` and the inner option represents the annotated type of the - // keyword variadic parameter. - let mut self_keyword_variadic: Option>> = None; + // This is an option representing the presence (and annotated type) of a keyword variadic + // parameter in `self`. + let mut self_keyword_variadic: Option> = None; for self_parameter in self_parameters { match self_parameter.kind() { @@ -1700,12 +1684,14 @@ impl<'db> VarianceInferable<'db> for &Signature<'db> { .iter() .filter_map(|parameter| match parameter.form { ParameterForm::Type => None, - ParameterForm::Value => parameter.annotated_type().map(|ty| { - ty.with_polarity(TypeVarVariance::Contravariant) - .variance_of(db, typevar) - }), + ParameterForm::Value => Some( + parameter + .annotated_type() + .with_polarity(TypeVarVariance::Contravariant) + .variance_of(db, typevar), + ), }), - self.return_ty.map(|ty| ty.variance_of(db, typevar)), + Some(self.return_ty.variance_of(db, typevar)), ) .collect() } @@ -1773,10 +1759,10 @@ impl<'db> Parameters<'db> { && p2.is_keyword_variadic() { match (p1.annotated_type(), p2.annotated_type()) { - (None | Some(Type::Dynamic(_)), None | Some(Type::Dynamic(_))) => { + (Type::Dynamic(_), Type::Dynamic(_)) => { kind = ParametersKind::Gradual; } - (Some(Type::TypeVar(args_typevar)), Some(Type::TypeVar(kwargs_typevar))) => { + (Type::TypeVar(args_typevar), Type::TypeVar(kwargs_typevar)) => { if let (Some(ParamSpecAttrKind::Args), Some(ParamSpecAttrKind::Kwargs)) = ( args_typevar.paramspec_attr(db), kwargs_typevar.paramspec_attr(db), @@ -1938,7 +1924,7 @@ impl<'db> Parameters<'db> { } let (Type::TypeVar(args_typevar), Type::TypeVar(kwargs_typevar)) = - (maybe_args.annotated_type()?, maybe_kwargs.annotated_type()?) + (maybe_args.annotated_type(), maybe_kwargs.annotated_type()) else { return None; }; @@ -2195,11 +2181,14 @@ impl<'db> std::ops::Index for Parameters<'db> { #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)] pub(crate) struct Parameter<'db> { - /// Annotated type of the parameter. - annotated_type: Option>, + /// Annotated type of the parameter. If no annotation was provided, this is `Unknown`. + annotated_type: Type<'db>, /// Does the type of this parameter come from an explicit annotation, or was it inferred from - /// the context, like `Self` for the `self` parameter of instance methods. + /// the context, like `Unknown` for any normal un-annotated parameter, `Self` for the `self` + /// parameter of instance method, or `type[Self]` for `cls` parameter of classmethods. This + /// field is only used to decide whether to display the annotated type; it has no effect on the + /// type semantics of the parameter. pub(crate) inferred_annotation: bool, kind: ParameterKind<'db>, @@ -2209,8 +2198,8 @@ pub(crate) struct Parameter<'db> { impl<'db> Parameter<'db> { pub(crate) fn positional_only(name: Option) -> Self { Self { - annotated_type: None, - inferred_annotation: false, + annotated_type: Type::unknown(), + inferred_annotation: true, kind: ParameterKind::PositionalOnly { name, default_type: None, @@ -2221,8 +2210,8 @@ impl<'db> Parameter<'db> { pub(crate) fn positional_or_keyword(name: Name) -> Self { Self { - annotated_type: None, - inferred_annotation: false, + annotated_type: Type::unknown(), + inferred_annotation: true, kind: ParameterKind::PositionalOrKeyword { name, default_type: None, @@ -2233,8 +2222,8 @@ impl<'db> Parameter<'db> { pub(crate) fn variadic(name: Name) -> Self { Self { - annotated_type: None, - inferred_annotation: false, + annotated_type: Type::unknown(), + inferred_annotation: true, kind: ParameterKind::Variadic { name }, form: ParameterForm::Value, } @@ -2242,8 +2231,8 @@ impl<'db> Parameter<'db> { pub(crate) fn keyword_only(name: Name) -> Self { Self { - annotated_type: None, - inferred_annotation: false, + annotated_type: Type::unknown(), + inferred_annotation: true, kind: ParameterKind::KeywordOnly { name, default_type: None, @@ -2254,15 +2243,18 @@ impl<'db> Parameter<'db> { pub(crate) fn keyword_variadic(name: Name) -> Self { Self { - annotated_type: None, - inferred_annotation: false, + annotated_type: Type::unknown(), + inferred_annotation: true, kind: ParameterKind::KeywordVariadic { name }, form: ParameterForm::Value, } } + /// Set the annotated type for this parameter. This also marks the annotation as explicit + /// (not inferred), so it will be displayed. pub(crate) fn with_annotated_type(mut self, annotated_type: Type<'db>) -> Self { - self.annotated_type = Some(annotated_type); + self.annotated_type = annotated_type; + self.inferred_annotation = false; self } @@ -2291,9 +2283,12 @@ impl<'db> Parameter<'db> { visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { Self { - annotated_type: self - .annotated_type - .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)), + annotated_type: self.annotated_type.apply_type_mapping_impl( + db, + type_mapping, + tcx, + visitor, + ), kind: self .kind .apply_type_mapping_impl(db, type_mapping, tcx, visitor), @@ -2303,7 +2298,7 @@ impl<'db> Parameter<'db> { } /// Strip information from the parameter so that two equivalent parameters compare equal. - /// Normalize nested unions and intersections in the annotated type, if any. + /// Normalize nested unions and intersections in the annotated type. /// /// See [`Type::normalized`] for more details. pub(crate) fn normalized_impl( @@ -2313,18 +2308,14 @@ impl<'db> Parameter<'db> { ) -> Self { let Parameter { annotated_type, - inferred_annotation, kind, form, + .. } = self; - // Ensure unions and intersections are ordered in the annotated type (if there is one). - // Ensure that a parameter without an annotation is treated equivalently to a parameter - // with a dynamic type as its annotation. (We must use `Any` here as all dynamic types - // normalize to `Any`.) - let annotated_type = annotated_type - .map(|ty| ty.normalized_impl(db, visitor)) - .unwrap_or_else(Type::any); + // Ensure unions and intersections are ordered in the annotated type. + // Unknown normalizes to Any. + let annotated_type = annotated_type.normalized_impl(db, visitor); // Ensure that parameter names are stripped from positional-only, variadic and keyword-variadic parameters. // Ensure that we only record whether a parameter *has* a default @@ -2356,8 +2347,10 @@ impl<'db> Parameter<'db> { }; Self { - annotated_type: Some(annotated_type), - inferred_annotation: *inferred_annotation, + annotated_type, + // Normalize `inferred_annotation` to `false` since it's a display-only field + // that doesn't affect type semantics. + inferred_annotation: false, kind, form: *form, } @@ -2376,13 +2369,12 @@ impl<'db> Parameter<'db> { form, } = self; - let annotated_type = match annotated_type { - Some(ty) if nested => Some(ty.recursive_type_normalized_impl(db, div, true)?), - Some(ty) => Some( - ty.recursive_type_normalized_impl(db, div, true) - .unwrap_or(div), - ), - None => None, + let annotated_type = if nested { + annotated_type.recursive_type_normalized_impl(db, div, true)? + } else { + annotated_type + .recursive_type_normalized_impl(db, div, true) + .unwrap_or(div) }; let kind = match kind { @@ -2443,13 +2435,20 @@ impl<'db> Parameter<'db> { parameter: &ast::Parameter, kind: ParameterKind<'db>, ) -> Self { + let (annotated_type, inferred_annotation) = if let Some(annotation) = parameter.annotation() + { + ( + function_signature_expression_type(db, definition, annotation), + false, + ) + } else { + (Type::unknown(), true) + }; Self { - annotated_type: parameter - .annotation() - .map(|annotation| function_signature_expression_type(db, definition, annotation)), + annotated_type, kind, form: ParameterForm::Value, - inferred_annotation: false, + inferred_annotation, } } @@ -2494,8 +2493,8 @@ impl<'db> Parameter<'db> { } } - /// Annotated type of the parameter, if annotated. - pub(crate) fn annotated_type(&self) -> Option> { + /// Annotated type of the parameter. If no annotation was provided, this is `Unknown`. + pub(crate) fn annotated_type(&self) -> Type<'db> { self.annotated_type } @@ -2654,7 +2653,7 @@ mod tests { let sig = func.signature(&db); - assert!(sig.return_ty.is_none()); + assert!(sig.return_ty.is_unknown()); assert_params(&sig, &[]); } @@ -2679,7 +2678,7 @@ mod tests { let sig = func.signature(&db); - assert_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes"); + assert_eq!(sig.return_ty.display(&db).to_string(), "bytes"); assert_params( &sig, &[ @@ -2743,7 +2742,7 @@ mod tests { }; assert_eq!(name, "a"); // Parameter resolution not deferred; we should see A not B - assert_eq!(annotated_type.unwrap().display(&db).to_string(), "A"); + assert_eq!(annotated_type.display(&db).to_string(), "A"); } #[test] @@ -2781,7 +2780,7 @@ mod tests { }; assert_eq!(name, "a"); // Parameter resolution deferred: - assert_eq!(annotated_type.unwrap().display(&db).to_string(), "A | B"); + assert_eq!(annotated_type.display(&db).to_string(), "A | B"); } #[test] @@ -2824,8 +2823,8 @@ mod tests { }; assert_eq!(a_name, "a"); assert_eq!(b_name, "b"); - assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A"); - assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f"); + assert_eq!(a_annotated_ty.display(&db).to_string(), "A"); + assert_eq!(b_annotated_ty.display(&db).to_string(), "T@f"); } #[test] @@ -2869,8 +2868,8 @@ mod tests { assert_eq!(a_name, "a"); assert_eq!(b_name, "b"); // Parameter resolution deferred: - assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A | B"); - assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f"); + assert_eq!(a_annotated_ty.display(&db).to_string(), "A | B"); + assert_eq!(b_annotated_ty.display(&db).to_string(), "T@f"); } #[test]