WIP: working without standalone

This commit is contained in:
Carl Meyer 2025-09-27 13:05:03 -07:00
parent dd8b555fa5
commit bc4930e9a3
No known key found for this signature in database
GPG key ID: 2D1FB7916A52E121
9 changed files with 376 additions and 240 deletions

View file

@ -1,5 +1,12 @@
# Generic classes: Legacy syntax
We use TypeVar defaults here, which was added in Python 3.13 for legacy typevars.
```toml
[environment]
python-version = "3.13"
```
## Defining a generic class
At its simplest, to define a generic class using the legacy syntax, you inherit from the

View file

@ -33,13 +33,12 @@ reveal_type(T.__name__) # revealed: Literal["T"]
from typing import TypeVar
T = TypeVar("T")
# TODO: no error
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
TestList = list[TypeVar("W")]
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
```
### `TypeVar` parameter must match variable name
@ -49,7 +48,7 @@ TestList = list[TypeVar("W")]
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
# error: [invalid-legacy-type-variable]
T = TypeVar("Q")
```
@ -66,6 +65,22 @@ T = TypeVar("T")
T = TypeVar("T")
```
### No variadic arguments
```py
from typing import TypeVar
types = (int, str)
# error: [invalid-legacy-type-variable]
T = TypeVar("T", *types)
reveal_type(T) # revealed: TypeVar
# error: [invalid-legacy-type-variable]
S = TypeVar("S", **{"bound": int})
reveal_type(S) # revealed: TypeVar
```
### Type variables with a default
Note that the `__default__` property is only available in Python ≥3.13.
@ -91,6 +106,11 @@ reveal_type(S.__default__) # revealed: NoDefault
### Using other typevars as a default
```toml
[environment]
python-version = "3.13"
```
```py
from typing import Generic, TypeVar, Union
@ -122,6 +142,20 @@ reveal_type(T.__constraints__) # revealed: tuple[()]
S = TypeVar("S")
reveal_type(S.__bound__) # revealed: None
from typing import TypedDict
# error: [invalid-type-form]
T = TypeVar("T", bound=TypedDict)
```
The upper bound must be a valid type expression:
```py
from typing import TypedDict
# error: [invalid-type-form]
T = TypeVar("T", bound=TypedDict)
```
### Type variables with constraints
@ -144,8 +178,8 @@ Constraints are not simplified relative to each other, even if one is a subtype
T = TypeVar("T", int, bool)
reveal_type(T.__constraints__) # revealed: tuple[int, bool]
S = TypeVar("S", float)
reveal_type(S.__constraints__) # revealed: tuple[int | float]
S = TypeVar("S", float, str)
reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
```
### Cannot have only one constraint
@ -156,10 +190,19 @@ reveal_type(S.__constraints__) # revealed: tuple[int | float]
```py
from typing import TypeVar
# TODO: error: [invalid-type-variable-constraints]
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int)
```
### Cannot have both bound and constraint
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int, str, bound=bytes)
```
### Cannot be both covariant and contravariant
> To facilitate the declaration of container types where covariant or contravariant type checking is
@ -188,6 +231,46 @@ T = TypeVar("T", covariant=cond())
U = TypeVar("U", contravariant=cond())
```
### Invalid keyword arguments
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
```pyi
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
### Constructor signature versioning
```toml
[environment]
python-version = "3.10"
```
In a stub file, features from the latest supported Python version can be used on any version:
```pyi
from typing import TypeVar
T = TypeVar("T", default=int)
```
But this raises an error in a non-stub file:
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", default=int)
```
## Callability
A typevar bound to a Callable type is callable:
@ -256,13 +339,10 @@ S = TypeVar("S")
T = TypeVar("T", bound=list[S])
# TODO: error
U = TypeVar("U", list["T"])
U = TypeVar("U", list["T"], str)
# TODO: error
V = TypeVar("V", list[S], str)
# TODO: error
W = TypeVar("W", list["W"], str)
V = TypeVar("V", list["V"], str)
```
However, they are lazily evaluated and can cyclically refer to their own type:
@ -280,6 +360,11 @@ reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
### Defaults
```toml
[environment]
python-version = "3.13"
```
Defaults can be generic, but can only refer to earlier typevars:
```py

View file

