diff --git a/crates/ty_python_semantic/resources/mdtest/import/builtins.md b/crates/ty_python_semantic/resources/mdtest/import/builtins.md index f8ef2a2f9a..0f32f422ca 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/builtins.md +++ b/crates/ty_python_semantic/resources/mdtest/import/builtins.md @@ -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] ``` diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index ac6d092c08..9ebb7a371c 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -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; @@ -2358,16 +2363,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { decorator_types_and_nodes.push((decorator_type, decorator)); } - // In stub files, default values may reference names that are defined later in the file. - let in_stub = self.in_stub(); - let previous_deferred_state = std::mem::replace(&mut self.deferred_state, in_stub.into()); for default in parameters .iter_non_variadic_params() .filter_map(|param| param.default.as_deref()) { self.infer_expression(default, TypeContext::default()); } - self.deferred_state = previous_deferred_state; // If there are type params, parameters and returns are evaluated in that scope. Otherwise, // we always defer the inference of the parameters and returns. That ensures that we do not @@ -3002,14 +3003,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // if there are type parameters, then the keywords and bases are within that scope // and we don't need to run inference here if type_params.is_none() { - // In stub files, keyword values may reference names that are defined later in the file. - let in_stub = self.in_stub(); - let previous_deferred_state = - std::mem::replace(&mut self.deferred_state, in_stub.into()); for keyword in class_node.keywords() { self.infer_expression(&keyword.value, TypeContext::default()); } - self.deferred_state = previous_deferred_state; // Inference of bases deferred in stubs, or if any are string literals. if self.in_stub() || class_node.bases().iter().any(contains_string_literal) { @@ -5922,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`. @@ -5941,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 @@ -8331,10 +8318,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { body: _, } = lambda_expression; - // In stub files, default values may reference names that are defined later in the file. - let in_stub = self.in_stub(); - let previous_deferred_state = std::mem::replace(&mut self.deferred_state, in_stub.into()); - let parameters = if let Some(parameters) = parameters { let positional_only = parameters .posonlyargs @@ -8400,8 +8383,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Parameters::empty() }; - self.deferred_state = previous_deferred_state; - // 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.