Compare commits
24 Commits
alex/let-c
...
dcreager/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb07775617 | ||
|
|
180b38987c | ||
|
|
7bbe3d73fc | ||
|
|
d5089ccd4d | ||
|
|
1c247af1cf | ||
|
|
f735222ed6 | ||
|
|
f1188c74b1 | ||
|
|
e5533da00f | ||
|
|
138bf79857 | ||
|
|
e39f4654cb | ||
|
|
2c95befaff | ||
|
|
61aafffab5 | ||
|
|
47840fdd0c | ||
|
|
acd1e6c466 | ||
|
|
9ca1207667 | ||
|
|
83378f01b1 | ||
|
|
be4e7e773d | ||
|
|
6df88e8fd2 | ||
|
|
8888c3ce1d | ||
|
|
b6ca2d9050 | ||
|
|
2fa8636a2e | ||
|
|
c8664f68bb | ||
|
|
4ff828d996 | ||
|
|
517566a8ca |
@@ -17,8 +17,7 @@ from datetime import time
|
||||
t = time(12, 0, 0)
|
||||
t = replace(t, minute=30)
|
||||
|
||||
# TODO: this should be `time`, once we support specialization of generic protocols
|
||||
reveal_type(t) # revealed: Unknown
|
||||
reveal_type(t) # revealed: time
|
||||
```
|
||||
|
||||
## The `__replace__` protocol
|
||||
@@ -48,8 +47,7 @@ b = a.__replace__(x=3, y=4)
|
||||
reveal_type(b) # revealed: Point
|
||||
|
||||
b = replace(a, x=3, y=4)
|
||||
# TODO: this should be `Point`, once we support specialization of generic protocols
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Point
|
||||
```
|
||||
|
||||
A call to `replace` does not require all keyword arguments:
|
||||
@@ -59,8 +57,7 @@ c = a.__replace__(y=4)
|
||||
reveal_type(c) # revealed: Point
|
||||
|
||||
d = replace(a, y=4)
|
||||
# TODO: this should be `Point`, once we support specialization of generic protocols
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Point
|
||||
```
|
||||
|
||||
Invalid calls to `__replace__` or `replace` will raise an error:
|
||||
@@ -96,8 +93,7 @@ b = a.__replace__(x=3, y=4)
|
||||
reveal_type(b) # revealed: Point
|
||||
|
||||
b = replace(a, x=3, y=4)
|
||||
# TODO: this should be `Point`, once we support specialization of generic protocols
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Point
|
||||
```
|
||||
|
||||
Invalid calls to `__replace__` will raise an error:
|
||||
|
||||
@@ -561,7 +561,7 @@ from typing_extensions import overload, Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
U = TypeVar("U", covariant=True)
|
||||
|
||||
class C(Generic[T]):
|
||||
@overload
|
||||
@@ -611,9 +611,9 @@ reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D("string")) # revealed: D[str, str]
|
||||
reveal_type(D(1)) # revealed: D[str, int]
|
||||
reveal_type(D(1, "string")) # revealed: D[int, str]
|
||||
reveal_type(D("string")) # revealed: D[str, Literal["string"]]
|
||||
reveal_type(D(1)) # revealed: D[str, Literal[1]]
|
||||
reveal_type(D(1, "string")) # revealed: D[int, Literal["string"]]
|
||||
```
|
||||
|
||||
### Synthesized methods with dataclasses
|
||||
|
||||
@@ -90,13 +90,11 @@ def takes_in_protocol(x: CanIndex[T]) -> T:
|
||||
|
||||
def deep_list(x: list[str]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(x)) # revealed: str
|
||||
|
||||
def deeper_list(x: list[set[str]]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[set[str]]
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(x)) # revealed: set[str]
|
||||
|
||||
def deep_explicit(x: ExplicitlyImplements[str]) -> None:
|
||||
reveal_type(takes_in_protocol(x)) # revealed: str
|
||||
@@ -129,12 +127,10 @@ class Sub(list[int]): ...
|
||||
class GenericSub(list[T]): ...
|
||||
|
||||
reveal_type(takes_in_list(Sub())) # revealed: list[int]
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: int
|
||||
|
||||
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: str
|
||||
|
||||
class ExplicitSub(ExplicitlyImplements[int]): ...
|
||||
class ExplicitGenericSub(ExplicitlyImplements[T]): ...
|
||||
|
||||
@@ -538,6 +538,10 @@ C[None](b"bytes") # error: [no-matching-overload]
|
||||
C[None](12)
|
||||
|
||||
class D[T, U]:
|
||||
# we need to use the type variable or else the class is bivariant in T, and
|
||||
# specializations become meaningless
|
||||
x: T
|
||||
|
||||
@overload
|
||||
def __init__(self: "D[str, U]", u: U) -> None: ...
|
||||
@overload
|
||||
@@ -551,7 +555,7 @@ reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D("string")) # revealed: D[str, Literal["string"]]
|
||||
reveal_type(D(1)) # revealed: D[str, Literal[1]]
|
||||
reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]]
|
||||
reveal_type(D(1, "string")) # revealed: D[int, Literal["string"]]
|
||||
```
|
||||
|
||||
### Synthesized methods with dataclasses
|
||||
|
||||
@@ -85,13 +85,11 @@ def takes_in_protocol[T](x: CanIndex[T]) -> T:
|
||||
|
||||
def deep_list(x: list[str]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(x)) # revealed: str
|
||||
|
||||
def deeper_list(x: list[set[str]]) -> None:
|
||||
reveal_type(takes_in_list(x)) # revealed: list[set[str]]
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(x)) # revealed: set[str]
|
||||
|
||||
def deep_explicit(x: ExplicitlyImplements[str]) -> None:
|
||||
reveal_type(takes_in_protocol(x)) # revealed: str
|
||||
@@ -124,12 +122,10 @@ class Sub(list[int]): ...
|
||||
class GenericSub[T](list[T]): ...
|
||||
|
||||
reveal_type(takes_in_list(Sub())) # revealed: list[int]
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: int
|
||||
|
||||
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: str
|
||||
|
||||
class ExplicitSub(ExplicitlyImplements[int]): ...
|
||||
class ExplicitGenericSub[T](ExplicitlyImplements[T]): ...
|
||||
|
||||
@@ -1369,6 +1369,31 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of union clauses in this type. If the type is not a union, returns 1.
|
||||
pub(crate) fn union_size(self, db: &'db dyn Db) -> usize {
|
||||
self.as_union()
|
||||
.map(|union_type| union_type.elements(db).len())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
/// Returns the number of intersection clauses in this type. If the type is a union, this is
|
||||
/// the maximum of the `intersection_size` of each union element. If the type is not a union
|
||||
/// nor an intersection, returns 1.
|
||||
pub(crate) fn intersection_size(self, db: &'db dyn Db) -> usize {
|
||||
match self {
|
||||
Type::Intersection(intersection) => {
|
||||
intersection.positive(db).len() + intersection.negative(db).len()
|
||||
}
|
||||
Type::Union(union_type) => union_type
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|element| element.intersection_size(db))
|
||||
.max()
|
||||
.unwrap_or(1),
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn as_function_literal(self) -> Option<FunctionType<'db>> {
|
||||
match self {
|
||||
Type::FunctionLiteral(function_type) => Some(function_type),
|
||||
@@ -7895,6 +7920,16 @@ impl<'db> TypeVarInstance<'db> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the bounds or constraints of this typevar. If the typevar is unbounded, returns
|
||||
/// `object` as its upper bound.
|
||||
pub(crate) fn require_bound_or_constraints(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> TypeVarBoundOrConstraints<'db> {
|
||||
self.bound_or_constraints(db)
|
||||
.unwrap_or_else(|| TypeVarBoundOrConstraints::UpperBound(Type::object()))
|
||||
}
|
||||
|
||||
pub(crate) fn default_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
self._default(db).and_then(|d| match d {
|
||||
TypeVarDefaultEvaluation::Eager(ty) => Some(ty),
|
||||
|
||||
@@ -4489,7 +4489,6 @@ impl<'db> BindingError<'db> {
|
||||
return;
|
||||
};
|
||||
|
||||
let typevar = error.bound_typevar().typevar(context.db());
|
||||
let argument_type = error.argument_type();
|
||||
let argument_ty_display = argument_type.display(context.db());
|
||||
|
||||
@@ -4502,21 +4501,51 @@ impl<'db> BindingError<'db> {
|
||||
}
|
||||
));
|
||||
|
||||
let typevar_name = typevar.name(context.db());
|
||||
match error {
|
||||
SpecializationError::MismatchedBound { .. } => {
|
||||
diag.set_primary_message(format_args!("Argument type `{argument_ty_display}` does not satisfy upper bound `{}` of type variable `{typevar_name}`",
|
||||
typevar.upper_bound(context.db()).expect("type variable should have an upper bound if this error occurs").display(context.db())
|
||||
));
|
||||
SpecializationError::NoSolution { parameter, .. } => {
|
||||
diag.set_primary_message(format_args!(
|
||||
"Argument type `{argument_ty_display}` does not \
|
||||
satisfy generic parameter annotation `{}",
|
||||
parameter.display(context.db()),
|
||||
));
|
||||
}
|
||||
SpecializationError::MismatchedConstraint { .. } => {
|
||||
diag.set_primary_message(format_args!("Argument type `{argument_ty_display}` does not satisfy constraints ({}) of type variable `{typevar_name}`",
|
||||
typevar.constraints(context.db()).expect("type variable should have constraints if this error occurs").iter().map(|ty| format!("`{}`", ty.display(context.db()))).join(", ")
|
||||
));
|
||||
SpecializationError::MismatchedBound { bound_typevar, .. } => {
|
||||
let typevar = bound_typevar.typevar(context.db());
|
||||
let typevar_name = typevar.name(context.db());
|
||||
diag.set_primary_message(format_args!(
|
||||
"Argument type `{argument_ty_display}` does not \
|
||||
satisfy upper bound `{}` of type variable `{typevar_name}`",
|
||||
typevar
|
||||
.upper_bound(context.db())
|
||||
.expect(
|
||||
"type variable should have an upper bound if this error occurs"
|
||||
)
|
||||
.display(context.db())
|
||||
));
|
||||
}
|
||||
SpecializationError::MismatchedConstraint { bound_typevar, .. } => {
|
||||
let typevar = bound_typevar.typevar(context.db());
|
||||
let typevar_name = typevar.name(context.db());
|
||||
diag.set_primary_message(format_args!(
|
||||
"Argument type `{argument_ty_display}` does not \
|
||||
satisfy constraints ({}) of type variable `{typevar_name}`",
|
||||
typevar
|
||||
.constraints(context.db())
|
||||
.expect(
|
||||
"type variable should have constraints if this error occurs"
|
||||
)
|
||||
.iter()
|
||||
.format_with(", ", |ty, f| f(&format_args!(
|
||||
"`{}`",
|
||||
ty.display(context.db())
|
||||
)))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(typevar_definition) = typevar.definition(context.db()) {
|
||||
if let Some(typevar_definition) = error.bound_typevar().and_then(|bound_typevar| {
|
||||
bound_typevar.typevar(context.db()).definition(context.db())
|
||||
}) {
|
||||
let module = parsed_module(context.db(), typevar_definition.file(context.db()))
|
||||
.load(context.db());
|
||||
let typevar_range = typevar_definition.full_range(context.db(), &module);
|
||||
|
||||
@@ -72,6 +72,7 @@ use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ordermap::map::Entry;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use salsa::plumbing::AsId;
|
||||
|
||||
@@ -757,9 +758,25 @@ impl<'db> ConstrainedTypeVar<'db> {
|
||||
|
||||
/// Returns the intersection of two range constraints, or `None` if the intersection is empty.
|
||||
fn intersect(self, db: &'db dyn Db, other: Self) -> IntersectionResult<'db> {
|
||||
// TODO: For now, we treat some upper bounds as unsimplifiable if they become "too big".
|
||||
// When intersecting constraints, the upper bounds are also intersected together. If the
|
||||
// lhs and rhs upper bounds are unions of intersections (e.g. `(a & b) | (c & d)`), then
|
||||
// intersecting them together will require distributing across every pair of union
|
||||
// elements. That can quickly balloon in size. We are looking at a better representation
|
||||
// that would let us model this case more directly, but for now, we punt.
|
||||
const MAX_UPPER_BOUND_SIZE: usize = 4;
|
||||
let self_upper = self.upper(db);
|
||||
let other_upper = other.upper(db);
|
||||
let estimated_upper_bound_size = self_upper.union_size(db)
|
||||
* other_upper.union_size(db)
|
||||
* (self_upper.intersection_size(db) + other_upper.intersection_size(db));
|
||||
if estimated_upper_bound_size >= MAX_UPPER_BOUND_SIZE {
|
||||
return IntersectionResult::CannotSimplify;
|
||||
}
|
||||
|
||||
// (s₁ ≤ α ≤ t₁) ∧ (s₂ ≤ α ≤ t₂) = (s₁ ∪ s₂) ≤ α ≤ (t₁ ∩ t₂))
|
||||
let lower = UnionType::from_elements(db, [self.lower(db), other.lower(db)]);
|
||||
let upper = IntersectionType::from_elements(db, [self.upper(db), other.upper(db)]);
|
||||
let upper = IntersectionType::from_elements(db, [self_upper, other_upper]);
|
||||
|
||||
// If `lower ≰ upper`, then the intersection is empty, since there is no type that is both
|
||||
// greater than `lower`, and less than `upper`.
|
||||
@@ -767,6 +784,8 @@ impl<'db> ConstrainedTypeVar<'db> {
|
||||
return IntersectionResult::Disjoint;
|
||||
}
|
||||
|
||||
// We do not create lower bounds that are unions, or upper bounds that are intersections,
|
||||
// since those can be broken apart into BDDs over simpler constraints.
|
||||
if lower.is_union() || upper.is_nontrivial_intersection(db) {
|
||||
return IntersectionResult::CannotSimplify;
|
||||
}
|
||||
@@ -3437,9 +3456,11 @@ impl<'db> PathAssignments<'db> {
|
||||
);
|
||||
return Err(PathAssignmentConflict);
|
||||
}
|
||||
if self.assignments.insert(assignment, source_order).is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.assignments.entry(assignment) {
|
||||
Entry::Vacant(entry) => entry.insert(source_order),
|
||||
Entry::Occupied(_) => return Ok(()),
|
||||
};
|
||||
|
||||
// Then use our sequents to add additional facts that we know to be true. We currently
|
||||
// reuse the `source_order` of the "real" constraint passed into `walk_edge` when we add
|
||||
@@ -3741,6 +3762,11 @@ impl<'db> BoundTypeVarInstance<'db> {
|
||||
/// when this typevar is in inferable position, where we only need _some_ specialization to
|
||||
/// satisfy the constraint set.
|
||||
fn valid_specializations(self, db: &'db dyn Db) -> Node<'db> {
|
||||
if self.paramspec_attr(db).is_some() {
|
||||
// P.args and P.kwargs are variadic, and do not have an upper bound or constraints.
|
||||
return Node::AlwaysTrue;
|
||||
}
|
||||
|
||||
// For gradual upper bounds and constraints, we are free to choose any materialization that
|
||||
// makes the check succeed. In inferable positions, it is most helpful to choose a
|
||||
// materialization that is as permissive as possible, since that maximizes the number of
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::semantic_index::{SemanticIndex, semantic_index};
|
||||
use crate::types::class::ClassType;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
};
|
||||
@@ -1634,12 +1633,43 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
upper: FxOrderSet<Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> Bounds<'db> {
|
||||
fn add_lower(&mut self, _db: &'db dyn Db, ty: Type<'db>) {
|
||||
// Lower bounds are unioned. Our type representation is in DNF, so unioning a new
|
||||
// element is typically cheap (in that it does not involve a combinatorial
|
||||
// explosion from distributing the clause through an existing disjunction). So we
|
||||
// don't need to be as clever here as in `add_upper`.
|
||||
self.lower.insert(ty);
|
||||
}
|
||||
|
||||
fn add_upper(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
// Upper bounds are intersectioned. If `ty` is a union, that involves distributing
|
||||
// the union elements through the existing type. That makes it worth checking first
|
||||
// whether any of the types in the upper bound are redundant.
|
||||
|
||||
// First check if there's an existing upper bound clause that is a subtype of the
|
||||
// new type. If so, adding the new type does nothing to the intersection.
|
||||
if self
|
||||
.upper
|
||||
.iter()
|
||||
.any(|existing| existing.is_subtype_of(db, ty))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise remove any existing clauses that are a supertype of the new type,
|
||||
// since the intersection will clip them to the new type.
|
||||
self.upper
|
||||
.retain(|existing| !ty.is_subtype_of(db, *existing));
|
||||
self.upper.insert(ty);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the constraints in each path by their `source_order`s, to ensure that we construct
|
||||
// any unions or intersections in our type mappings in a stable order. Constraints might
|
||||
// come out of `PathAssignment`s with identical `source_order`s, but if they do, those
|
||||
// "tied" constraints will still be ordered in a stable way. So we need a stable sort to
|
||||
// retain that stable per-tie ordering.
|
||||
let constraints = constraints.limit_to_valid_specializations(self.db);
|
||||
let mut sorted_paths = Vec::new();
|
||||
constraints.for_each_path(self.db, |path| {
|
||||
let mut path: Vec<_> = path.positive_constraints().collect();
|
||||
@@ -1660,33 +1690,68 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
let lower = constraint.lower(self.db);
|
||||
let upper = constraint.upper(self.db);
|
||||
let bounds = mappings.entry(typevar).or_default();
|
||||
bounds.lower.insert(lower);
|
||||
bounds.upper.insert(upper);
|
||||
bounds.add_lower(self.db, lower);
|
||||
bounds.add_upper(self.db, upper);
|
||||
|
||||
if let Type::TypeVar(lower_bound_typevar) = lower {
|
||||
let bounds = mappings.entry(lower_bound_typevar).or_default();
|
||||
bounds.upper.insert(Type::TypeVar(typevar));
|
||||
bounds.add_upper(self.db, Type::TypeVar(typevar));
|
||||
}
|
||||
|
||||
if let Type::TypeVar(upper_bound_typevar) = upper {
|
||||
let bounds = mappings.entry(upper_bound_typevar).or_default();
|
||||
bounds.lower.insert(Type::TypeVar(typevar));
|
||||
bounds.add_lower(self.db, Type::TypeVar(typevar));
|
||||
}
|
||||
}
|
||||
|
||||
for (bound_typevar, bounds) in mappings.drain() {
|
||||
let variance = formal.variance_of(self.db, bound_typevar);
|
||||
// Prefer the lower bound (often the concrete actual type seen) over the
|
||||
// upper bound (which may include TypeVar bounds/constraints). The upper bound
|
||||
// should only be used as a fallback when no concrete type was inferred.
|
||||
let lower = UnionType::from_elements(self.db, bounds.lower);
|
||||
if !lower.is_never() {
|
||||
self.add_type_mapping(bound_typevar, lower, variance, &mut f);
|
||||
continue;
|
||||
}
|
||||
let upper = IntersectionType::from_elements(self.db, bounds.upper);
|
||||
if !upper.is_object() {
|
||||
self.add_type_mapping(bound_typevar, upper, variance, &mut f);
|
||||
match bound_typevar
|
||||
.typevar(self.db)
|
||||
.require_bound_or_constraints(self.db)
|
||||
{
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
||||
let bound = bound.top_materialization(self.db);
|
||||
let lower = UnionType::from_elements(self.db, bounds.lower);
|
||||
if !lower.is_assignable_to(self.db, bound) {
|
||||
// This path does not satisfy the typevar's upper bound, and is
|
||||
// therefore not a valid specialization.
|
||||
continue;
|
||||
}
|
||||
|
||||
let upper = IntersectionType::from_elements(
|
||||
self.db,
|
||||
bounds.upper.into_iter().chain([bound]),
|
||||
);
|
||||
if upper != bound {
|
||||
self.add_type_mapping(bound_typevar, upper, variance, &mut f);
|
||||
} else if !lower.is_never() {
|
||||
self.add_type_mapping(bound_typevar, lower, variance, &mut f);
|
||||
}
|
||||
}
|
||||
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
// Filter out the typevar constraints that aren't satisfied by this path.
|
||||
let lower = UnionType::from_elements(self.db, bounds.lower);
|
||||
let upper = IntersectionType::from_elements(self.db, bounds.upper);
|
||||
let compatible_constraints =
|
||||
constraints.elements(self.db).iter().filter(|constraint| {
|
||||
let constraint_lower = constraint.bottom_materialization(self.db);
|
||||
let constraint_upper = constraint.top_materialization(self.db);
|
||||
lower.is_assignable_to(self.db, constraint_lower)
|
||||
&& constraint_upper.is_assignable_to(self.db, upper)
|
||||
});
|
||||
|
||||
// If only one constraint remains, that's our specialization for this path.
|
||||
if let Ok(compatible_constraint) = compatible_constraints.exactly_one() {
|
||||
self.add_type_mapping(
|
||||
bound_typevar,
|
||||
*compatible_constraint,
|
||||
variance,
|
||||
&mut f,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1967,14 +2032,31 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
Type::NominalInstance(formal_nominal) => {
|
||||
formal_nominal.class(self.db).into_generic_alias()
|
||||
}
|
||||
// TODO: This will only handle classes that explicit implement a generic protocol
|
||||
// by listing it as a base class. To handle classes that implicitly implement a
|
||||
// generic protocol, we will need to check the types of the protocol members to be
|
||||
// able to infer the specialization of the protocol that the class implements.
|
||||
Type::ProtocolInstance(ProtocolInstanceType {
|
||||
inner: Protocol::FromClass(class),
|
||||
..
|
||||
}) => class.into_generic_alias(),
|
||||
|
||||
Type::ProtocolInstance(_) => {
|
||||
// TODO: For protocols, we use the new constraint set implementation, which
|
||||
// will handle implicitly implemented protocols and generic protocols. We
|
||||
// eventually want this logic to be used for _all_ nominal instances
|
||||
// (replacing the logic below).
|
||||
let when = actual.when_constraint_set_assignable_to(
|
||||
self.db,
|
||||
formal,
|
||||
self.inferable,
|
||||
);
|
||||
if when
|
||||
.limit_to_valid_specializations(self.db)
|
||||
.is_never_satisfied(self.db)
|
||||
&& (formal.has_typevar(self.db) || actual.has_typevar(self.db))
|
||||
{
|
||||
return Err(SpecializationError::NoSolution {
|
||||
parameter: formal,
|
||||
argument: actual,
|
||||
});
|
||||
}
|
||||
self.add_type_mappings_from_constraint_set(formal, when, &mut f);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -2132,6 +2214,10 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum SpecializationError<'db> {
|
||||
NoSolution {
|
||||
parameter: Type<'db>,
|
||||
argument: Type<'db>,
|
||||
},
|
||||
MismatchedBound {
|
||||
bound_typevar: BoundTypeVarInstance<'db>,
|
||||
argument: Type<'db>,
|
||||
@@ -2143,15 +2229,17 @@ pub(crate) enum SpecializationError<'db> {
|
||||
}
|
||||
|
||||
impl<'db> SpecializationError<'db> {
|
||||
pub(crate) fn bound_typevar(&self) -> BoundTypeVarInstance<'db> {
|
||||
pub(crate) fn bound_typevar(&self) -> Option<BoundTypeVarInstance<'db>> {
|
||||
match self {
|
||||
Self::MismatchedBound { bound_typevar, .. } => *bound_typevar,
|
||||
Self::MismatchedConstraint { bound_typevar, .. } => *bound_typevar,
|
||||
Self::NoSolution { .. } => None,
|
||||
Self::MismatchedBound { bound_typevar, .. } => Some(*bound_typevar),
|
||||
Self::MismatchedConstraint { bound_typevar, .. } => Some(*bound_typevar),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn argument_type(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::NoSolution { argument, .. } => *argument,
|
||||
Self::MismatchedBound { argument, .. } => *argument,
|
||||
Self::MismatchedConstraint { argument, .. } => *argument,
|
||||
}
|
||||
|
||||
@@ -134,14 +134,29 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let structurally_satisfied = if let Type::ProtocolInstance(self_protocol) = self {
|
||||
self_protocol.interface(db).has_relation_to_impl(
|
||||
db,
|
||||
protocol.interface(db),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
let self_as_nominal = self_protocol.to_nominal_instance();
|
||||
let other_as_nominal = protocol.to_nominal_instance();
|
||||
let nominal_match = match self_as_nominal.zip(other_as_nominal) {
|
||||
Some((self_as_nominal, other_as_nominal)) => self_as_nominal.has_relation_to_impl(
|
||||
db,
|
||||
other_as_nominal,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
_ => ConstraintSet::from(false),
|
||||
};
|
||||
nominal_match.or(db, || {
|
||||
self_protocol.interface(db).has_relation_to_impl(
|
||||
db,
|
||||
protocol.interface(db),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
protocol
|
||||
.inner
|
||||
|
||||
Reference in New Issue
Block a user