@ -343,7 +343,7 @@ def _[T]() -> None:
reveal_type(negated_range_constraint(Sub, T, Base) & negated_range_constraint(Base, T, Super))
# revealed: ty_extensions.ConstraintSet[(¬(Base ≤ T@_ ≤ Super) ∧ ¬(SubSub ≤ T@_ ≤ Sub))]
reveal_type(negated_range_constraint(SubSub, T, Sub) & negated_range_constraint(Base, T, Super))
# revealed: ty_extensions.ConstraintSet[(¬(SubSub ≤ T@_ ≤ Sub) ∧ ¬(Unrelated ≤ T@_))]
# revealed: ty_extensions.ConstraintSet[(¬(Unrelated ≤ T@_) ∧ ¬(SubSub ≤ T@_ ≤ Sub))]
reveal_type(negated_range_constraint(SubSub, T, Sub) & negated_range_constraint(Unrelated, T, object))
```
@ -421,7 +421,7 @@ def _[T]() -> None:
reveal_type(range_constraint(Sub, T, Base) | range_constraint(Base, T, Super))
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super) (SubSub ≤ T@_ ≤ Sub)]
reveal_type(range_constraint(SubSub, T, Sub) | range_constraint(Base, T, Super))
# revealed: ty_extensions.ConstraintSet[(SubSub ≤ T@_ ≤ Sub) (Unrelated ≤ T@_)]
# revealed: ty_extensions.ConstraintSet[(Unrelated ≤ T@_) (SubSub ≤ T@_ ≤ Sub)]
reveal_type(range_constraint(SubSub, T, Sub) | range_constraint(Unrelated, T, object))
```

View file

@ -1626,9 +1626,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
self.visit_expr(&node.value);
// Optimization for the common case: if there's just one target, and it's not an
// unpacking, we don't need the RHS to be a standalone expression at all.
// 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[..]
&& !matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_))
&& target.is_name_expr()
{
self.push_assignment(CurrentAssignment::Assign { node, unpack: None });
self.visit_expr(target);

View file

@ -4493,56 +4493,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"))
.deferred_type_form()
.with_annotated_type(Type::any()),
Parameter::keyword_only(Name::new_static("bound"))
.deferred_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"))
.deferred_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:

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,
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext,
TypeMapping, TypeRelation, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation,
TypeVarInstance, TypeVarKind, TypedDictParams, UnionBuilder, VarianceInferable,
declaration_type, determine_upper_bound, infer_definition_types,
TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type,
determine_upper_bound, infer_definition_types,
};
use crate::{
Db, FxIndexMap, FxOrderSet, Program,
@ -4980,6 +4979,7 @@ impl KnownClass {
_ => {}
}
}
KnownClass::Deprecated => {
// Parsing something of the form:
//
@ -5006,136 +5006,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(_), None) => Some(TypeVarBoundOrConstraintsEvaluation::LazyUpperBound),
(None, Some(_)) => Some(TypeVarBoundOrConstraintsEvaluation::LazyConstraints),
// 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(|_| TypeVarDefaultEvaluation::Lazy),
TypeVarKind::Legacy,
),
)));
}
KnownClass::TypeAliasType => {
let assigned_to = index

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, 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, 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, TypeMapping, TypeQualifiers,
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind,
UnionBuilder, UnionType, binding_type, todo_type,
TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
};
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
use crate::unpack::{EvaluationMode, UnpackPosition};
@ -375,15 +376,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.scope
}
fn definition(&self) -> Option<Definition<'db>> {
match self.region {
InferenceRegion::Definition(definition) | InferenceRegion::Deferred(definition) => {
Some(definition)
}
_ => None,
}
}
/// Are we currently inferring types in file with deferred types?
/// This is true for stub files and files with `__future__.annotations`
fn defer_annotations(&self) -> bool {
@ -3967,8 +3959,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
unpacked.expression_type(target)
}
TargetKind::Single => {
let value_ty =
self.infer_maybe_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 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.
if let Some(call_expr) = value.as_call_expr() {
let callable_type = self.infer_maybe_standalone_expression(
call_expr.func.as_ref(),
TypeContext::default(),
);
let ty = if callable_type
.into_class_literal()
.is_some_and(|cls| cls.is_known(self.db(), KnownClass::TypeVar))
{
self.infer_legacy_typevar(target, call_expr, definition)
} 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.
@ -3999,8 +4016,199 @@ 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>,
) -> 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 mut has_bound = false;
let mut default = None;
let mut covariant = false;
let mut contravariant = false;
for kwarg in &arguments.keywords {
let Some(identifier) = kwarg.arg.as_ref() else {
return error(
&self.context,
"Starred arguments are not supported in legacy `typing.TypeVar` creation",
kwarg,
);
};
match identifier.id().as_str() {
"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 a legacy `typing.TypeVar` \
cannot have an ambiguous value",
kwarg,
);
}
}
}
"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 a legacy `typing.TypeVar` \
cannot have an ambiguous value",
kwarg,
);
}
}
}
"default" => {
if !self.in_stub() && Program::get(db).python_version(db) < PythonVersion::PY313
{
return error(
&self.context,
"TypeVar `default` keyword was added in Python 3.13",
kwarg,
);
}
default = Some(TypeVarDefaultEvaluation::Lazy);
}
"infer_variance" => {
if !self.in_stub() && Program::get(db).python_version(db) < PythonVersion::PY312
{
return error(
&self.context,
"TypeVar `infer_variance` keyword was added in Python 3.12",
kwarg,
);
}
// TODO support `infer_variance` in legacy TypeVars
}
name => {
return error(
&self.context,
format_args!(
"Unknown keyword argument `{name}` in legacy `typing.TypeVar` creation",
),
kwarg,
);
}
}
}
let variance = match (covariant, contravariant) {
(true, true) => {
return error(
&self.context,
"A legacy `typing.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) = arguments
.find_positional(0)
.map(|arg| self.infer_expression(arg, TypeContext::default()))
.and_then(Type::into_string_literal)
.map(|name| name.value(db))
else {
return error(
&self.context,
"The first argument to a legacy `typing.TypeVar` must be a string literal.",
call_expr,
);
};
let Some(target_name) = target.as_name_expr().map(|n| &n.id) else {
return error(
&self.context,
"A legacy `typing.TypeVar` must be assigned to a variable",
target,
);
};
if name_param != target_name {
return error(
&self.context,
format_args!(
"The name of a legacy `typing.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() - 1;
let bound_or_constraints = match (has_bound, num_constraints) {
(false, 0) => None,
(true, 0) => Some(TypeVarBoundOrConstraintsEvaluation::LazyUpperBound),
(true, _) => {
return error(
&self.context,
"A legacy `typing.TypeVar` cannot have both a bound and constraints",
call_expr,
);
}
(_, 1) => {
return error(
&self.context,
"A legacy `typing.TypeVar` cannot have exactly one constraint",
call_expr,
);
}
(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,
)))
}
fn infer_assignment_deferred(&mut self, value: &ast::Expr) {
// infer deferred bounds/constraints/defaults of a legacy TypeVar
// Infer deferred bounds/constraints/defaults of a legacy TypeVar.
let Some(call_expr) = value.as_call_expr() else {
return;
};
@ -4987,12 +5195,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
match form {
None | Some(ParameterForm::Value) => self.infer_expression(ast_argument, tcx),
Some(ParameterForm::Type) => self.infer_type_expression(ast_argument),
Some(ParameterForm::TypeDeferred) => {
if let Some(definition) = self.definition() {
self.deferred.insert(definition);
}
Type::unknown()
}
}
}
@ -5861,6 +6063,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: _,
@ -5881,9 +6096,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);
@ -5987,7 +6199,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::Object
| KnownClass::Property
| KnownClass::Super
| KnownClass::TypeVar
| KnownClass::TypeAliasType
| KnownClass::Deprecated
)
@ -6010,6 +6221,21 @@ 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 class.is_known(self.db(), KnownClass::TypeVar) {
// 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 legacy `typing.TypeVar` must be immediately assigned to a variable",
);
}
}
return callable_type
.try_call_constructor(self.db(), call_arguments)
.unwrap_or_else(|err| {

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,7 @@ fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
r#"
from foo import foo
a = foo()
a = b = foo()
"#,
)?;

View file

@ -1032,7 +1032,7 @@ impl<'db> VarianceInferable<'db> for &Signature<'db> {
self.parameters
.iter()
.filter_map(|parameter| match parameter.form {
ParameterForm::Type | ParameterForm::TypeDeferred => None,
ParameterForm::Type => None,
ParameterForm::Value => parameter.annotated_type().map(|ty| {
ty.with_polarity(TypeVarVariance::Contravariant)
.variance_of(db, typevar)
@ -1472,11 +1472,6 @@ impl<'db> Parameter<'db> {
self
}
pub(crate) fn deferred_type_form(mut self) -> Self {
self.form = ParameterForm::TypeDeferred;
self
}
fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
@ -1728,7 +1723,6 @@ impl<'db> ParameterKind<'db> {
pub(crate) enum ParameterForm {
Value,
Type,
TypeDeferred,
}
#[cfg(test)]