diff options
| -rw-r--r-- | CHANGELOG.md | 10 | ||||
| -rw-r--r-- | mingling_macros/src/systems/dispatch_tree_gen.rs | 78 |
2 files changed, 55 insertions, 33 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1608466..8d4528f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,16 @@ None Both methods consume the original `Suggest` value and return a new one, enabling ergonomic chaining with the existing `combine()` method for transforming completion suggestion sets. +5. **[`core:dispatch_tree`]** Refactored `build_dispatch_body` in `mingling_macros/src/systems/dispatch_tree_gen.rs` to use a caller-provided `no_match` fallback token stream instead of hardcoding `return Ok(Self::build_entry_fallback(raw.to_vec()));` at every level of the generated trie. + + - The root call from `gen_dispatch_args_trie` passes the current fallback token stream as `no_match`. + - The `no_match` parameter is threaded down through recursive calls; when a subtree has no nodes, it returns the caller's `no_match` code instead of unconditionally emitting the fallback. + - For each node, `level_no_match` is computed as the exact-endpoint checks for that node followed by the caller's `no_match` code. This ensures exact endpoints at the current level are tried before giving up and bubbling up to the parent. + - When a node has children, the arm for each child runs the child's body first; if the child subtree fails to match, control falls through to `level_no_match` (exact endpoints here, then the parent's fallback), so longer registered paths are preferred over the exact endpoint at the same depth. + - When a node has no children, the generated code runs the exact-endpoint checks followed by `no_match`. + + Behavioral result: the static trie dispatcher now follows the same "longest registered prefix wins" rule as the dynamic dispatcher — a child (longer) path is preferred over an exact endpoint at the same depth, and only when every descendant fails to match is the exact endpoint at the current depth dispatched. + #### Optimizations: 1. **[`pathf`]** Added `is_module` field to `AnalyzeItem` and a new constructor `AnalyzeItem::local_module(module, item_name)` which sets `is_module: true`. The `type_mapping_builder` now tracks whether an item is a module: when generating `type_using.rs`, module items produce `use path::to::module::*;` (glob import) instead of the standard `use path::to::TypeName;` direct import. Non-module items continue to use direct imports as before. The internal data structure changed from `Vec<(String, String)>` to `Vec<(String, String, bool)>` to carry the `is_module` flag through the pipeline. diff --git a/mingling_macros/src/systems/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs index 8e3660c..f390b33 100644 --- a/mingling_macros/src/systems/dispatch_tree_gen.rs +++ b/mingling_macros/src/systems/dispatch_tree_gen.rs @@ -39,7 +39,13 @@ pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> To .map(|(name, disp, _)| (name.replace('.', " "), disp.clone())) .collect(); - let dispatch_body = build_dispatch_body(&nodes, 0); + let dispatch_body = build_dispatch_body( + &nodes, + 0, + "e! { + return Ok(Self::build_entry_fallback(raw.to_vec())); + }, + ); quote! { fn dispatch_args_trie( @@ -58,11 +64,19 @@ pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> To /// /// `nodes`: slice of (display_name, disp_type) for commands that share the same prefix so far. /// `depth`: The character index currently being matched. -fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream { +/// `no_match`: fallback code to run when no node in this subtree matches the input. +/// +/// Matching follows the same "longest registered prefix" rule used by the +/// dynamic dispatcher: a child (longer) path is preferred over an exact +/// endpoint at the same depth. Only when every descendant fails to match is +/// the exact endpoint here dispatched. +fn build_dispatch_body( + nodes: &[(String, String)], + depth: usize, + no_match: &TokenStream, +) -> TokenStream { if nodes.is_empty() { - return quote! { - return Ok(Self::build_entry_fallback(raw.to_vec())); - }; + return no_match.clone(); } let mut groups: BTreeMap<char, Vec<(String, String)>> = BTreeMap::new(); @@ -102,6 +116,20 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream } }; + // Fallback code for when neither a child path nor the exact endpoint(s) + // here match: run the exact endpoint checks for this node first (they must + // win over nothing at all), then pass control back up to the caller. + let exact_checks: Vec<TokenStream> = exact_nodes + .iter() + .map(|(name, disp_type)| make_starts_with_arm(name, disp_type)) + .collect(); + + let level_no_match = { + let mut body = exact_checks.clone(); + body.push(no_match.clone()); + quote! { #(#body)* } + }; + let mut arms = Vec::new(); for (&ch, sub_nodes) in &groups { @@ -110,14 +138,16 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream if sub_nodes.len() == 1 { let (name, disp_type) = &sub_nodes[0]; let arm = make_starts_with_arm(name, disp_type); + // Try the child first; if it does not match, fall through to the + // exact endpoint(s) here so the longer path wins when present. arms.push(quote! { Some(#ch_char) => { #arm - return Ok(Self::build_entry_fallback(raw.to_vec())); + #level_no_match } }); } else { - let sub_body = build_dispatch_body(sub_nodes, depth + 1); + let sub_body = build_dispatch_body(sub_nodes, depth + 1, &level_no_match); arms.push(quote! { Some(#ch_char) => { #sub_body @@ -126,36 +156,18 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream } } - let exact_checks: Vec<TokenStream> = exact_nodes - .iter() - .map(|(name, disp_type)| make_starts_with_arm(name, disp_type)) - .collect(); - - if !exact_checks.is_empty() && !groups.is_empty() { - let match_body = quote! { - match raw_chars.nth(0) { - #(#arms)* - _ => return Ok(Self::build_entry_fallback(raw.to_vec())), - } - }; - quote! { - #(#exact_checks)* - #match_body - } - } else if !exact_checks.is_empty() { - quote! { - #(#exact_checks)* - return Ok(Self::build_entry_fallback(raw.to_vec())); - } - } else if arms.is_empty() { - quote! { - return Ok(Self::build_entry_fallback(raw.to_vec())); - } + if groups.is_empty() { + // No children exist for this node; only the exact endpoint(s) apply. + let mut body = exact_checks; + body.push(no_match.clone()); + quote! { #(#body)* } } else { quote! { match raw_chars.nth(0) { #(#arms)* - _ => return Ok(Self::build_entry_fallback(raw.to_vec())), + _ => { + #level_no_match + } } } } |
