[ty] defer inference of legacy TypeVar bound/constraints/defaults (#20598)

## Summary

This allows us to handle self-referential bounds/constraints/defaults
without panicking.

Handles more cases from https://github.com/astral-sh/ty/issues/256

This also changes the way we infer the types of legacy TypeVars. Rather
than understanding a constructor call to `typing[_extension].TypeVar`
inside of any (arbitrarily nested) expression, and having to use a
special `assigned_to` field of the semantic index to try to best-effort
figure out what name the typevar was assigned to, we instead understand
the creation of a legacy `TypeVar` only in the supported syntactic
position (RHS of a simple un-annotated assignment with one target). In
any other position, we just infer it as creating an opaque instance of
`typing.TypeVar`. (This behavior matches all other type checkers.)

So we now special-case TypeVar creation in `TypeInferenceBuilder`, as a
special case of an assignment definition, rather than deeper inside call
binding. This does mean we re-implement slightly more of
argument-parsing, but in practice this is minimal and easy to handle
correctly.

This is easier to implement if we also make the RHS of a simple (no
unpacking) one-target assignment statement no longer a standalone
expression. Which is fine to do, because simple one-target assignments
don't need to infer the RHS more than once. This is a bonus performance
(0-3% across various projects) and significant memory-usage win, since
most assignment statements are simple one-target assignment statements,
meaning we now create many fewer standalone-expression salsa
ingredients.

This change does mean that inference of manually-constructed
`TypeAliasType` instances can no longer find its Definition in
`assigned_to`, which regresses go-to-definition for these aliases. In a
future PR, `TypeAliasType` will receive the same treatment that
`TypeVar` did in this PR (moving its special-case inference into
`TypeInferenceBuilder` and supporting it only in the correct syntactic
position, and lazily inferring its value type to support recursion),
which will also fix the go-to-definition regression. (I decided a
temporary edge-case regression is better in this case than doubling the
size of this PR.)

This PR also tightens up and fixes various aspects of the validation of
`TypeVar` creation, as seen in the tests.

We still (for now) treat all typevars as instances of `typing.TypeVar`,
even if they were created using `typing_extensions.TypeVar`. This means
we'll wrongly error on e.g. `T.__default__` on Python 3.11, even if `T`
is a `typing_extensions.TypeVar` instance at runtime. We share this
wrong behavior with both mypy and pyrefly. It will be easier to fix
after we pull in https://github.com/python/typeshed/pull/14840.

There are some issues that showed up here with typevar identity and
`MarkTypeVarsInferable`; the fix here (using the new `original` field
and `is_identical_to` methods on `BoundTypeVarInstance` and
`TypeVarInstance`) is a bit kludgy, but it can go away when we eliminate
`MarkTypeVarsInferable`.

## Test Plan

Added and updated mdtests.

### Conformance suite impact

The impact here is all positive:

* We now correctly error on a legacy TypeVar with exactly one constraint
type given.
* We now correctly error on a legacy TypeVar with both an upper bound
and constraints specified.

### Ecosystem impact

Basically none; in the setuptools case we just issue slightly different
errors on an invalid TypeVar definition, due to the modified validation
code.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Carl Meyer 2025-10-09 14:08:37 -07:00 committed by GitHub
parent b086ffe921
commit 8248193ed9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1441 additions and 408 deletions

View file

@ -1165,10 +1165,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
target: &'ast ast::Expr,
value: Expression<'db>,
) {
// We only handle assignments to names and unpackings here, other targets like
// attribute and subscript are handled separately as they don't create a new
// definition.
let current_assignment = match target {
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
if matches!(unpackable, Unpackable::Comprehension { .. }) {
@ -1628,10 +1624,22 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
debug_assert_eq!(&self.current_assignments, &[]);
self.visit_expr(&node.value);
let value = self.add_standalone_assigned_expression(&node.value, node);
for target in &node.targets {
self.add_unpackable_assignment(&Unpackable::Assign(node), target, value);
// Optimization for the common case: if there's just one target, and it's not an
// unpacking, and the target is a simple name, we don't need the RHS to be a
// standalone expression at all.
if let [target] = &node.targets[..]
&& target.is_name_expr()
{
self.push_assignment(CurrentAssignment::Assign { node, unpack: None });
self.visit_expr(target);
self.pop_assignment();
} else {
let value = self.add_standalone_assigned_expression(&node.value, node);
for target in &node.targets {
self.add_unpackable_assignment(&Unpackable::Assign(node), target, value);
}
}
}
ast::Stmt::AnnAssign(node) => {

View file

@ -706,13 +706,6 @@ impl DefinitionKind<'_> {
matches!(self, DefinitionKind::Assignment(_))
}
pub(crate) fn as_typevar(&self) -> Option<&AstNodeRef<ast::TypeParamTypeVar>> {
match self {
DefinitionKind::TypeVar(type_var) => Some(type_var),
_ => None,
}
}
/// Returns the [`TextRange`] of the definition target.
///
/// A definition target would mainly be the node representing the place being defined i.e.,

View file

@ -33,7 +33,7 @@ pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, resolve_module};
use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol};
use crate::semantic_index::definition::Definition;
use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::semantic_index::place::ScopedPlaceId;
use crate::semantic_index::scope::ScopeId;
use crate::semantic_index::{imported_modules, place_table, semantic_index};
@ -1642,7 +1642,9 @@ impl<'db> Type<'db> {
(
Type::NonInferableTypeVar(lhs_bound_typevar),
Type::NonInferableTypeVar(rhs_bound_typevar),
) if lhs_bound_typevar == rhs_bound_typevar => ConstraintSet::from(true),
) if lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => {
ConstraintSet::from(true)
}
// A fully static typevar is a subtype of its upper bound, and to something similar to
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
@ -4841,56 +4843,6 @@ impl<'db> Type<'db> {
.into()
}
Some(KnownClass::TypeVar) => {
// ```py
// class TypeVar:
// def __new__(
// cls,
// name: str,
// *constraints: Any,
// bound: Any | None = None,
// contravariant: bool = False,
// covariant: bool = False,
// infer_variance: bool = False,
// default: Any = ...,
// ) -> Self: ...
// ```
Binding::single(
self,
Signature::new(
Parameters::new([
Parameter::positional_or_keyword(Name::new_static("name"))
.with_annotated_type(Type::LiteralString),
Parameter::variadic(Name::new_static("constraints"))
.type_form()
.with_annotated_type(Type::any()),
Parameter::keyword_only(Name::new_static("bound"))
.type_form()
.with_annotated_type(UnionType::from_elements(
db,
[Type::any(), Type::none(db)],
))
.with_default_type(Type::none(db)),
Parameter::keyword_only(Name::new_static("default"))
.type_form()
.with_annotated_type(Type::any())
.with_default_type(KnownClass::NoneType.to_instance(db)),
Parameter::keyword_only(Name::new_static("contravariant"))
.with_annotated_type(KnownClass::Bool.to_instance(db))
.with_default_type(Type::BooleanLiteral(false)),
Parameter::keyword_only(Name::new_static("covariant"))
.with_annotated_type(KnownClass::Bool.to_instance(db))
.with_default_type(Type::BooleanLiteral(false)),
Parameter::keyword_only(Name::new_static("infer_variance"))
.with_annotated_type(KnownClass::Bool.to_instance(db))
.with_default_type(Type::BooleanLiteral(false)),
]),
Some(KnownClass::TypeVar.to_instance(db)),
),
)
.into()
}
Some(KnownClass::Deprecated) => {
// ```py
// class deprecated:
@ -7832,6 +7784,12 @@ pub struct TypeVarInstance<'db> {
_default: Option<TypeVarDefaultEvaluation<'db>>,
pub kind: TypeVarKind,
/// If this typevar was transformed from another typevar via `mark_typevars_inferable`, this
/// records the identity of the "original" typevar, so we can recognize them as the same
/// typevar in `bind_typevar`. TODO: this (and the `is_identical_to` methods) should be
/// removable once we remove `mark_typevars_inferable`.
pub(crate) original: Option<TypeVarInstance<'db>>,
}
// The Salsa heap is tracked separately.
@ -7942,6 +7900,7 @@ impl<'db> TypeVarInstance<'db> {
.map(|ty| ty.normalized_impl(db, visitor).into()),
}),
self.kind(db),
self.original(db),
)
}
@ -7987,6 +7946,7 @@ impl<'db> TypeVarInstance<'db> {
.map(|ty| ty.materialize(db, materialization_kind, visitor).into()),
}),
self.kind(db),
self.original(db),
)
}
@ -8000,10 +7960,7 @@ impl<'db> TypeVarInstance<'db> {
// inferable, so we set the parameter to `None` here.
let type_mapping = &TypeMapping::MarkTypeVarsInferable(None);
Self::new(
db,
self.name(db),
self.definition(db),
let new_bound_or_constraints =
self._bound_or_constraints(db)
.map(|bound_or_constraints| match bound_or_constraints {
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
@ -8013,22 +7970,46 @@ impl<'db> TypeVarInstance<'db> {
}
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound
| TypeVarBoundOrConstraintsEvaluation::LazyConstraints => bound_or_constraints,
}),
self.explicit_variance(db),
self._default(db).and_then(|default| match default {
TypeVarDefaultEvaluation::Eager(ty) => Some(
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
.into(),
),
TypeVarDefaultEvaluation::Lazy => self.lazy_default(db).map(|ty| {
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
.into()
}),
});
let new_default = self._default(db).and_then(|default| match default {
TypeVarDefaultEvaluation::Eager(ty) => Some(
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
.into(),
),
TypeVarDefaultEvaluation::Lazy => self.lazy_default(db).map(|ty| {
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
.into()
}),
});
// Ensure that we only modify the `original` field if we are going to modify one or both of
// `_bound_or_constraints` and `_default`; don't trigger creation of a new
// `TypeVarInstance` unnecessarily.
let new_original = if new_bound_or_constraints == self._bound_or_constraints(db)
&& new_default == self._default(db)
{
self.original(db)
} else {
Some(self)
};
Self::new(
db,
self.name(db),
self.definition(db),
new_bound_or_constraints,
self.explicit_variance(db),
new_default,
self.kind(db),
new_original,
)
}
fn is_identical_to(self, db: &'db dyn Db, other: Self) -> bool {
self == other || (self.original(db) == Some(other) || other.original(db) == Some(self))
}
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
let bound_or_constraints = match self.bound_or_constraints(db)? {
TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
@ -8046,38 +8027,88 @@ impl<'db> TypeVarInstance<'db> {
self.explicit_variance(db),
None,
self.kind(db),
self.original(db),
))
}
#[salsa::tracked(cycle_fn=lazy_bound_cycle_recover, cycle_initial=lazy_bound_cycle_initial)]
#[salsa::tracked(cycle_fn=lazy_bound_cycle_recover, cycle_initial=lazy_bound_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
fn lazy_bound(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
let definition = self.definition(db)?;
let module = parsed_module(db, definition.file(db)).load(db);
let typevar_node = definition.kind(db).as_typevar()?.node(&module);
let ty = definition_expression_type(db, definition, typevar_node.bound.as_ref()?);
let ty = match definition.kind(db) {
// PEP 695 typevar
DefinitionKind::TypeVar(typevar) => {
let typevar_node = typevar.node(&module);
definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
}
// legacy typevar
DefinitionKind::Assignment(assignment) => {
let call_expr = assignment.value(&module).as_call_expr()?;
let expr = &call_expr.arguments.find_keyword("bound")?.value;
definition_expression_type(db, definition, expr)
}
_ => return None,
};
Some(TypeVarBoundOrConstraints::UpperBound(ty))
}
#[salsa::tracked]
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn lazy_constraints(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
let definition = self.definition(db)?;
let module = parsed_module(db, definition.file(db)).load(db);
let typevar_node = definition.kind(db).as_typevar()?.node(&module);
let ty = definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
.into_union()?;
let ty = match definition.kind(db) {
// PEP 695 typevar
DefinitionKind::TypeVar(typevar) => {
let typevar_node = typevar.node(&module);
definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
.into_union()?
}
// legacy typevar
DefinitionKind::Assignment(assignment) => {
let call_expr = assignment.value(&module).as_call_expr()?;
// We don't use `UnionType::from_elements` or `UnionBuilder` here,
// because we don't want to simplify the list of constraints as we would with
// an actual union type.
// TODO: We probably shouldn't use `UnionType` to store these at all? TypeVar
// constraints are not a union.
UnionType::new(
db,
call_expr
.arguments
.args
.iter()
.skip(1)
.map(|arg| definition_expression_type(db, definition, arg))
.collect::<Box<_>>(),
)
}
_ => return None,
};
Some(TypeVarBoundOrConstraints::Constraints(ty))
}
#[salsa::tracked]
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn lazy_default(self, db: &'db dyn Db) -> Option<Type<'db>> {
let definition = self.definition(db)?;
let module = parsed_module(db, definition.file(db)).load(db);
let typevar_node = definition.kind(db).as_typevar()?.node(&module);
Some(definition_expression_type(
db,
definition,
typevar_node.default.as_ref()?,
))
match definition.kind(db) {
// PEP 695 typevar
DefinitionKind::TypeVar(typevar) => {
let typevar_node = typevar.node(&module);
Some(definition_expression_type(
db,
definition,
typevar_node.default.as_ref()?,
))
}
// legacy typevar
DefinitionKind::Assignment(assignment) => {
let call_expr = assignment.value(&module).as_call_expr()?;
let expr = &call_expr.arguments.find_keyword("default")?.value;
Some(definition_expression_type(db, definition, expr))
}
_ => None,
}
}
}
@ -8153,6 +8184,7 @@ impl<'db> BoundTypeVarInstance<'db> {
Some(variance),
None, // _default
TypeVarKind::Pep695,
None,
),
BindingContext::Synthetic,
)
@ -8174,11 +8206,24 @@ impl<'db> BoundTypeVarInstance<'db> {
Some(TypeVarVariance::Invariant),
None,
TypeVarKind::TypingSelf,
None,
),
binding_context,
)
}
pub(crate) fn is_identical_to(self, db: &'db dyn Db, other: Self) -> bool {
if self == other {
return true;
}
if self.binding_context(db) != other.binding_context(db) {
return false;
}
self.typevar(db).is_identical_to(db, other.typevar(db))
}
pub(crate) fn variance_with_polarity(
self,
db: &'db dyn Db,

View file

@ -16,7 +16,7 @@ use crate::semantic_index::{
};
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::context::InferContext;
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE;
use crate::types::enums::enum_metadata;
use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
@ -29,9 +29,8 @@ use crate::types::{
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarKind, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type,
determine_upper_bound, infer_definition_types,
TypeContext, TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable,
declaration_type, determine_upper_bound, infer_definition_types,
};
use crate::{
Db, FxIndexMap, FxOrderSet, Program,
@ -3761,6 +3760,8 @@ pub enum KnownClass {
SupportsIndex,
Iterable,
Iterator,
// typing_extensions
ExtensionsTypeVar, // must be distinct from typing.TypeVar, backports new features
// Collections
ChainMap,
Counter,
@ -3815,6 +3816,7 @@ impl KnownClass {
| Self::VersionInfo
| Self::TypeAliasType
| Self::TypeVar
| Self::ExtensionsTypeVar
| Self::ParamSpec
| Self::ParamSpecArgs
| Self::ParamSpecKwargs
@ -3943,6 +3945,7 @@ impl KnownClass {
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
| KnownClass::ExtensionsTypeVar
| KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs
@ -4025,6 +4028,7 @@ impl KnownClass {
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
| KnownClass::ExtensionsTypeVar
| KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs
@ -4107,6 +4111,7 @@ impl KnownClass {
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
| KnownClass::ExtensionsTypeVar
| KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs
@ -4194,6 +4199,7 @@ impl KnownClass {
| Self::NoneType
| Self::SpecialForm
| Self::TypeVar
| Self::ExtensionsTypeVar
| Self::ParamSpec
| Self::ParamSpecArgs
| Self::ParamSpecKwargs
@ -4289,6 +4295,7 @@ impl KnownClass {
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
| KnownClass::ExtensionsTypeVar
| KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs
@ -4358,6 +4365,7 @@ impl KnownClass {
Self::NoneType => "NoneType",
Self::SpecialForm => "_SpecialForm",
Self::TypeVar => "TypeVar",
Self::ExtensionsTypeVar => "TypeVar",
Self::ParamSpec => "ParamSpec",
Self::ParamSpecArgs => "ParamSpecArgs",
Self::ParamSpecKwargs => "ParamSpecKwargs",
@ -4656,6 +4664,7 @@ impl KnownClass {
| Self::ProtocolMeta
| Self::SupportsIndex => KnownModule::Typing,
Self::TypeAliasType
| Self::ExtensionsTypeVar
| Self::TypeVarTuple
| Self::ParamSpec
| Self::ParamSpecArgs
@ -4752,6 +4761,7 @@ impl KnownClass {
| Self::SupportsIndex
| Self::StdlibAlias
| Self::TypeVar
| Self::ExtensionsTypeVar
| Self::ParamSpec
| Self::ParamSpecArgs
| Self::ParamSpecKwargs
@ -4838,6 +4848,7 @@ impl KnownClass {
| Self::Generator
| Self::Deprecated
| Self::TypeVar
| Self::ExtensionsTypeVar
| Self::ParamSpec
| Self::ParamSpecArgs
| Self::ParamSpecKwargs
@ -4875,99 +4886,102 @@ impl KnownClass {
) -> Option<Self> {
// We assert that this match is exhaustive over the right-hand side in the unit test
// `known_class_roundtrip_from_str()`
let candidate = match class_name {
"bool" => Self::Bool,
"object" => Self::Object,
"bytes" => Self::Bytes,
"bytearray" => Self::Bytearray,
"tuple" => Self::Tuple,
"type" => Self::Type,
"int" => Self::Int,
"float" => Self::Float,
"complex" => Self::Complex,
"str" => Self::Str,
"set" => Self::Set,
"frozenset" => Self::FrozenSet,
"dict" => Self::Dict,
"list" => Self::List,
"slice" => Self::Slice,
"property" => Self::Property,
"BaseException" => Self::BaseException,
"BaseExceptionGroup" => Self::BaseExceptionGroup,
"Exception" => Self::Exception,
"ExceptionGroup" => Self::ExceptionGroup,
"staticmethod" => Self::Staticmethod,
"classmethod" => Self::Classmethod,
"Awaitable" => Self::Awaitable,
"Generator" => Self::Generator,
"deprecated" => Self::Deprecated,
"GenericAlias" => Self::GenericAlias,
"NoneType" => Self::NoneType,
"ModuleType" => Self::ModuleType,
"GeneratorType" => Self::GeneratorType,
"AsyncGeneratorType" => Self::AsyncGeneratorType,
"CoroutineType" => Self::CoroutineType,
"FunctionType" => Self::FunctionType,
"MethodType" => Self::MethodType,
"UnionType" => Self::UnionType,
"MethodWrapperType" => Self::MethodWrapperType,
"WrapperDescriptorType" => Self::WrapperDescriptorType,
"BuiltinFunctionType" => Self::BuiltinFunctionType,
"NewType" => Self::NewType,
"TypeAliasType" => Self::TypeAliasType,
"TypeVar" => Self::TypeVar,
"Iterable" => Self::Iterable,
"Iterator" => Self::Iterator,
"ParamSpec" => Self::ParamSpec,
"ParamSpecArgs" => Self::ParamSpecArgs,
"ParamSpecKwargs" => Self::ParamSpecKwargs,
"TypeVarTuple" => Self::TypeVarTuple,
"ChainMap" => Self::ChainMap,
"Counter" => Self::Counter,
"defaultdict" => Self::DefaultDict,
"deque" => Self::Deque,
"OrderedDict" => Self::OrderedDict,
"_Alias" => Self::StdlibAlias,
"_SpecialForm" => Self::SpecialForm,
"_NoDefaultType" => Self::NoDefaultType,
"SupportsIndex" => Self::SupportsIndex,
"Enum" => Self::Enum,
"EnumMeta" => Self::EnumType,
let candidates: &[Self] = match class_name {
"bool" => &[Self::Bool],
"object" => &[Self::Object],
"bytes" => &[Self::Bytes],
"bytearray" => &[Self::Bytearray],
"tuple" => &[Self::Tuple],
"type" => &[Self::Type],
"int" => &[Self::Int],
"float" => &[Self::Float],
"complex" => &[Self::Complex],
"str" => &[Self::Str],
"set" => &[Self::Set],
"frozenset" => &[Self::FrozenSet],
"dict" => &[Self::Dict],
"list" => &[Self::List],
"slice" => &[Self::Slice],
"property" => &[Self::Property],
"BaseException" => &[Self::BaseException],
"BaseExceptionGroup" => &[Self::BaseExceptionGroup],
"Exception" => &[Self::Exception],
"ExceptionGroup" => &[Self::ExceptionGroup],
"staticmethod" => &[Self::Staticmethod],
"classmethod" => &[Self::Classmethod],
"Awaitable" => &[Self::Awaitable],
"Generator" => &[Self::Generator],
"deprecated" => &[Self::Deprecated],
"GenericAlias" => &[Self::GenericAlias],
"NoneType" => &[Self::NoneType],
"ModuleType" => &[Self::ModuleType],
"GeneratorType" => &[Self::GeneratorType],
"AsyncGeneratorType" => &[Self::AsyncGeneratorType],
"CoroutineType" => &[Self::CoroutineType],
"FunctionType" => &[Self::FunctionType],
"MethodType" => &[Self::MethodType],
"UnionType" => &[Self::UnionType],
"MethodWrapperType" => &[Self::MethodWrapperType],
"WrapperDescriptorType" => &[Self::WrapperDescriptorType],
"BuiltinFunctionType" => &[Self::BuiltinFunctionType],
"NewType" => &[Self::NewType],
"TypeAliasType" => &[Self::TypeAliasType],
"TypeVar" => &[Self::TypeVar, Self::ExtensionsTypeVar],
"Iterable" => &[Self::Iterable],
"Iterator" => &[Self::Iterator],
"ParamSpec" => &[Self::ParamSpec],
"ParamSpecArgs" => &[Self::ParamSpecArgs],
"ParamSpecKwargs" => &[Self::ParamSpecKwargs],
"TypeVarTuple" => &[Self::TypeVarTuple],
"ChainMap" => &[Self::ChainMap],
"Counter" => &[Self::Counter],
"defaultdict" => &[Self::DefaultDict],
"deque" => &[Self::Deque],
"OrderedDict" => &[Self::OrderedDict],
"_Alias" => &[Self::StdlibAlias],
"_SpecialForm" => &[Self::SpecialForm],
"_NoDefaultType" => &[Self::NoDefaultType],
"SupportsIndex" => &[Self::SupportsIndex],
"Enum" => &[Self::Enum],
"EnumMeta" => &[Self::EnumType],
"EnumType" if Program::get(db).python_version(db) >= PythonVersion::PY311 => {
Self::EnumType
&[Self::EnumType]
}
"StrEnum" if Program::get(db).python_version(db) >= PythonVersion::PY311 => {
Self::StrEnum
&[Self::StrEnum]
}
"auto" => Self::Auto,
"member" => Self::Member,
"nonmember" => Self::Nonmember,
"ABCMeta" => Self::ABCMeta,
"super" => Self::Super,
"_version_info" => Self::VersionInfo,
"auto" => &[Self::Auto],
"member" => &[Self::Member],
"nonmember" => &[Self::Nonmember],
"ABCMeta" => &[Self::ABCMeta],
"super" => &[Self::Super],
"_version_info" => &[Self::VersionInfo],
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
Self::EllipsisType
&[Self::EllipsisType]
}
"EllipsisType" if Program::get(db).python_version(db) >= PythonVersion::PY310 => {
Self::EllipsisType
&[Self::EllipsisType]
}
"_NotImplementedType" => Self::NotImplementedType,
"Field" => Self::Field,
"KW_ONLY" => Self::KwOnly,
"InitVar" => Self::InitVar,
"NamedTupleFallback" => Self::NamedTupleFallback,
"NamedTupleLike" => Self::NamedTupleLike,
"ConstraintSet" => Self::ConstraintSet,
"TypedDictFallback" => Self::TypedDictFallback,
"Template" => Self::Template,
"Path" => Self::Path,
"_ProtocolMeta" => Self::ProtocolMeta,
"_NotImplementedType" => &[Self::NotImplementedType],
"Field" => &[Self::Field],
"KW_ONLY" => &[Self::KwOnly],
"InitVar" => &[Self::InitVar],
"NamedTupleFallback" => &[Self::NamedTupleFallback],
"NamedTupleLike" => &[Self::NamedTupleLike],
"ConstraintSet" => &[Self::ConstraintSet],
"TypedDictFallback" => &[Self::TypedDictFallback],
"Template" => &[Self::Template],
"Path" => &[Self::Path],
"_ProtocolMeta" => &[Self::ProtocolMeta],
_ => return None,
};
candidate
.check_module(db, file_to_module(db, file)?.known(db)?)
.then_some(candidate)
let module = file_to_module(db, file)?.known(db)?;
candidates
.iter()
.copied()
.find(|&candidate| candidate.check_module(db, module))
}
/// Return `true` if the module of `self` matches `module`
@ -5028,6 +5042,8 @@ impl KnownClass {
| Self::InitVar
| Self::NamedTupleFallback
| Self::TypedDictFallback
| Self::TypeVar
| Self::ExtensionsTypeVar
| Self::NamedTupleLike
| Self::ConstraintSet
| Self::Awaitable
@ -5036,7 +5052,6 @@ impl KnownClass {
| Self::Path => module == self.canonical_module(db),
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
Self::SpecialForm
| Self::TypeVar
| Self::TypeAliasType
| Self::NoDefaultType
| Self::SupportsIndex
@ -5059,7 +5074,6 @@ impl KnownClass {
context: &InferContext<'db, '_>,
index: &SemanticIndex<'db>,
overload: &mut Binding<'db>,
call_arguments: &CallArguments<'_, 'db>,
call_expression: &ast::ExprCall,
) {
let db = context.db();
@ -5132,6 +5146,7 @@ impl KnownClass {
_ => {}
}
}
KnownClass::Deprecated => {
// Parsing something of the form:
//
@ -5158,153 +5173,6 @@ impl KnownClass {
DeprecatedInstance::new(db, message.into_string_literal()),
)));
}
KnownClass::TypeVar => {
let assigned_to = index
.try_expression(ast::ExprRef::from(call_expression))
.and_then(|expr| expr.assigned_to(db));
let Some(target) = assigned_to.as_ref().and_then(|assigned_to| {
match assigned_to.node(module).targets.as_slice() {
[ast::Expr::Name(target)] => Some(target),
_ => None,
}
}) else {
if let Some(builder) =
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
{
builder.into_diagnostic(
"A legacy `typing.TypeVar` must be immediately assigned to a variable",
);
}
return;
};
let [
Some(name_param),
constraints,
bound,
default,
contravariant,
covariant,
_infer_variance,
] = overload.parameter_types()
else {
return;
};
let covariant = covariant
.map(|ty| ty.bool(db))
.unwrap_or(Truthiness::AlwaysFalse);
let contravariant = contravariant
.map(|ty| ty.bool(db))
.unwrap_or(Truthiness::AlwaysFalse);
let variance = match (contravariant, covariant) {
(Truthiness::Ambiguous, _) => {
if let Some(builder) =
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
{
builder.into_diagnostic(
"The `contravariant` parameter of a legacy `typing.TypeVar` \
cannot have an ambiguous value",
);
}
return;
}
(_, Truthiness::Ambiguous) => {
if let Some(builder) =
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
{
builder.into_diagnostic(
"The `covariant` parameter of a legacy `typing.TypeVar` \
cannot have an ambiguous value",
);
}
return;
}
(Truthiness::AlwaysTrue, Truthiness::AlwaysTrue) => {
if let Some(builder) =
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
{
builder.into_diagnostic(
"A legacy `typing.TypeVar` cannot be both \
covariant and contravariant",
);
}
return;
}
(Truthiness::AlwaysTrue, Truthiness::AlwaysFalse) => {
TypeVarVariance::Contravariant
}
(Truthiness::AlwaysFalse, Truthiness::AlwaysTrue) => TypeVarVariance::Covariant,
(Truthiness::AlwaysFalse, Truthiness::AlwaysFalse) => {
TypeVarVariance::Invariant
}
};
let name_param = name_param.into_string_literal().map(|name| name.value(db));
if name_param.is_none_or(|name_param| name_param != target.id) {
if let Some(builder) =
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
{
builder.into_diagnostic(format_args!(
"The name of a legacy `typing.TypeVar`{} must match \
the name of the variable it is assigned to (`{}`)",
if let Some(name_param) = name_param {
format!(" (`{name_param}`)")
} else {
String::new()
},
target.id,
));
}
return;
}
let bound_or_constraint = match (bound, constraints) {
(Some(bound), None) => {
Some(TypeVarBoundOrConstraints::UpperBound(*bound).into())
}
(None, Some(_constraints)) => {
// We don't use UnionType::from_elements or UnionBuilder here,
// because we don't want to simplify the list of constraints like
// we do with the elements of an actual union type.
// TODO: Consider using a new `OneOfType` connective here instead,
// since that more accurately represents the actual semantics of
// typevar constraints.
let elements = UnionType::new(
db,
overload
.arguments_for_parameter(call_arguments, 1)
.map(|(_, ty)| ty)
.collect::<Box<_>>(),
);
Some(TypeVarBoundOrConstraints::Constraints(elements).into())
}
// TODO: Emit a diagnostic that TypeVar cannot be both bounded and
// constrained
(Some(_), Some(_)) => return,
(None, None) => None,
};
let containing_assignment = index.expect_single_definition(target);
overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar(
TypeVarInstance::new(
db,
&target.id,
Some(containing_assignment),
bound_or_constraint,
Some(variance),
default.map(Into::into),
TypeVarKind::Legacy,
),
)));
}
KnownClass::TypeAliasType => {
let assigned_to = index

View file

@ -126,6 +126,7 @@ pub(crate) fn typing_self<'db>(
Some(TypeVarVariance::Invariant),
None,
TypeVarKind::TypingSelf,
None,
);
bind_typevar(
@ -396,7 +397,7 @@ impl<'db> GenericContext<'db> {
typevar: TypeVarInstance<'db>,
) -> Option<BoundTypeVarInstance<'db>> {
self.variables(db)
.find(|self_bound_typevar| self_bound_typevar.typevar(db) == typevar)
.find(|self_bound_typevar| self_bound_typevar.typevar(db).is_identical_to(db, typevar))
}
/// Creates a specialization of this generic context. Panics if the length of `types` does not

View file

@ -631,7 +631,7 @@ struct DefinitionInferenceExtra<'db> {
/// Is this a cycle-recovery inference result, and if so, what kind?
cycle_recovery: Option<CycleRecovery<'db>>,
/// The definitions that are deferred.
/// The definitions that have some deferred parts.
deferred: Box<[Definition<'db>]>,
/// The diagnostics for this region.

View file

@ -53,11 +53,12 @@ use crate::types::diagnostic::{
CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION,
DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE,
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, INVALID_PARAMETER_DEFAULT,
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS,
IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, report_bad_dunder_set_call,
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_NAMED_TUPLE,
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE,
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, UNDEFINED_REVEAL,
UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE,
UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, report_bad_dunder_set_call,
report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type,
report_instance_layout_conflict, report_invalid_assignment,
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
@ -95,7 +96,7 @@ use crate::types::{
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind,
TypedDictType, UnionBuilder, UnionType, binding_type, todo_type,
TypeVarVariance, TypedDictType, UnionBuilder, UnionType, binding_type, todo_type,
};
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
use crate::unpack::{EvaluationMode, UnpackPosition};
@ -212,9 +213,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
/// The list should only contain one entry per declaration at most.
declarations: VecMap<Definition<'db>, TypeAndQualifiers<'db>>,
/// The definitions that are deferred.
/// The definitions with deferred sub-parts.
///
/// The list should only contain one entry per deferred.
/// The list should only contain one entry per definition.
deferred: VecSet<Definition<'db>>,
/// The returned types and their corresponding ranges of the region, if it is a function body.
@ -497,8 +498,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// Infer the deferred types for the definitions here to consider the end-of-scope
// semantics.
// Infer deferred types for all definitions.
for definition in std::mem::take(&mut self.deferred) {
self.extend_definition(infer_deferred_types(self.db(), definition));
}
@ -1245,6 +1245,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
DefinitionKind::TypeVar(typevar) => {
self.infer_typevar_deferred(typevar.node(self.module()));
}
DefinitionKind::Assignment(assignment) => {
self.infer_assignment_deferred(assignment.value(self.module()));
}
_ => {}
}
}
@ -2961,6 +2964,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
None,
default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy),
TypeVarKind::Pep695,
None,
)));
self.add_declaration_with_binding(
node.into(),
@ -3993,7 +3997,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
unpacked.expression_type(target)
}
TargetKind::Single => {
let value_ty = self.infer_standalone_expression(value, TypeContext::default());
let tcx = TypeContext::default();
let value_ty = if let Some(standalone_expression) = self.index.try_expression(value)
{
self.infer_standalone_expression_impl(value, standalone_expression, tcx)
} else if let ast::Expr::Call(call_expr) = value {
// If the RHS is not a standalone expression, this is a simple assignment
// (single target, no unpackings). That means it's a valid syntactic form
// for a legacy TypeVar creation; check for that.
let callable_type = self.infer_maybe_standalone_expression(
call_expr.func.as_ref(),
TypeContext::default(),
);
let typevar_class = callable_type
.into_class_literal()
.and_then(|cls| cls.known(self.db()))
.filter(|cls| {
matches!(cls, KnownClass::TypeVar | KnownClass::ExtensionsTypeVar)
});
let ty = if let Some(typevar_class) = typevar_class {
self.infer_legacy_typevar(target, call_expr, definition, typevar_class)
} else {
self.infer_call_expression_impl(call_expr, callable_type, tcx)
};
self.store_expression_type(value, ty);
ty
} else {
self.infer_expression(value, tcx)
};
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
// at runtime, but is always considered `True` in type checking.
@ -4024,6 +4057,272 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.add_binding(target.into(), definition, target_ty);
}
fn infer_legacy_typevar(
&mut self,
target: &ast::Expr,
call_expr: &ast::ExprCall,
definition: Definition<'db>,
known_class: KnownClass,
) -> Type<'db> {
fn error<'db>(
context: &InferContext<'db, '_>,
message: impl std::fmt::Display,
node: impl Ranged,
) -> Type<'db> {
if let Some(builder) = context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, node) {
builder.into_diagnostic(message);
}
// If the call doesn't create a valid typevar, we'll emit diagnostics and fall back to
// just creating a regular instance of `typing.TypeVar`.
KnownClass::TypeVar.to_instance(context.db())
}
let db = self.db();
let arguments = &call_expr.arguments;
let is_typing_extensions = known_class == KnownClass::ExtensionsTypeVar;
let assume_all_features = self.in_stub() || is_typing_extensions;
let python_version = Program::get(db).python_version(db);
let have_features_from =
|version: PythonVersion| assume_all_features || python_version >= version;
let mut has_bound = false;
let mut default = None;
let mut covariant = false;
let mut contravariant = false;
let mut name_param_ty = None;
if let Some(starred) = arguments.args.iter().find(|arg| arg.is_starred_expr()) {
return error(
&self.context,
"Starred arguments are not supported in `TypeVar` creation",
starred,
);
}
for kwarg in &arguments.keywords {
let Some(identifier) = kwarg.arg.as_ref() else {
return error(
&self.context,
"Starred arguments are not supported in `TypeVar` creation",
kwarg,
);
};
match identifier.id().as_str() {
"name" => {
// Duplicate keyword argument is a syntax error, so we don't have to check if
// `name_param_ty.is_some()` here.
if !arguments.args.is_empty() {
return error(
&self.context,
"The `name` parameter of `TypeVar` can only be provided once.",
kwarg,
);
}
name_param_ty =
Some(self.infer_expression(&kwarg.value, TypeContext::default()));
}
"bound" => has_bound = true,
"covariant" => {
match self
.infer_expression(&kwarg.value, TypeContext::default())
.bool(db)
{
Truthiness::AlwaysTrue => covariant = true,
Truthiness::AlwaysFalse => {}
Truthiness::Ambiguous => {
return error(
&self.context,
"The `covariant` parameter of `TypeVar` \
cannot have an ambiguous truthiness",
&kwarg.value,
);
}
}
}
"contravariant" => {
match self
.infer_expression(&kwarg.value, TypeContext::default())
.bool(db)
{
Truthiness::AlwaysTrue => contravariant = true,
Truthiness::AlwaysFalse => {}
Truthiness::Ambiguous => {
return error(
&self.context,
"The `contravariant` parameter of `TypeVar` \
cannot have an ambiguous truthiness",
&kwarg.value,
);
}
}
}
"default" => {
if !have_features_from(PythonVersion::PY313) {
// We don't return here; this error is informational since this will error
// at runtime, but the user's intent is plain, we may as well respect it.
error(
&self.context,
"The `default` parameter of `typing.TypeVar` was added in Python 3.13",
kwarg,
);
}
default = Some(TypeVarDefaultEvaluation::Lazy);
}
"infer_variance" => {
if !have_features_from(PythonVersion::PY312) {
// We don't return here; this error is informational since this will error
// at runtime, but the user's intent is plain, we may as well respect it.
error(
&self.context,
"The `infer_variance` parameter of `typing.TypeVar` was added in Python 3.12",
kwarg,
);
}
// TODO support `infer_variance` in legacy TypeVars
if self
.infer_expression(&kwarg.value, TypeContext::default())
.bool(db)
.is_ambiguous()
{
return error(
&self.context,
"The `infer_variance` parameter of `TypeVar` \
cannot have an ambiguous truthiness",
&kwarg.value,
);
}
}
name => {
// We don't return here; this error is informational since this will error
// at runtime, but it will likely cause fewer cascading errors if we just
// ignore the unknown keyword and still understand as much of the typevar as we
// can.
error(
&self.context,
format_args!("Unknown keyword argument `{name}` in `TypeVar` creation",),
kwarg,
);
self.infer_expression(&kwarg.value, TypeContext::default());
}
}
}
let variance = match (covariant, contravariant) {
(true, true) => {
return error(
&self.context,
"A `TypeVar` cannot be both covariant and contravariant",
call_expr,
);
}
(true, false) => TypeVarVariance::Covariant,
(false, true) => TypeVarVariance::Contravariant,
(false, false) => TypeVarVariance::Invariant,
};
let Some(name_param_ty) = name_param_ty.or_else(|| {
arguments
.find_positional(0)
.map(|arg| self.infer_expression(arg, TypeContext::default()))
}) else {
return error(
&self.context,
"The `name` parameter of `TypeVar` is required.",
call_expr,
);
};
let Some(name_param) = name_param_ty
.into_string_literal()
.map(|name| name.value(db))
else {
return error(
&self.context,
"The first argument to `TypeVar` must be a string literal.",
call_expr,
);
};
let ast::Expr::Name(ast::ExprName {
id: target_name, ..
}) = target
else {
return error(
&self.context,
"A `TypeVar` definition must be a simple variable assignment",
target,
);
};
if name_param != target_name {
return error(
&self.context,
format_args!(
"The name of a `TypeVar` (`{name_param}`) must match \
the name of the variable it is assigned to (`{target_name}`)"
),
target,
);
}
// Inference of bounds, constraints, and defaults must be deferred, to avoid cycles. So we
// only check presence/absence/number here.
let num_constraints = arguments.args.len().saturating_sub(1);
let bound_or_constraints = match (has_bound, num_constraints) {
(false, 0) => None,
(true, 0) => Some(TypeVarBoundOrConstraintsEvaluation::LazyUpperBound),
(true, _) => {
return error(
&self.context,
"A `TypeVar` cannot have both a bound and constraints",
call_expr,
);
}
(_, 1) => {
return error(
&self.context,
"A `TypeVar` cannot have exactly one constraint",
&arguments.args[1],
);
}
(false, _) => Some(TypeVarBoundOrConstraintsEvaluation::LazyConstraints),
};
if bound_or_constraints.is_some() || default.is_some() {
self.deferred.insert(definition);
}
Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
db,
target_name,
Some(definition),
bound_or_constraints,
Some(variance),
default,
TypeVarKind::Legacy,
None,
)))
}
fn infer_assignment_deferred(&mut self, value: &ast::Expr) {
// Infer deferred bounds/constraints/defaults of a legacy TypeVar.
let ast::Expr::Call(ast::ExprCall { arguments, .. }) = value else {
return;
};
for arg in arguments.args.iter().skip(1) {
self.infer_type_expression(arg);
}
if let Some(bound) = arguments.find_keyword("bound") {
self.infer_type_expression(&bound.value);
}
if let Some(default) = arguments.find_keyword("default") {
self.infer_type_expression(&default.value);
}
}
fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) {
if assignment.target.is_name_expr() {
self.infer_definition(assignment);
@ -6045,6 +6344,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
&mut self,
call_expression: &ast::ExprCall,
tcx: TypeContext<'db>,
) -> Type<'db> {
// TODO: Use the type context for more precise inference.
let callable_type =
self.infer_maybe_standalone_expression(&call_expression.func, TypeContext::default());
self.infer_call_expression_impl(call_expression, callable_type, tcx)
}
fn infer_call_expression_impl(
&mut self,
call_expression: &ast::ExprCall,
callable_type: Type<'db>,
tcx: TypeContext<'db>,
) -> Type<'db> {
let ast::ExprCall {
range: _,
@ -6065,9 +6377,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
ty
});
// TODO: Use the type context for more precise inference.
let callable_type = self.infer_maybe_standalone_expression(func, TypeContext::default());
// Special handling for `TypedDict` method calls
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
let value_type = self.expression_type(value);
@ -6171,7 +6480,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::Object
| KnownClass::Property
| KnownClass::Super
| KnownClass::TypeVar
| KnownClass::TypeAliasType
| KnownClass::Deprecated
)
@ -6194,6 +6502,24 @@ 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);
if matches!(
class.known(self.db()),
Some(KnownClass::TypeVar | KnownClass::ExtensionsTypeVar)
) {
// Inference of correctly-placed `TypeVar` definitions is done in
// `TypeInferenceBuilder::infer_legacy_typevar`, and doesn't use the full
// call-binding machinery. If we reach here, it means that someone is trying to
// instantiate a `typing.TypeVar` in an invalid context.
if let Some(builder) = self
.context
.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
{
builder.into_diagnostic(
"A `TypeVar` definition must be a simple variable assignment",
);
}
}
return callable_type
.try_call_constructor(self.db(), call_arguments, tcx)
.unwrap_or_else(|err| {
@ -6253,7 +6579,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
&self.context,
self.index,
overload,
&call_arguments,
call_expression,
);
}
@ -9353,7 +9678,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
assert!(
deferred.is_empty(),
"Expression region can't have deferred types"
"Expression region can't have deferred definitions"
);
let extra =

View file

@ -418,7 +418,8 @@ fn dependency_implicit_instance_attribute() -> anyhow::Result<()> {
"/src/main.py",
r#"
from mod import C
x = C().attr
# multiple targets ensures RHS is a standalone expression, relied on by this test
x = y = C().attr
"#,
)?;
@ -508,7 +509,8 @@ fn dependency_own_instance_member() -> anyhow::Result<()> {
"/src/main.py",
r#"
from mod import C
x = C().attr
# multiple targets ensures RHS is a standalone expression, relied on by this test
x = y = C().attr
"#,
)?;
@ -603,7 +605,8 @@ fn dependency_implicit_class_member() -> anyhow::Result<()> {
r#"
from mod import C
C.method()
x = C().class_attr
# multiple targets ensures RHS is a standalone expression, relied on by this test
x = y = C().class_attr
"#,
)?;
@ -688,7 +691,8 @@ fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
r#"
from foo import foo
a = foo()
# multiple targets ensures RHS is a standalone expression, relied on by this test
a = b = foo()
"#,
)?;