Skip to content

Commit 3f3bed6

Browse files
asgerfCopilot
andcommitted
yeast: type-check for missing required fields
Add FieldCardinality to Schema to track required/multiple per field, populated from the ast_types.yml suffixes (bare = required single, ? = optional single, + = required multiple, * = optional multiple). dump_ast_with_type_errors now emits: <-- ERROR: missing required field 'name' for any node in the output AST whose declared schema requires a field that is absent from the actual node. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 21f216a commit 3f3bed6

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

shared/yeast/src/dump.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ fn dump_node(
273273
}
274274
}
275275

276+
// Check for required fields that are absent
277+
if let Some((schema, _, _)) = type_check {
278+
for (field_id, field_name) in schema.required_fields_for_kind(node.kind_name()) {
279+
if !node.fields.contains_key(&field_id) {
280+
let name = field_name.unwrap_or("child");
281+
writeln!(out, "{prefix} <-- ERROR: missing required field '{name}'").unwrap();
282+
}
283+
}
284+
}
285+
276286
// Unnamed children — skip unnamed tokens (keywords, punctuation)
277287
if let Some(children) = node.fields.get(&CHILD_FIELD) {
278288
let child_type_check = type_check.map(|(schema, _, _)| {

shared/yeast/src/node_types_yaml.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ fn apply_yaml_to_schema(
314314
node_types.sort_by(|a, b| a.kind.cmp(&b.kind).then(a.named.cmp(&b.named)));
315315
node_types.dedup_by(|a, b| a.kind == b.kind && a.named == b.named);
316316
schema.set_field_types(parent_kind, field_id, node_types);
317+
schema.set_field_cardinality(
318+
parent_kind,
319+
field_id,
320+
crate::schema::FieldCardinality {
321+
multiple: spec.multiple,
322+
required: spec.required,
323+
},
324+
);
317325
}
318326
}
319327
}

shared/yeast/src/schema.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ pub struct NodeType {
88
pub named: bool,
99
}
1010

11+
/// Multiplicity/optionality of a field declaration.
12+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13+
pub struct FieldCardinality {
14+
/// Whether the field may hold more than one child.
15+
pub multiple: bool,
16+
/// Whether at least one child must be present.
17+
pub required: bool,
18+
}
19+
1120
/// A schema defining node kinds and field names for the output AST.
1221
/// Built from a node-types.yml file, independent of any tree-sitter grammar.
1322
///
@@ -32,6 +41,7 @@ pub struct Schema {
3241
kind_names: BTreeMap<KindId, &'static str>,
3342
next_kind_id: KindId,
3443
field_types: BTreeMap<(String, FieldId), Vec<NodeType>>,
44+
field_cardinalities: BTreeMap<(String, FieldId), FieldCardinality>,
3545
supertypes: BTreeMap<String, Vec<NodeType>>,
3646
}
3747

@@ -52,6 +62,7 @@ impl Schema {
5262
kind_names: BTreeMap::new(),
5363
next_kind_id: 1, // 0 is reserved
5464
field_types: BTreeMap::new(),
65+
field_cardinalities: BTreeMap::new(),
5566
supertypes: BTreeMap::new(),
5667
}
5768
}
@@ -196,6 +207,42 @@ impl Schema {
196207
.get(&(parent_kind.to_string(), field_id))
197208
}
198209

210+
pub fn set_field_cardinality(
211+
&mut self,
212+
parent_kind: &str,
213+
field_id: FieldId,
214+
cardinality: FieldCardinality,
215+
) {
216+
self.field_cardinalities
217+
.insert((parent_kind.to_string(), field_id), cardinality);
218+
}
219+
220+
/// Returns the declared cardinality for a field, if known.
221+
pub fn field_cardinality(
222+
&self,
223+
parent_kind: &str,
224+
field_id: FieldId,
225+
) -> Option<FieldCardinality> {
226+
self.field_cardinalities
227+
.get(&(parent_kind.to_string(), field_id))
228+
.copied()
229+
}
230+
231+
/// Returns an iterator over all `(field_id, field_name)` pairs that are
232+
/// declared as required (`required: true`) for the given `parent_kind`.
233+
pub fn required_fields_for_kind<'a>(
234+
&'a self,
235+
parent_kind: &'a str,
236+
) -> impl Iterator<Item = (FieldId, Option<&'static str>)> + 'a {
237+
self.field_cardinalities
238+
.iter()
239+
.filter(move |((kind, _), card)| kind == parent_kind && card.required)
240+
.map(move |((_, field_id), _)| {
241+
let name = self.field_name_for_id(*field_id);
242+
(*field_id, name)
243+
})
244+
}
245+
199246
pub fn set_supertype_members(&mut self, supertype: &str, node_types: Vec<NodeType>) {
200247
self.supertypes.insert(supertype.to_string(), node_types);
201248
}

0 commit comments

Comments
 (0)