Skip to content

Commit 0373541

Browse files
committed
Deduplicate constraints based on superclasses
1 parent 0f24377 commit 0373541

File tree

5 files changed

+113
-37
lines changed

5 files changed

+113
-37
lines changed

compiler-core/checking/src/algorithm.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn check_type_items<Q: ExternalQueries>(
122122
type_item::check_type_item(state, context, *item)?;
123123

124124
build_data_constructor_types(state, context, slice::from_ref(item))?;
125-
state.commit_binding_group(context);
125+
state.commit_binding_group(context)?;
126126
}
127127
Scc::Mutual(items) => {
128128
let with_signature = items
@@ -142,14 +142,14 @@ fn check_type_items<Q: ExternalQueries>(
142142
}
143143

144144
build_data_constructor_types(state, context, &without_signature)?;
145-
state.commit_binding_group(context);
145+
state.commit_binding_group(context)?;
146146

147147
for item in &with_signature {
148148
type_item::check_type_item(state, context, *item)?;
149149
}
150150

151151
build_data_constructor_types(state, context, &with_signature)?;
152-
state.commit_binding_group(context);
152+
state.commit_binding_group(context)?;
153153
}
154154
}
155155
}
@@ -304,7 +304,7 @@ fn check_value_groups<Q: ExternalQueries>(
304304
state.term_binding_group(context, [*item]);
305305
}
306306
term_item::check_value_group(state, context, *item)?;
307-
state.commit_binding_group(context);
307+
state.commit_binding_group(context)?;
308308
}
309309
Scc::Mutual(items) => {
310310
let with_signature = items
@@ -325,12 +325,12 @@ fn check_value_groups<Q: ExternalQueries>(
325325
for item in &without_signature {
326326
term_item::check_value_group(state, context, *item)?;
327327
}
328-
state.commit_binding_group(context);
328+
state.commit_binding_group(context)?;
329329

330330
for item in &with_signature {
331331
term_item::check_value_group(state, context, *item)?;
332332
}
333-
state.commit_binding_group(context);
333+
state.commit_binding_group(context)?;
334334
}
335335
_ => {}
336336
}

compiler-core/checking/src/algorithm/constraint.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,17 @@ where
114114
Ok(residual)
115115
}
116116

117-
struct ConstraintApplication {
118-
file_id: FileId,
119-
item_id: TypeItemId,
120-
arguments: Vec<TypeId>,
117+
#[derive(Clone, PartialEq, Eq, Hash)]
118+
pub(crate) struct ConstraintApplication {
119+
pub(crate) file_id: FileId,
120+
pub(crate) item_id: TypeItemId,
121+
pub(crate) arguments: Vec<TypeId>,
121122
}
122123

123-
fn constraint_application(state: &mut CheckState, id: TypeId) -> Option<ConstraintApplication> {
124+
pub(crate) fn constraint_application(
125+
state: &mut CheckState,
126+
id: TypeId,
127+
) -> Option<ConstraintApplication> {
124128
let mut arguments = vec![];
125129
let mut current_id = id;
126130
loop {
@@ -164,7 +168,7 @@ where
164168
}
165169

166170
/// Discovers superclass constraints for a given constraint.
167-
fn elaborate_superclasses<Q>(
171+
pub(crate) fn elaborate_superclasses<Q>(
168172
state: &mut CheckState,
169173
context: &CheckContext<Q>,
170174
constraint: TypeId,
@@ -206,7 +210,7 @@ where
206210
bindings.insert(level, argument);
207211
}
208212

209-
for &(superclass, _kind) in &class_info.superclasses {
213+
for &(superclass, _) in &class_info.superclasses {
210214
let localized = transfer::localize(state, context, superclass);
211215
let substituted = ApplyBindings::on(state, &bindings, localized);
212216
constraints.push(substituted);
@@ -260,7 +264,7 @@ where
260264
Ok(result)
261265
}
262266

263-
fn get_class_info<Q>(
267+
pub(crate) fn get_class_info<Q>(
264268
state: &CheckState,
265269
context: &CheckContext<Q>,
266270
file_id: FileId,

compiler-core/checking/src/algorithm/quantify.rs

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
use std::fmt::Write;
22
use std::sync::Arc;
33

4+
use building_types::QueryResult;
45
use indexmap::IndexSet;
56
use itertools::Itertools;
67
use petgraph::prelude::DiGraphMap;
78
use petgraph::visit::{DfsPostOrder, Reversed};
89
use rustc_hash::FxHashSet;
910
use smol_str::SmolStrBuilder;
1011

12+
use crate::ExternalQueries;
13+
use crate::algorithm::constraint::{
14+
ConstraintApplication, constraint_application, elaborate_superclasses,
15+
};
1116
use crate::algorithm::fold::Zonk;
12-
use crate::algorithm::state::CheckState;
17+
use crate::algorithm::state::{CheckContext, CheckState};
1318
use crate::algorithm::substitute::{ShiftLevels, SubstituteUnification, UniToLevel};
1419
use crate::core::{ForallBinder, RowType, Type, TypeId, debruijn};
1520

@@ -52,8 +57,8 @@ pub fn quantify(state: &mut CheckState, id: TypeId) -> Option<(TypeId, debruijn:
5257
Some((quantified, size))
5358
}
5459

55-
/// Output of constraint quantification.
56-
pub struct QuantifyConstraints {
60+
/// The result of generalisation including constraints.
61+
pub struct QuantifiedWithConstraints {
5762
/// The quantified type with generalisable constraints.
5863
pub quantified: TypeId,
5964
/// The number of quantified type variables.
@@ -73,19 +78,24 @@ pub struct QuantifyConstraints {
7378
///
7479
/// Generalisable constraints are added to the signature before generalisation.
7580
/// Ambiguous and unsatisfied constraints are returned for error reporting.
76-
pub fn quantify_with_constraints(
81+
pub fn quantify_with_constraints<Q>(
7782
state: &mut CheckState,
83+
context: &CheckContext<Q>,
7884
type_id: TypeId,
7985
constraints: Vec<TypeId>,
80-
) -> Option<QuantifyConstraints> {
86+
) -> QueryResult<Option<QuantifiedWithConstraints>>
87+
where
88+
Q: ExternalQueries,
89+
{
8190
if constraints.is_empty() {
82-
let (quantified, size) = quantify(state, type_id)?;
83-
return Some(QuantifyConstraints {
84-
quantified,
85-
size,
86-
ambiguous: vec![],
87-
unsatisfied: vec![],
88-
});
91+
let Some((quantified, size)) = quantify(state, type_id) else {
92+
return Ok(None);
93+
};
94+
95+
let quantified_with_constraints =
96+
QuantifiedWithConstraints { quantified, size, ambiguous: vec![], unsatisfied: vec![] };
97+
98+
return Ok(Some(quantified_with_constraints));
8999
}
90100

91101
let unsolved_graph = collect_unification(state, type_id);
@@ -108,14 +118,74 @@ pub fn quantify_with_constraints(
108118
}
109119

110120
// Subtle: stable ordering for consistent output
111-
let valid = valid.into_iter().sorted();
121+
let valid = valid.into_iter().sorted().collect_vec();
122+
let minimized = minimize_by_superclasses(state, context, valid)?;
112123

113-
let constrained_type = valid.rfold(type_id, |constrained, constraint| {
124+
let constrained_type = minimized.into_iter().rfold(type_id, |constrained, constraint| {
114125
state.storage.intern(Type::Constrained(constraint, constrained))
115126
});
116127

117-
let (quantified, size) = quantify(state, constrained_type)?;
118-
Some(QuantifyConstraints { quantified, size, ambiguous, unsatisfied })
128+
let Some((quantified, size)) = quantify(state, constrained_type) else {
129+
return Ok(None);
130+
};
131+
132+
let quantified_with_constraints =
133+
QuantifiedWithConstraints { quantified, size, ambiguous, unsatisfied };
134+
135+
Ok(Some(quantified_with_constraints))
136+
}
137+
138+
/// Removes constraints that are implied by other constraints via superclass relationships.
139+
///
140+
/// For example, given constraints `[Apply f, Applicative f, Functor f]`, this function
141+
/// returns only `[Applicative f]` because `Applicative` implies both `Apply` and `Functor`.
142+
///
143+
/// Uses GHC's algorithm (mkMinimalBySCs): O(n × superclass_depth)
144+
/// 1. Build the union of all superclass constraints
145+
/// 2. Filter out constraints that appear in the union
146+
fn minimize_by_superclasses<Q>(
147+
state: &mut CheckState,
148+
context: &CheckContext<Q>,
149+
constraints: Vec<TypeId>,
150+
) -> QueryResult<Vec<TypeId>>
151+
where
152+
Q: ExternalQueries,
153+
{
154+
if constraints.len() <= 1 {
155+
return Ok(constraints);
156+
}
157+
158+
// Collect the set of all superclasses, including transitive ones.
159+
let mut superclasses = FxHashSet::default();
160+
for &constraint in &constraints {
161+
for application in superclass_applications(state, context, constraint)? {
162+
superclasses.insert(application);
163+
}
164+
}
165+
166+
// Remove constraints found in the superclasses, keeping the most specific.
167+
let minimized = constraints.into_iter().filter(|&constraint| {
168+
constraint_application(state, constraint)
169+
.map_or(true, |constraint| !superclasses.contains(&constraint))
170+
});
171+
172+
Ok(minimized.collect_vec())
173+
}
174+
175+
fn superclass_applications<Q>(
176+
state: &mut CheckState,
177+
context: &CheckContext<Q>,
178+
constraint: TypeId,
179+
) -> QueryResult<Vec<ConstraintApplication>>
180+
where
181+
Q: ExternalQueries,
182+
{
183+
let mut superclasses = vec![];
184+
elaborate_superclasses(state, context, constraint, &mut superclasses)?;
185+
Ok(superclasses
186+
.into_iter()
187+
.filter_map(|constraint| constraint_application(state, constraint))
188+
.collect())
119189
}
120190

121191
fn generate_type_name(id: u32) -> smol_str::SmolStr {

compiler-core/checking/src/algorithm/state.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,14 +596,16 @@ impl CheckState {
596596
constraint::solve_constraints(self, context, wanted, given)
597597
}
598598

599-
pub fn commit_binding_group<Q>(&mut self, context: &CheckContext<Q>)
599+
pub fn commit_binding_group<Q>(&mut self, context: &CheckContext<Q>) -> QueryResult<()>
600600
where
601601
Q: ExternalQueries,
602602
{
603603
let mut residuals = mem::take(&mut self.binding_group.residual);
604604
for (item_id, type_id) in mem::take(&mut self.binding_group.terms) {
605605
let constraints = residuals.remove(&item_id).unwrap_or_default();
606-
if let Some(result) = quantify::quantify_with_constraints(self, type_id, constraints) {
606+
if let Some(result) =
607+
quantify::quantify_with_constraints(self, context, type_id, constraints)?
608+
{
607609
self.with_error_step(ErrorStep::TermDeclaration(item_id), |this| {
608610
for constraint in result.ambiguous {
609611
let constraint = transfer::globalize(this, context, constraint);
@@ -636,6 +638,8 @@ impl CheckState {
636638
self.checked.synonyms.insert(item_id, synonym);
637639
}
638640
}
641+
642+
Ok(())
639643
}
640644

641645
/// Executes an action with an [`ErrorStep`] in scope.

tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ pure :: forall (f :: Type -> Type) (a :: Type). Applicative f => a -> f a
1010
discard :: forall (m :: Type -> Type) (a :: Type) (b :: Type). Discard m => m a -> (a -> m b) -> m b
1111
bind :: forall (m :: Type -> Type) (a :: Type) (b :: Type). Bind m => m a -> (a -> m b) -> m b
1212
testDo :: forall (m :: Type -> Type). Monad m => m (Tuple Int String)
13-
testDo' :: forall (t63 :: Type -> Type). Bind t63 => Applicative t63 => t63 (Tuple Int String)
13+
testDo' :: forall (t63 :: Type -> Type). Bind t63 => t63 (Tuple Int String)
1414
testAdo :: forall (f :: Type -> Type). Applicative f => f (Tuple Int String)
15-
testAdo' ::
16-
forall (t93 :: Type -> Type).
17-
Apply t93 => Applicative t93 => Functor t93 => t93 (Tuple Int String)
15+
testAdo' :: forall (t93 :: Type -> Type). Applicative t93 => t93 (Tuple Int String)
1816
testDoDiscard :: forall (m :: Type -> Type). Monad m => m Int
19-
testDoDiscard' :: forall (t109 :: Type -> Type). Discard t109 => Applicative t109 => t109 Int
17+
testDoDiscard' :: forall (t109 :: Type -> Type). Discard t109 => t109 Int
2018

2119
Types
2220
Tuple :: Type -> Type -> Type

0 commit comments

Comments
 (0)