[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.
This commit is contained in:
Carl Meyer
2026-01-07 09:18:39 -08:00
committed by GitHub
parent 3ad99fb1f4
commit 30902497db
18 changed files with 472 additions and 452 deletions

View File

@@ -420,7 +420,7 @@ impl<'db> Completion<'db> {
}
}
fn argument(name: &str, ty: Option<Type<'db>>, 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));
}
}

View File

@@ -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<Type<'db>>,
/// 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<String>,
@@ -237,7 +237,7 @@ fn create_parameters_from_offsets<'db>(
docstring: Option<&Docstring>,
parameter_names: &[String],
parameter_kinds: &[ParameterKind],
parameter_types: &[Option<Type<'db>>],
parameter_types: &[Type<'db>],
) -> Vec<ParameterDetails<'db>> {
// 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,
}

View File

@@ -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]
```

View File

@@ -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
```

View File

@@ -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 <https://github.com/astral-sh/ty/issues/2363>, 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_

View File

@@ -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))

View File

@@ -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,

View File

@@ -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(),
)))
}
}

View File

@@ -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 &parameter_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 = &parameters[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 = &parameters[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,

View File

@@ -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<Type<'db>>| {
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))

View File

@@ -1680,7 +1680,7 @@ impl<'db> Signature<'db> {
pub(crate) struct DisplaySignature<'a, 'db> {
definition: Option<Definition<'db>>,
parameters: &'a Parameters<'db>,
return_ty: Option<Type<'db>>,
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<Item = Parameter<'db>>,
return_ty: Option<Type<'db>>,
) -> 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<Item = Parameter<'db>>,
return_ty: Option<Type<'db>>,
) -> 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]

View File

@@ -416,21 +416,19 @@ impl<'db> GenericContext<'db> {
db: &'db dyn Db,
definition: Definition<'db>,
parameters: &Parameters<'db>,
return_type: Option<Type<'db>>,
return_type: Type<'db>,
) -> Option<Self> {
// 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;

View File

@@ -456,8 +456,8 @@ pub struct CallSignatureDetails<'db> {
/// Parameter kinds, useful to determine correct autocomplete suggestions.
pub parameter_kinds: Vec<ParameterKind<'db>>,
/// Parameter kinds, useful to determine correct autocomplete suggestions.
pub parameter_types: Vec<Option<Type<'db>>>,
/// Annotated types of parameters. If no annotation was provided, this is `Unknown`.
pub parameter_types: Vec<Type<'db>>,
/// 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<ParameterKind>, Vec<Option<Type>>) =
signature
.parameters()
.iter()
.map(|param| (param.kind().clone(), param.annotated_type()))
.unzip();
let (parameter_kinds, parameter_types): (Vec<ParameterKind>, Vec<Type>) = signature
.parameters()
.iter()
.map(|param| (param.kind().clone(), param.annotated_type()))
.unzip();
CallSignatureDetails {
definition: signature.definition(),

View File

@@ -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(

View File

@@ -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))
};

View File

@@ -59,7 +59,7 @@ pub(crate) enum Ty {
},
Callable {
params: CallableParams,
returns: Option<Box<Ty>>,
returns: Box<Ty>,
},
/// `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<Name>,
annotated_ty: Option<Ty>,
annotated_ty: Ty,
default_ty: Option<Ty>,
}
@@ -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<P
params.push(Param {
kind: next_kind,
name,
annotated_ty: arbitrary_annotation(g, size, fully_static),
annotated_ty: arbitrary_type(g, size, fully_static),
default_ty: if matches!(next_kind, ParamKind::Variadic | ParamKind::KeywordVariadic) {
None
} else {
@@ -445,15 +440,6 @@ fn arbitrary_parameter_list(g: &mut Gen, size: u32, fully_static: bool) -> Vec<P
params
}
/// An arbitrary optional type, always `Some` if fully static.
fn arbitrary_annotation(g: &mut Gen, size: u32, fully_static: bool) -> Option<Ty> {
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<Ty> {
match u32::arbitrary(g) % 2 {
0 => None,

View File

@@ -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);

View File

@@ -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<Type<'db>>)> {
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<Type<'db>>)> {
) -> 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<Type<'db>>,
/// 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<Type<'db>>) -> 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<GenericContext<'db>>,
parameters: Parameters<'db>,
return_ty: Option<Type<'db>>,
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, &parameters, 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<Self> {
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<Type<'db>>, other_type: Option<Type<'db>>| {
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<Type<'db>>, type2: Option<Type<'db>>| {
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<Option<Type<'db>>> = None;
// This is an option representing the presence (and annotated type) of a keyword variadic
// parameter in `self`.
let mut self_keyword_variadic: Option<Type<'db>> = 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<usize> 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<Type<'db>>,
/// 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<Name>) -> 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<Type<'db>> {
/// 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]