[ty] don't union in default type for annotated parameters (#21208)

This commit is contained in:
Carl Meyer 2025-11-02 18:21:54 -05:00 committed by GitHub
parent c32234cf0d
commit 0454a72674
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 21 additions and 40 deletions

View File

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

View File

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

View File

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