Skip to content

Commit cddf3bf

Browse files
author
Markus Westerlind
committed
fix: Error when a label variable in a record label could not be made concrete
1 parent 2a31e76 commit cddf3bf

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

libflux/flux-core/src/semantic/tests/labels.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ fn optional_label() {
154154
x = columns(table: { a: 1, b: "b" })
155155
y = x.abc
156156
"#,
157-
expect: expect![[]],
157+
// TODO Improve this error
158+
expect: expect![[r#"
159+
error: A is not a label
160+
┌─ main:2:17
161+
162+
2 │ x = columns(table: { a: 1, b: "b" })
163+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
164+
165+
"#]],
158166
}
159167
}

libflux/flux-core/src/semantic/types.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::{
44
borrow::Cow,
5-
cell::Cell,
5+
cell::{Cell, RefCell},
66
cmp,
77
collections::{BTreeMap, BTreeSet},
88
fmt::{self, Write},
@@ -147,7 +147,11 @@ impl<'a, E> Unifier<'a, E> {
147147
}
148148
}
149149

150-
fn finish<T>(mut self, value: T, mk_error: impl Fn(Error) -> E) -> Result<T, Errors<E>> {
150+
fn finish(
151+
mut self,
152+
value: MonoType,
153+
mk_error: impl Fn(Error) -> E,
154+
) -> Result<MonoType, Errors<E>> {
151155
if !self.delayed_records.is_empty() {
152156
let mut sub_unifier = Unifier::new(self.sub);
153157
while let Some((expected, actual)) = self.delayed_records.pop() {
@@ -160,6 +164,51 @@ impl<'a, E> Unifier<'a, E> {
160164
.extend(sub_unifier.errors.into_iter().map(&mk_error));
161165
}
162166

167+
struct FindUnboundLabels<'a> {
168+
found: RefCell<Option<MonoType>>,
169+
sub: &'a dyn Substituter,
170+
}
171+
impl Substituter for FindUnboundLabels<'_> {
172+
fn try_apply(&self, _: Tvar) -> Option<MonoType> {
173+
None
174+
}
175+
fn visit_type(&self, typ: &MonoType) -> Option<MonoType> {
176+
if let MonoType::Record(rec) = typ {
177+
if let Record::Extension {
178+
head:
179+
Property {
180+
k: RecordLabel::Variable(var),
181+
..
182+
},
183+
..
184+
} = &**rec
185+
{
186+
match self.sub.try_apply(*var) {
187+
Some(MonoType::Var(_)) | None => {
188+
*self.found.borrow_mut() = Some(MonoType::Var(*var))
189+
}
190+
_ => (),
191+
}
192+
}
193+
}
194+
None
195+
}
196+
}
197+
198+
let mut error_on_unbound_record_labels = |typ: &MonoType| {
199+
let visitor = FindUnboundLabels {
200+
found: RefCell::new(None),
201+
sub: self.sub,
202+
};
203+
let typ = typ.apply_cow(self.sub);
204+
typ.visit(&visitor);
205+
if let Some(non_label) = visitor.found.into_inner() {
206+
self.errors.push(mk_error(Error::NotALabel(non_label)));
207+
}
208+
};
209+
210+
error_on_unbound_record_labels(&value);
211+
163212
if self.errors.has_errors() {
164213
Err(self.errors)
165214
} else {
@@ -1484,6 +1533,7 @@ impl Record {
14841533
RecordLabel::Concrete(_) => unifier.errors.push(Error::MissingLabel(a.to_string())),
14851534
RecordLabel::BoundVariable(v) | RecordLabel::Variable(v) => {
14861535
let t = unifier.sub.apply(v);
1536+
t.unify(&MonoType::Error, unifier);
14871537
unifier.errors.push(Error::NotALabel(t));
14881538
}
14891539
},
@@ -1999,7 +2049,8 @@ impl Function {
19992049

20002050
self.unify(actual, &mut unifier);
20012051

2002-
unifier.finish((), mk_error)
2052+
unifier.finish(MonoType::from(self.clone()), mk_error)?;
2053+
Ok(())
20032054
}
20042055

20052056
pub(crate) fn try_subsume_with<T>(
@@ -2015,7 +2066,8 @@ impl Function {
20152066

20162067
self.unify(actual, &mut unifier);
20172068

2018-
unifier.finish((), mk_error)
2069+
unifier.finish(MonoType::from(self.clone()), mk_error)?;
2070+
Ok(())
20192071
}
20202072

20212073
/// Given two function types f and g, the process for unifying their arguments is as follows:

0 commit comments

Comments
 (0)