mirror of https://github.com/astral-sh/ruff
[ty] don't union in default type for annotated parameters (#21208)
This commit is contained in:
parent
c32234cf0d
commit
0454a72674
|
|
@ -1,12 +1,9 @@
|
||||||
# Function parameter types
|
# Function parameter types
|
||||||
|
|
||||||
Within a function scope, the declared type of each parameter is its annotated type (or Unknown if
|
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
|
not annotated). The initial inferred type is the annotated type of the parameter, if any. If there
|
||||||
default value expression (if any). If both are fully static types, this union should simplify to the
|
is no annotation, it is the union of `Unknown` with the type of the default value expression (if
|
||||||
annotated type (since the default value type must be assignable to the annotated type, and for fully
|
any).
|
||||||
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.
|
|
||||||
|
|
||||||
The variadic parameter is a variadic tuple of its annotated type; the variadic-keywords parameter is
|
The variadic parameter is a variadic tuple of its annotated type; the variadic-keywords parameter is
|
||||||
a dictionary from strings to its annotated type.
|
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
|
## 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
|
```py
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
def f(x: Any = 1):
|
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
|
## Default value type must be assignable to annotated type
|
||||||
|
|
@ -64,7 +61,7 @@ def f(x: int = "foo"):
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
def g(x: Any = "foo"):
|
def g(x: Any = "foo"):
|
||||||
reveal_type(x) # revealed: Any | Literal["foo"]
|
reveal_type(x) # revealed: Any
|
||||||
```
|
```
|
||||||
|
|
||||||
## Stub functions
|
## 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:
|
def explicit_unknown(x: Unknown, y: tuple[str, Unknown], z: Unknown = 1) -> None:
|
||||||
reveal_type(x) # revealed: Unknown
|
reveal_type(x) # revealed: Unknown
|
||||||
reveal_type(y) # revealed: tuple[str, 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`:
|
`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 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
|
/// The inferred type is the annotated type, if any. If there is no annotation, it is the union
|
||||||
/// any. If both types are fully static, this union is a no-op (it should simplify to just the
|
/// of `Unknown` and the type of the default value, if any.
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// Parameter definitions are odd in that they define a symbol in the function-body scope, so
|
/// 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
|
/// 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));
|
.map(|default| self.file_expression_type(default));
|
||||||
if let Some(annotation) = parameter.annotation.as_ref() {
|
if let Some(annotation) = parameter.annotation.as_ref() {
|
||||||
let declared_ty = self.file_expression_type(annotation);
|
let declared_ty = self.file_expression_type(annotation);
|
||||||
let declared_and_inferred_ty = if let Some(default_ty) = default_ty {
|
if let Some(default_ty) = default_ty {
|
||||||
if default_ty.is_assignable_to(self.db(), declared_ty) {
|
if !default_ty.is_assignable_to(self.db(), declared_ty)
|
||||||
DeclaredAndInferredType::MightBeDifferent {
|
&& !((self.in_stub()
|
||||||
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.in_function_overload_or_abstractmethod()
|
||||||
|| self
|
|| self
|
||||||
.class_context_of_current_method()
|
.class_context_of_current_method()
|
||||||
.is_some_and(|class| class.is_protocol(self.db())))
|
.is_some_and(|class| class.is_protocol(self.db())))
|
||||||
&& default
|
&& default
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|d| d.is_ellipsis_literal_expr())
|
.is_some_and(|d| d.is_ellipsis_literal_expr()))
|
||||||
{
|
{
|
||||||
DeclaredAndInferredType::are_the_same_type(declared_ty)
|
|
||||||
} else {
|
|
||||||
if let Some(builder) = self
|
if let Some(builder) = self
|
||||||
.context
|
.context
|
||||||
.report_lint(&INVALID_PARAMETER_DEFAULT, parameter_with_default)
|
.report_lint(&INVALID_PARAMETER_DEFAULT, parameter_with_default)
|
||||||
|
|
@ -2488,15 +2475,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
declared_ty.display(self.db())
|
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(
|
self.add_declaration_with_binding(
|
||||||
parameter.into(),
|
parameter.into(),
|
||||||
definition,
|
definition,
|
||||||
&declared_and_inferred_ty,
|
&DeclaredAndInferredType::are_the_same_type(declared_ty),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let ty = if let Some(default_ty) = default_ty {
|
let ty = if let Some(default_ty) = default_ty {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue