mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:04:51 +00:00
WIP: working without standalone
This commit is contained in:
parent
dd8b555fa5
commit
bc4930e9a3
9 changed files with 376 additions and 240 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
```
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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()
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue