mirror of https://github.com/astral-sh/ruff
only fold once
This commit is contained in:
parent
358185b5e2
commit
63c75d85d0
|
|
@ -1357,7 +1357,7 @@ impl<'db> Node<'db> {
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
bound_typevar: BoundTypeVarIdentity<'db>,
|
bound_typevar: BoundTypeVarIdentity<'db>,
|
||||||
mut f: impl FnMut(Option<(Type<'db>, Type<'db>, usize)>),
|
mut f: impl FnMut(Option<&[RepresentativeBounds<'db>]>),
|
||||||
) {
|
) {
|
||||||
self.retain_one(db, bound_typevar)
|
self.retain_one(db, bound_typevar)
|
||||||
.find_representative_types_inner(db, &mut Vec::default(), &mut f);
|
.find_representative_types_inner(db, &mut Vec::default(), &mut f);
|
||||||
|
|
@ -1367,43 +1367,37 @@ impl<'db> Node<'db> {
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
current_bounds: &mut Vec<RepresentativeBounds<'db>>,
|
current_bounds: &mut Vec<RepresentativeBounds<'db>>,
|
||||||
f: &mut dyn FnMut(Option<(Type<'db>, Type<'db>, usize)>),
|
f: &mut dyn FnMut(Option<&[RepresentativeBounds<'db>]>),
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
Node::AlwaysTrue => {
|
Node::AlwaysTrue => {
|
||||||
|
// If we reach the `true` terminal, the path we've been following represents one
|
||||||
|
// representative type.
|
||||||
if current_bounds.is_empty() {
|
if current_bounds.is_empty() {
|
||||||
f(None);
|
f(None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we reach the `true` terminal, the path we've been following represents one
|
|
||||||
// representative type. Before constructing the final lower and upper bound, sort
|
|
||||||
// the constraints by their source order. This should give us a consistently
|
|
||||||
// ordered specialization, regardless of the variable ordering of the original BDD.
|
|
||||||
current_bounds.sort_unstable_by_key(|bounds| bounds.source_order);
|
|
||||||
let greatest_lower_bound =
|
|
||||||
UnionType::from_elements(db, current_bounds.iter().map(|bounds| bounds.lower));
|
|
||||||
let least_upper_bound = IntersectionType::from_elements(
|
|
||||||
db,
|
|
||||||
current_bounds.iter().map(|bounds| bounds.upper),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If `lower ≰ upper`, then this path somehow represents in invalid specialization.
|
// If `lower ≰ upper`, then this path somehow represents in invalid specialization.
|
||||||
// That should have been removed from the BDD domain as part of the simplification
|
// That should have been removed from the BDD domain as part of the simplification
|
||||||
// process.
|
// process. (Here we are just checking assignability, so we don't need to construct
|
||||||
debug_assert!(greatest_lower_bound.is_assignable_to(db, least_upper_bound));
|
// the lower and upper bounds in a consistent order.)
|
||||||
|
debug_assert!({
|
||||||
// SAFETY: Checked that current_bounds is non-empty above.
|
let greatest_lower_bound = UnionType::from_elements(
|
||||||
let minimum_source_order = current_bounds[0].source_order;
|
db,
|
||||||
|
current_bounds.iter().map(|bounds| bounds.lower),
|
||||||
|
);
|
||||||
|
let least_upper_bound = IntersectionType::from_elements(
|
||||||
|
db,
|
||||||
|
current_bounds.iter().map(|bounds| bounds.upper),
|
||||||
|
);
|
||||||
|
greatest_lower_bound.is_assignable_to(db, least_upper_bound)
|
||||||
|
});
|
||||||
|
|
||||||
// We've been tracking the lower and upper bound that the types for this path must
|
// We've been tracking the lower and upper bound that the types for this path must
|
||||||
// satisfy. Pass those bounds along and let the caller choose a representative type
|
// satisfy. Pass those bounds along and let the caller choose a representative type
|
||||||
// from within that range.
|
// from within that range.
|
||||||
f(Some((
|
f(Some(¤t_bounds));
|
||||||
greatest_lower_bound,
|
|
||||||
least_upper_bound,
|
|
||||||
minimum_source_order,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::AlwaysFalse => {
|
Node::AlwaysFalse => {
|
||||||
|
|
@ -1771,6 +1765,7 @@ impl<'db> Node<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct RepresentativeBounds<'db> {
|
struct RepresentativeBounds<'db> {
|
||||||
lower: Type<'db>,
|
lower: Type<'db>,
|
||||||
upper: Type<'db>,
|
upper: Type<'db>,
|
||||||
|
|
@ -3674,96 +3669,99 @@ impl<'db> GenericContext<'db> {
|
||||||
|
|
||||||
// Then we find all of the "representative types" for each typevar in the constraint set.
|
// Then we find all of the "representative types" for each typevar in the constraint set.
|
||||||
let mut error_occurred = false;
|
let mut error_occurred = false;
|
||||||
let mut constraints = Vec::new();
|
let mut representatives = Vec::new();
|
||||||
let types = self.variables(db).map(|bound_typevar| {
|
let types =
|
||||||
// Each representative type represents one of the ways that the typevar can satisfy the
|
self.variables(db).map(|bound_typevar| {
|
||||||
// constraint, expressed as a lower/upper bound on the types that the typevar can
|
// Each representative type represents one of the ways that the typevar can satisfy the
|
||||||
// specialize to.
|
// constraint, expressed as a lower/upper bound on the types that the typevar can
|
||||||
//
|
// specialize to.
|
||||||
// If there are multiple paths in the BDD, they technically represent independent
|
//
|
||||||
// possible specializations. If there's a type that satisfies all of them, we will
|
// If there are multiple paths in the BDD, they technically represent independent
|
||||||
// return that as the specialization. If not, then the constraint set is ambiguous.
|
// possible specializations. If there's a type that satisfies all of them, we will
|
||||||
// (This happens most often with constrained typevars.) We could in the future turn
|
// return that as the specialization. If not, then the constraint set is ambiguous.
|
||||||
// _each_ of the paths into separate specializations, but it's not clear what we would
|
// (This happens most often with constrained typevars.) We could in the future turn
|
||||||
// do with that, so instead we just report the ambiguity as a specialization failure.
|
// _each_ of the paths into separate specializations, but it's not clear what we would
|
||||||
let mut unconstrained = false;
|
// do with that, so instead we just report the ambiguity as a specialization failure.
|
||||||
let identity = bound_typevar.identity(db);
|
let mut unconstrained = false;
|
||||||
tracing::trace!(
|
let identity = bound_typevar.identity(db);
|
||||||
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
tracing::trace!(
|
||||||
bound_typevar = %identity.display(db),
|
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
||||||
abstracted = %abstracted.retain_one(db, identity).display(db),
|
bound_typevar = %identity.display(db),
|
||||||
"find specialization for typevar",
|
abstracted = %abstracted.retain_one(db, identity).display(db),
|
||||||
);
|
"find specialization for typevar",
|
||||||
constraints.clear();
|
);
|
||||||
abstracted.find_representative_types(db, identity, |bounds| match bounds {
|
representatives.clear();
|
||||||
Some(bounds @ (lower_bound, upper_bound, _)) => {
|
abstracted.find_representative_types(db, identity, |representative| {
|
||||||
tracing::trace!(
|
match representative {
|
||||||
|
Some(representative) => {
|
||||||
|
representatives.extend_from_slice(representative);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
unconstrained = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The BDD is satisfiable, but the typevar is unconstrained, then we use `None` to tell
|
||||||
|
// specialize_recursive to fall back on the typevar's default.
|
||||||
|
if unconstrained {
|
||||||
|
tracing::debug!(
|
||||||
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
||||||
bound_typevar = %identity.display(db),
|
bound_typevar = %identity.display(db),
|
||||||
lower_bound = %lower_bound.display(db),
|
"typevar is unconstrained",
|
||||||
upper_bound = %upper_bound.display(db),
|
|
||||||
"found representative type",
|
|
||||||
);
|
);
|
||||||
constraints.push(bounds);
|
return None;
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
unconstrained = true;
|
// If there are no satisfiable paths in the BDD, then there is no valid specialization
|
||||||
|
// for this constraint set.
|
||||||
|
if representatives.is_empty() {
|
||||||
|
// TODO: Construct a useful error here
|
||||||
|
tracing::debug!(
|
||||||
|
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
||||||
|
bound_typevar = %identity.display(db),
|
||||||
|
"typevar cannot be satisfied",
|
||||||
|
);
|
||||||
|
error_occurred = true;
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Before constructing the final lower and upper bound, sort the constraints by
|
||||||
|
// their source order. This should give us a consistently ordered specialization,
|
||||||
|
// regardless of the variable ordering of the original BDD.
|
||||||
|
representatives.sort_unstable_by_key(|bounds| bounds.source_order);
|
||||||
|
let greatest_lower_bound =
|
||||||
|
UnionType::from_elements(db, representatives.iter().map(|bounds| bounds.lower));
|
||||||
|
let least_upper_bound = IntersectionType::from_elements(
|
||||||
|
db,
|
||||||
|
representatives.iter().map(|bounds| bounds.upper),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If `lower ≰ upper`, then there is no type that satisfies all of the paths in the
|
||||||
|
// BDD. That's an ambiguous specialization, as described above.
|
||||||
|
if !greatest_lower_bound.is_assignable_to(db, least_upper_bound) {
|
||||||
|
tracing::debug!(
|
||||||
|
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
||||||
|
bound_typevar = %identity.display(db),
|
||||||
|
greatest_lower_bound = %greatest_lower_bound.display(db),
|
||||||
|
least_upper_bound = %least_upper_bound.display(db),
|
||||||
|
"typevar bounds are incompatible",
|
||||||
|
);
|
||||||
|
error_occurred = true;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of all of the types that satisfy all of the paths in the BDD, we choose the
|
||||||
|
// "largest" one (i.e., "closest to `object`") as the specialization.
|
||||||
|
tracing::debug!(
|
||||||
|
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
||||||
|
bound_typevar = %identity.display(db),
|
||||||
|
specialization = %least_upper_bound.display(db),
|
||||||
|
"found specialization for typevar",
|
||||||
|
);
|
||||||
|
Some(least_upper_bound)
|
||||||
});
|
});
|
||||||
|
|
||||||
// The BDD is satisfiable, but the typevar is unconstrained, then we use `None` to tell
|
|
||||||
// specialize_recursive to fall back on the typevar's default.
|
|
||||||
if unconstrained {
|
|
||||||
tracing::debug!(
|
|
||||||
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
|
||||||
bound_typevar = %identity.display(db),
|
|
||||||
"typevar is unconstrained",
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no satisfiable paths in the BDD, then there is no valid specialization
|
|
||||||
// for this constraint set.
|
|
||||||
if constraints.is_empty() {
|
|
||||||
// TODO: Construct a useful error here
|
|
||||||
tracing::debug!(
|
|
||||||
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
|
||||||
bound_typevar = %identity.display(db),
|
|
||||||
"typevar cannot be satisfied",
|
|
||||||
);
|
|
||||||
error_occurred = true;
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `lower ≰ upper`, then there is no type that satisfies all of the paths in the
|
|
||||||
// BDD. That's an ambiguous specialization, as described above.
|
|
||||||
let greatest_lower_bound =
|
|
||||||
UnionType::from_elements(db, constraints.iter().map(|(lower, _, _)| *lower));
|
|
||||||
let least_upper_bound =
|
|
||||||
IntersectionType::from_elements(db, constraints.iter().map(|(_, upper, _)| *upper));
|
|
||||||
if !greatest_lower_bound.is_assignable_to(db, least_upper_bound) {
|
|
||||||
tracing::debug!(
|
|
||||||
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
|
||||||
bound_typevar = %identity.display(db),
|
|
||||||
greatest_lower_bound = %greatest_lower_bound.display(db),
|
|
||||||
least_upper_bound = %least_upper_bound.display(db),
|
|
||||||
"typevar bounds are incompatible",
|
|
||||||
);
|
|
||||||
error_occurred = true;
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of all of the types that satisfy all of the paths in the BDD, we choose the
|
|
||||||
// "largest" one (i.e., "closest to `object`") as the specialization.
|
|
||||||
tracing::debug!(
|
|
||||||
target: "ty_python_semantic::types::constraints::specialize_constrained",
|
|
||||||
bound_typevar = %identity.display(db),
|
|
||||||
specialization = %least_upper_bound.display(db),
|
|
||||||
"found specialization for typevar",
|
|
||||||
);
|
|
||||||
Some(least_upper_bound)
|
|
||||||
});
|
|
||||||
|
|
||||||
let specialization = self.specialize_recursive(db, types);
|
let specialization = self.specialize_recursive(db, types);
|
||||||
if error_occurred {
|
if error_occurred {
|
||||||
return Err(());
|
return Err(());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue