Use assignment definition as typevar binding context

This commit is contained in:
David Peter
2025-11-24 11:27:40 +01:00
parent 013d43a2dd
commit eee6f25f2e
4 changed files with 71 additions and 17 deletions

View File

@@ -90,6 +90,12 @@ impl<'db> Definition<'db> {
.to_string(),
)
}
DefinitionKind::Assignment(assignment) => {
let target_node = assignment.target.node(&module);
target_node
.as_name_expr()
.map(|name_expr| name_expr.id.as_str().to_string())
}
_ => None,
}
}

View File

@@ -4739,6 +4739,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
unpacked.expression_type(target)
}
TargetKind::Single => {
// This could be an implicit type alias (OptionalList = list[T] | None). Use the definition
// of `OptionalList` as the typevar binding context while inferring the RHS (`list[T] | None`),
// in order to bind `T@OptionalList`.
let previous_typevar_binding_context =
self.typevar_binding_context.replace(definition);
let value_ty = if let Some(standalone_expression) = self.index.try_expression(value)
{
self.infer_standalone_expression_impl(value, standalone_expression, tcx)
@@ -4777,6 +4783,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_expression(value, tcx)
};
self.typevar_binding_context = previous_typevar_binding_context;
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
// at runtime, but is always considered `True` in type checking.
// See mdtest/known_constants.md#user-defined-type_checking for details.

View File

@@ -2,7 +2,6 @@ use itertools::Either;
use ruff_python_ast as ast;
use super::{DeferredExpressionState, TypeInferenceBuilder};
use crate::FxOrderSet;
use crate::types::diagnostic::{
self, INVALID_TYPE_FORM, NON_SUBSCRIPTABLE, report_invalid_argument_number_to_special_form,
report_invalid_arguments_to_callable,
@@ -16,6 +15,7 @@ use crate::types::{
KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType,
Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, UnionBuilder, UnionType, todo_type,
};
use crate::{FxOrderSet, ResolvedDefinition, definitions_for_name};
/// Type expressions
impl<'db> TypeInferenceBuilder<'db, '_> {
@@ -759,9 +759,32 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
) -> Type<'db> {
let db = self.db();
let Some(value) = subscript.value.as_name_expr() else {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic("Cannot specialize a non-name node in a type expression");
}
return Type::unknown();
};
// TODO: This is an expensive call to an API that was never meant to be called from
// type inference. We plan to rework how `in_type_expression` works in the future.
// This new approach will make this call unnecessary, so for now, we accept the hit
// in performance.
let definitions = definitions_for_name(self.db(), self.file(), value);
let Some(type_alias_definition) =
definitions.iter().find_map(ResolvedDefinition::definition)
else {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(
"Cannot specialize implicit type alias with unknown definition",
);
}
return Type::unknown();
};
let generic_type_alias = value_ty.apply_type_mapping(
db,
&TypeMapping::BindLegacyTypevars(BindingContext::Synthetic),
&TypeMapping::BindLegacyTypevars(BindingContext::Definition(type_alias_definition)),
TypeContext::default(),
);