From 7c2e757c03d05ea5813757ce5a6229a67d3d8e9b Mon Sep 17 00:00:00 2001 From: Franklin Chen Date: Sat, 28 Mar 2026 07:31:53 -0400 Subject: [PATCH] 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 --- crates/generate/src/node_types.rs | 51 ++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/crates/generate/src/node_types.rs b/crates/generate/src/node_types.rs index 7ec6700b6..b7aa6de37 100644 --- a/crates/generate/src/node_types.rs +++ b/crates/generate/src/node_types.rs @@ -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 {