mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:21 +00:00
move typevar arms around
This commit is contained in:
parent
19c421a27b
commit
b1a987318f
3 changed files with 71 additions and 93 deletions
|
@ -279,7 +279,7 @@ T = TypeVar("T", int, str)
|
||||||
|
|
||||||
def same_constrained_types(t1: T, t2: T) -> T:
|
def same_constrained_types(t1: T, t2: T) -> T:
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`"
|
# error: [invalid-return-type] "Return type does not match returned value: expected `T@same_constrained_types`, found `int | Unknown`"
|
||||||
return t1 + t2
|
return t1 + t2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ methods that are compatible with the return type, so the `return` expression is
|
||||||
```py
|
```py
|
||||||
def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T:
|
def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T:
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`"
|
# error: [invalid-return-type] "Return type does not match returned value: expected `T@same_constrained_types`, found `int | Unknown`"
|
||||||
return t1 + t2
|
return t1 + t2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1568,85 +1568,6 @@ impl<'db> Type<'db> {
|
||||||
Type::NonInferableTypeVar(rhs_bound_typevar),
|
Type::NonInferableTypeVar(rhs_bound_typevar),
|
||||||
) if lhs_bound_typevar == rhs_bound_typevar => ConstraintSet::from(true),
|
) if lhs_bound_typevar == 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
|
|
||||||
// implicit upper bound of `object` (which is handled above).
|
|
||||||
(Type::NonInferableTypeVar(bound_typevar), _)
|
|
||||||
if bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
|
|
||||||
{
|
|
||||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
|
||||||
None => unreachable!(),
|
|
||||||
// Verify that the upper bound satisfies the relation. (If it does, than any
|
|
||||||
// subtype of the upper bound will too.) Also add a safety constraint that
|
|
||||||
// ensures that the typevar specializes to a subtype of the upper bound.
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
|
||||||
ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, bound)
|
|
||||||
.implies(db, || {
|
|
||||||
bound.has_relation_to_impl(db, target, relation, visitor)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
|
||||||
constraints.elements(db).iter().when_all(db, |constraint| {
|
|
||||||
// Note that constrained typevars must specialize to _exactly_ one of the
|
|
||||||
// constraints, not to a _subtype_ of one.
|
|
||||||
ConstraintSet::constrain_typevar(
|
|
||||||
db,
|
|
||||||
bound_typevar,
|
|
||||||
*constraint,
|
|
||||||
*constraint,
|
|
||||||
)
|
|
||||||
.implies(db, || {
|
|
||||||
constraint.has_relation_to_impl(db, target, relation, visitor)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the typevar is constrained, there must be multiple constraints, and the typevar
|
|
||||||
// might be specialized to any one of them. However, the constraints do not have to be
|
|
||||||
// disjoint, which means an lhs type might be a subtype of all of the constraints.
|
|
||||||
(_, Type::NonInferableTypeVar(bound_typevar))
|
|
||||||
if !bound_typevar
|
|
||||||
.typevar(db)
|
|
||||||
.constraints(db)
|
|
||||||
.when_some_and(|constraints| {
|
|
||||||
constraints.iter().when_all(db, |constraint| {
|
|
||||||
ConstraintSet::constrain_typevar(
|
|
||||||
db,
|
|
||||||
bound_typevar,
|
|
||||||
*constraint,
|
|
||||||
*constraint,
|
|
||||||
)
|
|
||||||
.implies(db, || {
|
|
||||||
self.has_relation_to_impl(db, *constraint, relation, visitor)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.is_never_satisfied() =>
|
|
||||||
{
|
|
||||||
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
|
|
||||||
// where this arm only engages if it returns true (or in the world of constraints,
|
|
||||||
// not false). Once we're using real constraint sets instead of bool, we should be
|
|
||||||
// able to simplify the typevar logic.
|
|
||||||
bound_typevar
|
|
||||||
.typevar(db)
|
|
||||||
.constraints(db)
|
|
||||||
.when_some_and(|constraints| {
|
|
||||||
constraints.iter().when_all(db, |constraint| {
|
|
||||||
ConstraintSet::constrain_typevar(
|
|
||||||
db,
|
|
||||||
bound_typevar,
|
|
||||||
*constraint,
|
|
||||||
*constraint,
|
|
||||||
)
|
|
||||||
.implies(db, || {
|
|
||||||
self.has_relation_to_impl(db, *constraint, relation, visitor)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
(Type::TypeVar(_), _) if relation.is_assignability() => {
|
(Type::TypeVar(_), _) if relation.is_assignability() => {
|
||||||
// The implicit lower bound of a typevar is `Never`, which means
|
// The implicit lower bound of a typevar is `Never`, which means
|
||||||
// that it is always assignable to any other type.
|
// that it is always assignable to any other type.
|
||||||
|
@ -1656,9 +1577,6 @@ impl<'db> Type<'db> {
|
||||||
ConstraintSet::from(true)
|
ConstraintSet::from(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Never` is the bottom type, the empty set.
|
|
||||||
(_, Type::Never) => ConstraintSet::from(false),
|
|
||||||
|
|
||||||
(Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| {
|
(Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| {
|
||||||
elem_ty.has_relation_to_impl(db, target, relation, visitor)
|
elem_ty.has_relation_to_impl(db, target, relation, visitor)
|
||||||
}),
|
}),
|
||||||
|
@ -1686,12 +1604,74 @@ impl<'db> Type<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other than the special cases checked above, no other types are a subtype of a
|
// A non-inferable typevar must satisfy the relation for every possible specialization.
|
||||||
// typevar, since there's no guarantee what type the typevar will be specialized to.
|
(Type::NonInferableTypeVar(bound_typevar), _) => {
|
||||||
// (If the typevar is bounded, it might be specialized to a smaller type than the
|
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
// bound. This is true even if the bound is a final class, since the typevar can still
|
// Verify that the upper bound satisfies the relation. (If it does, than any
|
||||||
// be specialized to `Never`.)
|
// subtype of the upper bound will too.)
|
||||||
(_, Type::NonInferableTypeVar(_)) => ConstraintSet::from(false),
|
None => {
|
||||||
|
let bound = Type::object();
|
||||||
|
ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, bound)
|
||||||
|
.implies(db, || {
|
||||||
|
bound.has_relation_to_impl(db, target, relation, visitor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, bound)
|
||||||
|
.implies(db, || {
|
||||||
|
bound.has_relation_to_impl(db, target, relation, visitor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Verify that each constraint satisfies the relation. (Note that constrained
|
||||||
|
// typevars must specialize to _exactly_ one of the constraints, not to a
|
||||||
|
// _subtype_ of one.)
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
constraints.elements(db).iter().when_all(db, |constraint| {
|
||||||
|
ConstraintSet::constrain_typevar(
|
||||||
|
db,
|
||||||
|
bound_typevar,
|
||||||
|
*constraint,
|
||||||
|
*constraint,
|
||||||
|
)
|
||||||
|
.implies(db, || {
|
||||||
|
constraint.has_relation_to_impl(db, target, relation, visitor)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(_, Type::NonInferableTypeVar(bound_typevar)) => {
|
||||||
|
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
// There is no guarantee which type an unconstrained typevar might specialize
|
||||||
|
// to. Never (which is checked above) is the only type that is a subtype of any
|
||||||
|
// specialization.
|
||||||
|
None | Some(TypeVarBoundOrConstraints::UpperBound(_)) => {
|
||||||
|
ConstraintSet::from(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A constrained typevar, on the other hand, does have a fixed set of types
|
||||||
|
// that it can specialize to, so we can check them all exhaustively.
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
constraints.elements(db).iter().when_all(db, |constraint| {
|
||||||
|
ConstraintSet::constrain_typevar(
|
||||||
|
db,
|
||||||
|
bound_typevar,
|
||||||
|
*constraint,
|
||||||
|
*constraint,
|
||||||
|
)
|
||||||
|
.implies(db, || {
|
||||||
|
self.has_relation_to_impl(db, *constraint, relation, visitor)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Never` is the bottom type, the empty set.
|
||||||
|
// Other than one unlikely edge case (TypeVars bound to `Never`),
|
||||||
|
// no other type is a subtype of or assignable to `Never`.
|
||||||
|
(_, Type::Never) => ConstraintSet::from(false),
|
||||||
|
|
||||||
(_, Type::TypeVar(typevar))
|
(_, Type::TypeVar(typevar))
|
||||||
if relation.is_assignability()
|
if relation.is_assignability()
|
||||||
|
@ -1968,9 +1948,7 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
// Other than the special cases enumerated above, `Instance` types and typevars are
|
// Other than the special cases enumerated above, `Instance` types and typevars are
|
||||||
// never subtypes of any other variants
|
// never subtypes of any other variants
|
||||||
(Type::NominalInstance(_) | Type::NonInferableTypeVar(_), _) => {
|
(Type::NominalInstance(_), _) => ConstraintSet::from(false),
|
||||||
ConstraintSet::from(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue