Compare commits

...

2 Commits

Author SHA1 Message Date
Charlie Marsh
85f5039b11 Always defer 2025-12-17 13:40:25 -05:00
Charlie Marsh
96dbe609eb Respect deferred values in keyword arguments et al for .pyi files 2025-12-17 11:33:28 -05:00
3 changed files with 104 additions and 18 deletions

View File

@@ -205,3 +205,93 @@ class B:
class A(B): ...
class B: ...
```
## Default argument values
### Not deferred in regular files
```py
# error: [unresolved-reference]
def f(mode: int = ParseMode.test):
pass
class ParseMode:
test = 1
```
### Deferred in stub files
Forward references in default argument values are allowed in stub files.
```pyi
def f(mode: int = ParseMode.test): ...
class ParseMode:
test: int
```
### Undefined names are still errors in stub files
```pyi
# error: [unresolved-reference]
def f(mode: int = NeverDefined.test): ...
```
## Class keyword arguments
### Not deferred in regular files
```py
# error: [unresolved-reference]
class Foo(metaclass=SomeMeta):
pass
class SomeMeta(type):
pass
```
### Deferred in stub files
Forward references in class keyword arguments are allowed in stub files.
```pyi
class Foo(metaclass=SomeMeta): ...
class SomeMeta(type): ...
```
### Undefined names are still errors in stub files
```pyi
# error: [unresolved-reference]
class Foo(metaclass=NeverDefined): ...
```
## Lambda default argument values
### Not deferred in regular files
```py
# error: [unresolved-reference]
f = lambda x=Foo(): x
class Foo:
pass
```
### Deferred in stub files
Forward references in lambda default argument values are allowed in stub files.
```pyi
f = lambda x=Foo(): x
class Foo: ...
```
### Undefined names are still errors in stub files
```pyi
# error: [unresolved-reference]
f = lambda x=NeverDefined(): x
```

View File

@@ -50,10 +50,10 @@ reveal_type(custom_builtin) # revealed: Custom
reveal_type(str) # revealed: Unknown
```
## Unknown builtin (later defined)
## Forward reference in builtins stub
`foo` has a type of `Unknown` in this example, as it relies on `bar` which has not been defined at
that point:
In stub files, forward references are allowed in all expressions, so `foo` correctly resolves to the
type of `bar` even though `bar` is defined later:
```toml
[environment]
@@ -74,5 +74,5 @@ def reveal_type(obj, /): ...
```
```py
reveal_type(foo) # revealed: Unknown
reveal_type(foo) # revealed: Literal[1]
```

View File

@@ -327,6 +327,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
module: &'ast ParsedModuleRef,
) -> Self {
let scope = region.scope(db);
let file = scope.file(db);
// In stub files, all expressions are evaluated in deferred mode to allow
// forward references. This matches Pyright's behavior.
let deferred_state = if file.is_stub(db) {
DeferredExpressionState::Deferred
} else {
DeferredExpressionState::None
};
Self {
context: InferContext::new(db, scope, module),
@@ -335,7 +344,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
scope,
return_types_and_ranges: vec![],
called_functions: FxIndexSet::default(),
deferred_state: DeferredExpressionState::None,
deferred_state,
multi_inference_state: MultiInferenceState::Panic,
inner_expression_inference_state: InnerExpressionInferenceState::Infer,
expressions: FxHashMap::default(),
@@ -1955,9 +1964,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_type_parameters(type_params);
if let Some(arguments) = class.arguments.as_deref() {
let in_stub = self.in_stub();
let previous_deferred_state =
std::mem::replace(&mut self.deferred_state, in_stub.into());
let mut call_arguments =
CallArguments::from_arguments(arguments, |argument, splatted_value| {
let ty = self.infer_expression(splatted_value, TypeContext::default());
@@ -1968,7 +1974,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
});
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
self.deferred_state = previous_deferred_state;
}
self.typevar_binding_context = previous_typevar_binding_context;
@@ -5913,13 +5918,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.dataclass_field_specifiers = specifiers;
}
// We defer the r.h.s. of PEP-613 `TypeAlias` assignments in stub files.
let previous_deferred_state = self.deferred_state;
if is_pep_613_type_alias && self.in_stub() {
self.deferred_state = DeferredExpressionState::Deferred;
}
// This might be a PEP-613 type alias (`OptionalList: TypeAlias = list[T] | None`). Use
// the definition of `OptionalList` as the binding context while inferring the
// RHS (`list[T] | None`), in order to bind `T` to `OptionalList`.
@@ -5932,8 +5930,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.typevar_binding_context = previous_typevar_binding_context;
self.deferred_state = previous_deferred_state;
self.dataclass_field_specifiers.clear();
let inferred_ty = if target