mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-19 12:16:43 +00:00
[ty] don't union in default type for annotated parameters (#21208)
This commit is contained in:
parent
c32234cf0d
commit
0454a72674
3 changed files with 21 additions and 40 deletions
|
|
@ -1,12 +1,9 @@
|
|||
# Function parameter types
|
||||
|
||||
Within a function scope, the declared type of each parameter is its annotated type (or Unknown if
|
||||
not annotated). The initial inferred type is the union of the declared type with the type of the
|
||||
default value expression (if any). If both are fully static types, this union should simplify to the
|
||||
annotated type (since the default value type must be assignable to the annotated type, and for fully
|
||||
static types this means subtype-of, which simplifies in unions). But if the annotated type is
|
||||
Unknown or another non-fully-static type, the default value type may still be relevant as lower
|
||||
bound.
|
||||
not annotated). The initial inferred type is the annotated type of the parameter, if any. If there
|
||||
is no annotation, it is the union of `Unknown` with the type of the default value expression (if
|
||||
any).
|
||||
|
||||
The variadic parameter is a variadic tuple of its annotated type; the variadic-keywords parameter is
|
||||
a dictionary from strings to its annotated type.
|
||||
|
|
@ -41,13 +38,13 @@ def g(*args, **kwargs):
|
|||
|
||||
## Annotation is present but not a fully static type
|
||||
|
||||
The default value type should be a lower bound on the inferred type.
|
||||
If there is an annotation, we respect it fully and don't union in the default value type.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def f(x: Any = 1):
|
||||
reveal_type(x) # revealed: Any | Literal[1]
|
||||
reveal_type(x) # revealed: Any
|
||||
```
|
||||
|
||||
## Default value type must be assignable to annotated type
|
||||
|
|
@ -64,7 +61,7 @@ def f(x: int = "foo"):
|
|||
from typing import Any
|
||||
|
||||
def g(x: Any = "foo"):
|
||||
reveal_type(x) # revealed: Any | Literal["foo"]
|
||||
reveal_type(x) # revealed: Any
|
||||
```
|
||||
|
||||
## Stub functions
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ static_assert(is_assignable_to(int, Unknown))
|
|||
def explicit_unknown(x: Unknown, y: tuple[str, Unknown], z: Unknown = 1) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
reveal_type(y) # revealed: tuple[str, Unknown]
|
||||
reveal_type(z) # revealed: Unknown | Literal[1]
|
||||
reveal_type(z) # revealed: Unknown
|
||||
```
|
||||
|
||||
`Unknown` can be subclassed, just like `Any`:
|
||||
|
|
|
|||
|
|
@ -2423,15 +2423,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
///
|
||||
/// The declared type is the annotated type, if any, or `Unknown`.
|
||||
///
|
||||
/// The inferred type is the annotated type, unioned with the type of the default value, if
|
||||
/// any. If both types are fully static, this union is a no-op (it should simplify to just the
|
||||
/// annotated type.) But in a case like `f(x=None)` with no annotated type, we want to infer
|
||||
/// the type `Unknown | None` for `x`, not just `Unknown`, so that we can error on usage of `x`
|
||||
/// that would not be valid for `None`.
|
||||
///
|
||||
/// If the default-value type is not assignable to the declared (annotated) type, we ignore the
|
||||
/// default-value type and just infer the annotated type; this is the same way we handle
|
||||
/// assignments, and allows an explicit annotation to override a bad inference.
|
||||
/// The inferred type is the annotated type, if any. If there is no annotation, it is the union
|
||||
/// of `Unknown` and the type of the default value, if any.
|
||||
///
|
||||
/// Parameter definitions are odd in that they define a symbol in the function-body scope, so
|
||||
/// the Definition belongs to the function body scope, but the expressions (annotation and
|
||||
|
|
@ -2460,23 +2453,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.map(|default| self.file_expression_type(default));
|
||||
if let Some(annotation) = parameter.annotation.as_ref() {
|
||||
let declared_ty = self.file_expression_type(annotation);
|
||||
let declared_and_inferred_ty = if let Some(default_ty) = default_ty {
|
||||
if default_ty.is_assignable_to(self.db(), declared_ty) {
|
||||
DeclaredAndInferredType::MightBeDifferent {
|
||||
declared_ty: TypeAndQualifiers::declared(declared_ty),
|
||||
inferred_ty: UnionType::from_elements(self.db(), [declared_ty, default_ty]),
|
||||
}
|
||||
} else if (self.in_stub()
|
||||
|| self.in_function_overload_or_abstractmethod()
|
||||
|| self
|
||||
.class_context_of_current_method()
|
||||
.is_some_and(|class| class.is_protocol(self.db())))
|
||||
&& default
|
||||
.as_ref()
|
||||
.is_some_and(|d| d.is_ellipsis_literal_expr())
|
||||
if let Some(default_ty) = default_ty {
|
||||
if !default_ty.is_assignable_to(self.db(), declared_ty)
|
||||
&& !((self.in_stub()
|
||||
|| self.in_function_overload_or_abstractmethod()
|
||||
|| self
|
||||
.class_context_of_current_method()
|
||||
.is_some_and(|class| class.is_protocol(self.db())))
|
||||
&& default
|
||||
.as_ref()
|
||||
.is_some_and(|d| d.is_ellipsis_literal_expr()))
|
||||
{
|
||||
DeclaredAndInferredType::are_the_same_type(declared_ty)
|
||||
} else {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_PARAMETER_DEFAULT, parameter_with_default)
|
||||
|
|
@ -2488,15 +2475,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
declared_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
DeclaredAndInferredType::are_the_same_type(declared_ty)
|
||||
}
|
||||
} else {
|
||||
DeclaredAndInferredType::are_the_same_type(declared_ty)
|
||||
};
|
||||
}
|
||||
self.add_declaration_with_binding(
|
||||
parameter.into(),
|
||||
definition,
|
||||
&declared_and_inferred_ty,
|
||||
&DeclaredAndInferredType::are_the_same_type(declared_ty),
|
||||
);
|
||||
} else {
|
||||
let ty = if let Some(default_ty) = default_ty {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue