generate: avoid panicking when a supertype only has hidden external token children

This commit skips adding entries to the subtype map when the subtypes
list is empty to avoid a lookup failure in the topological sort during
node type generation.

Co-authored-by: Amaan Qureshi <git@amaanq.com>
This commit is contained in:
Franklin Chen 2026-03-28 07:31:53 -04:00 committed by Amaan Qureshi
parent 0d4f5cb13f
commit 7c2e757c03

View file

@ -588,7 +588,13 @@ pub fn generate_node_types_json(
kind: node_type_json.kind.clone(),
named: true,
};
subtype_map.push((supertype, subtypes.clone()));
// We only add to the subtype map if there are visible subtypes.
// A supertype may have zero subtypes if its children are all
// hidden (e.g., wrapping a hidden external token).
if !subtypes.is_empty() {
subtype_map.push((supertype, subtypes.clone()));
}
node_type_json.subtypes = Some(subtypes);
} else if !syntax_grammar.variables_to_inline.contains(&symbol) {
// If a rule is aliased under multiple names, then its information
@ -1260,6 +1266,49 @@ mod tests {
);
}
/// A supertype whose only child is a hidden external token
/// xgust not cause generation to panic. The subtype map must
/// skip entries with empty subtypes to avoid a lookup failure
/// in the topological sort.
#[test]
fn test_node_types_supertype_with_only_hidden_child() {
let node_types = get_node_types(&InputGrammar {
supertype_symbols: vec!["_type_a".to_string(), "_type_b".to_string()],
variables: vec![
Variable {
name: "v1".to_string(),
kind: VariableType::Named,
rule: Rule::seq(vec![Rule::named("_type_a"), Rule::named("_type_b")]),
},
// Supertype A: a normal choice of named subtypes
Variable {
name: "_type_a".to_string(),
kind: VariableType::Hidden,
rule: Rule::choice(vec![Rule::named("v2"), Rule::named("v3")]),
},
Variable {
name: "v2".to_string(),
kind: VariableType::Named,
rule: Rule::string("x"),
},
Variable {
name: "v3".to_string(),
kind: VariableType::Named,
rule: Rule::string("y"),
},
// Supertype B: a hidden external token with no subtypes
Variable {
name: "_type_b".to_string(),
kind: VariableType::Hidden,
rule: Rule::external(0),
},
],
external_tokens: vec![Rule::named("_hidden_ext")],
..Default::default()
});
assert!(node_types.is_ok());
}
#[test]
fn test_node_types_for_children_without_fields() {
let node_types = get_node_types(&InputGrammar {