Skip to content

Commit b32c563

Browse files
committed
Auto merge of #752 - Dirbaio:fix-recursive-hang, r=jackh726
recursive: fix hang on fulfill by slightly smarter check for progress. This fixes a hang using the recursive solver when trying to prove `exists<'a, T> { if(T: 'a) { WellFormed(&'a T) } }`. <details> <summary>Snippet of the log when looping</summary> 617ms DEBUG start of round, 2 obligations prove wc=InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 } solve_goal goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: ^0.0: '^0.1 }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 } 0ms DEBUG Cache hit, goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: ^0.0: '^0.1 }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 }, result=Ok(Ambig(Definite(Canonical { value: [?0 := ^0.0, ?1 := '^0.1], binders: [U0 with kind type, U0 with kind lifetime] }))) 0ms DEBUG solve_reduced_goal: cache hit, value=Ok(Ambig(Definite(Canonical { value: [?0 := ^0.0, ?1 := '^0.1], binders: [U0 with kind type, U0 with kind lifetime] }))) 618ms DEBUG fulfill::apply_solution: adding constraints [] 618ms DEBUG unify(?2, ?1140) succeeded 618ms DEBUG unify: goals=[] 618ms DEBUG unify('?3, '?1141) succeeded 618ms DEBUG unify: goals=[] 618ms DEBUG ambiguous result: Prove(InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 }) prove wc=InEnvironment { environment: Env([for<> ?0: '?1]), goal: WellFormed(?2) } solve_goal goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: WellFormed(^0.0) }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 } 0ms DEBUG Cache hit, goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: WellFormed(^0.0) }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 }, result=Ok(Ambig(Unknown)) 0ms DEBUG solve_reduced_goal: cache hit, value=Ok(Ambig(Unknown)) 618ms DEBUG ambiguous result: Prove(InEnvironment { environment: Env([for<> ?0: '?1]), goal: WellFormed(?2) }) 618ms DEBUG end of round, 2 obligations left 618ms DEBUG start of round, 2 obligations prove wc=InEnvironment { environment: Env([for<> ?0: '?1]), goal: WellFormed(?2) } solve_goal goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: WellFormed(^0.0) }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 } 0ms DEBUG Cache hit, goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: WellFormed(^0.0) }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 }, result=Ok(Ambig(Unknown)) 0ms DEBUG solve_reduced_goal: cache hit, value=Ok(Ambig(Unknown)) 619ms DEBUG ambiguous result: Prove(InEnvironment { environment: Env([for<> ?0: '?1]), goal: WellFormed(?2) }) prove wc=InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 } solve_goal goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: ^0.0: '^0.1 }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 } 0ms DEBUG Cache hit, goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: ^0.0: '^0.1 }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 }, result=Ok(Ambig(Definite(Canonical { value: [?0 := ^0.0, ?1 := '^0.1], binders: [U0 with kind type, U0 with kind lifetime] }))) 0ms DEBUG solve_reduced_goal: cache hit, value=Ok(Ambig(Definite(Canonical { value: [?0 := ^0.0, ?1 := '^0.1], binders: [U0 with kind type, U0 with kind lifetime] }))) 619ms DEBUG fulfill::apply_solution: adding constraints [] 619ms DEBUG unify(?2, ?1142) succeeded 619ms DEBUG unify: goals=[] 619ms DEBUG unify('?3, '?1143) succeeded 619ms DEBUG unify: goals=[] 619ms DEBUG ambiguous result: Prove(InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 }) 619ms DEBUG end of round, 2 obligations left 619ms DEBUG start of round, 2 obligations prove wc=InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 } solve_goal goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: ^0.0: '^0.1 }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 } 0ms DEBUG Cache hit, goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: ^0.0: '^0.1 }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 }, result=Ok(Ambig(Definite(Canonical { value: [?0 := ^0.0, ?1 := '^0.1], binders: [U0 with kind type, U0 with kind lifetime] }))) 0ms DEBUG solve_reduced_goal: cache hit, value=Ok(Ambig(Definite(Canonical { value: [?0 := ^0.0, ?1 := '^0.1], binders: [U0 with kind type, U0 with kind lifetime] }))) 620ms DEBUG fulfill::apply_solution: adding constraints [] 620ms DEBUG unify(?2, ?1144) succeeded 620ms DEBUG unify: goals=[] 620ms DEBUG unify('?3, '?1145) succeeded 620ms DEBUG unify: goals=[] 620ms DEBUG ambiguous result: Prove(InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 }) prove wc=InEnvironment { environment: Env([for<> ?0: '?1]), goal: WellFormed(?2) } solve_goal goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: WellFormed(^0.0) }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 } 0ms DEBUG Cache hit, goal=UCanonical { canonical: Canonical { value: InEnvironment { environment: Env([for<> ^1.0: '^1.1]), goal: WellFormed(^0.0) }, binders: [U0 with kind type, U0 with kind lifetime] }, universes: 1 }, result=Ok(Ambig(Unknown)) 0ms DEBUG solve_reduced_goal: cache hit, value=Ok(Ambig(Unknown)) 620ms DEBUG ambiguous result: Prove(InEnvironment { environment: Env([for<> ?0: '?1]), goal: WellFormed(?2) }) 620ms DEBUG end of round, 2 obligations left </details> The issue is: - it tries to prove `InEnvironment { environment: Env([for<> ?0: '?1]), goal: ?2: '?3 }` - it gets "useless" substs `[?0 := ^0.0, ?1 := '^0.1]` - it applies them, they cause nothing to change but it thinks they did - so in next round it processes again the exact same obligation... There was already an `if` treating empty substs as "useless", this extends it to treating trivial subs as "useless". (empty substs are also trivial).
2 parents 9bba3a8 + 91f2ee7 commit b32c563

File tree

2 files changed

+60
-14
lines changed

2 files changed

+60
-14
lines changed

chalk-recursive/src/fulfill.rs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use chalk_ir::interner::{HasInterner, Interner};
66
use chalk_ir::visit::Visit;
77
use chalk_ir::zip::Zip;
88
use chalk_ir::{
9-
Binders, Canonical, ConstrainedSubst, Constraint, Constraints, DomainGoal, Environment, EqGoal,
10-
Fallible, GenericArg, Goal, GoalData, InEnvironment, NoSolution, ProgramClauseImplication,
11-
QuantifierKind, Substitution, SubtypeGoal, TyKind, TyVariableKind, UCanonical,
12-
UnificationDatabase, UniverseMap, Variance,
9+
Binders, BoundVar, Canonical, ConstrainedSubst, Constraint, Constraints, DomainGoal,
10+
Environment, EqGoal, Fallible, GenericArg, GenericArgData, Goal, GoalData, InEnvironment,
11+
NoSolution, ProgramClauseImplication, QuantifierKind, Substitution, SubtypeGoal, TyKind,
12+
TyVariableKind, UCanonical, UnificationDatabase, UniverseMap, Variance,
1313
};
1414
use chalk_solve::debug_span;
1515
use chalk_solve::infer::{InferenceTable, ParameterEnaVariableExt};
@@ -466,17 +466,19 @@ impl<'s, I: Interner, Solver: SolveDatabase<I>> Fulfill<'s, I, Solver> {
466466
} = self.prove(wc.clone(), minimums)?;
467467

468468
if let Some(constrained_subst) = solution.definite_subst(self.interner()) {
469-
// If the substitution is empty, we won't actually make any progress by applying it!
469+
// If the substitution is trivial, we won't actually make any progress by applying it!
470470
// So we need to check this to prevent endless loops.
471-
// (An ambiguous solution with empty substitution
472-
// can probably not happen in valid code, but it can
473-
// happen e.g. when there are overlapping impls.)
474-
if !constrained_subst.value.subst.is_empty(self.interner())
475-
|| !constrained_subst
476-
.value
477-
.constraints
478-
.is_empty(self.interner())
479-
{
471+
let nontrivial_subst = !is_trivial_canonical_subst(
472+
self.interner(),
473+
&constrained_subst.value.subst,
474+
);
475+
476+
let has_constraints = !constrained_subst
477+
.value
478+
.constraints
479+
.is_empty(self.interner());
480+
481+
if nontrivial_subst || has_constraints {
480482
self.apply_solution(free_vars, universes, constrained_subst);
481483
progress = true;
482484
}
@@ -608,3 +610,28 @@ impl<'s, I: Interner, Solver: SolveDatabase<I>> Fulfill<'s, I, Solver> {
608610
self.solver.interner()
609611
}
610612
}
613+
614+
fn is_trivial_canonical_subst<I: Interner>(interner: I, subst: &Substitution<I>) -> bool {
615+
// A subst is trivial if..
616+
subst.iter(interner).enumerate().all(|(index, parameter)| {
617+
let is_trivial = |b: Option<BoundVar>| match b {
618+
None => false,
619+
Some(bound_var) => {
620+
if let Some(index1) = bound_var.index_if_innermost() {
621+
index == index1
622+
} else {
623+
false
624+
}
625+
}
626+
};
627+
628+
match parameter.data(interner) {
629+
// All types and consts are mapped to distinct variables. Since this
630+
// has been canonicalized, those will also be the first N
631+
// variables.
632+
GenericArgData::Ty(t) => is_trivial(t.bound_var(interner)),
633+
GenericArgData::Const(t) => is_trivial(t.bound_var(interner)),
634+
GenericArgData::Lifetime(t) => is_trivial(t.bound_var(interner)),
635+
}
636+
})
637+
}

tests/test/misc.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,3 +826,22 @@ fn env_bound_vars() {
826826
}
827827
}
828828
}
829+
830+
#[test]
831+
fn recursive_hang() {
832+
test! {
833+
program {}
834+
835+
goal {
836+
exists<'a, T> {
837+
if(T: 'a) {
838+
WellFormed(&'a T)
839+
}
840+
}
841+
} yields[SolverChoice::slg_default()] {
842+
expect![["Ambiguous; definite substitution for<?U0,?U0> { [?0 := ^0.0, ?1 := '^0.1] }"]]
843+
} yields[SolverChoice::recursive_default()] {
844+
expect![["Ambiguous; suggested substitution for<?U0,?U0> { [?0 := ^0.0, ?1 := '^0.1] }"]]
845+
}
846+
}
847+
}

0 commit comments

Comments
 (0)