From b18749170b6006e53976dbb6df9f59a3b9c34127 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 10 Apr 2026 16:47:40 +0800 Subject: Add completion macro infrastructure without logic --- mingling_macros/src/completion.rs | 132 +++++++++----------------------------- 1 file changed, 29 insertions(+), 103 deletions(-) (limited to 'mingling_macros/src/completion.rs') diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs index cf66f13..23b509f 100644 --- a/mingling_macros/src/completion.rs +++ b/mingling_macros/src/completion.rs @@ -5,91 +5,7 @@ use proc_macro::TokenStream; use quote::quote; -use syn::spanned::Spanned; -use syn::{ - FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input, -}; - -/// Extracts the previous type from function arguments -fn extract_previous_type(sig: &Signature) -> syn::Result { - // The function should have exactly one parameter: ShellContext - if sig.inputs.len() != 1 { - return Err(syn::Error::new( - sig.inputs.span(), - "Completion function must have exactly one parameter (ShellContext)", - )); - } - - let arg = &sig.inputs[0]; - match arg { - FnArg::Typed(PatType { ty, .. }) => { - match &**ty { - Type::Path(type_path) => { - // Check if it's ShellContext - let last_segment = type_path.path.segments.last().unwrap(); - if last_segment.ident != "ShellContext" { - return Err(syn::Error::new( - ty.span(), - "Parameter type must be ShellContext", - )); - } - Ok(type_path.clone()) - } - _ => Err(syn::Error::new( - ty.span(), - "Parameter type must be a type path", - )), - } - } - FnArg::Receiver(_) => Err(syn::Error::new( - arg.span(), - "Completion function cannot have self parameter", - )), - } -} - -/// Extracts the return type from the function signature -fn extract_return_type(sig: &Signature) -> syn::Result { - match &sig.output { - ReturnType::Type(_, ty) => match &**ty { - Type::Path(type_path) => { - // Check if it's Suggest - let last_segment = type_path.path.segments.last().unwrap(); - if last_segment.ident != "Suggest" { - return Err(syn::Error::new(ty.span(), "Return type must be Suggest")); - } - Ok(type_path.clone()) - } - _ => Err(syn::Error::new( - ty.span(), - "Return type must be a type path", - )), - }, - ReturnType::Default => Err(syn::Error::new( - sig.span(), - "Completion function must have a return type", - )), - } -} - -/// Extracts the parameter name from function arguments -fn extract_param_name(sig: &Signature) -> syn::Result { - if sig.inputs.len() != 1 { - return Err(syn::Error::new( - sig.inputs.span(), - "Completion function must have exactly one parameter", - )); - } - - let arg = &sig.inputs[0]; - match arg { - FnArg::Typed(PatType { pat, .. }) => Ok((**pat).clone()), - FnArg::Receiver(_) => Err(syn::Error::new( - arg.span(), - "Completion function cannot have self parameter", - )), - } -} +use syn::{Ident, ItemFn, parse_macro_input}; #[cfg(feature = "comp")] pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -110,25 +26,28 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Validate the function is not async if input_fn.sig.asyncness.is_some() { + use syn::spanned::Spanned; + return syn::Error::new(input_fn.sig.span(), "Completion function cannot be async") .to_compile_error() .into(); } - // Extract the parameter name - let param_name = match extract_param_name(&input_fn.sig) { - Ok(name) => name, - Err(e) => return e.to_compile_error().into(), - }; + // Get the function signature parts + let sig = &input_fn.sig; + let inputs = &sig.inputs; + let output = &sig.output; - // Extract and validate the parameter type (must be ShellContext) - if let Err(e) = extract_previous_type(&input_fn.sig) { - return e.to_compile_error().into(); - } + // Check that the function has exactly one parameter + if inputs.len() != 1 { + use syn::spanned::Spanned; - // Extract and validate the return type (must be Suggest) - if let Err(e) = extract_return_type(&input_fn.sig) { - return e.to_compile_error().into(); + return syn::Error::new( + inputs.span(), + "Completion function must have exactly one parameter", + ) + .to_compile_error() + .into(); } // Get the function body @@ -142,7 +61,7 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let vis = &input_fn.vis; // Get function name - let fn_name = &input_fn.sig.ident; + let fn_name = &sig.ident; // Generate struct name from function name using pascal_case let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string()); @@ -156,20 +75,27 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { impl ::mingling::Completion for #struct_name { type Previous = #previous_type_ident; - fn comp(#param_name: ::mingling::ShellContext) -> ::mingling::Suggest { - // This is just to prevent warnings about imported ShellContext and Suggest - let _ = ShellContext::default(); - let _ = Suggest::file_comp(); + fn comp(#inputs) #output { #fn_body } } // Keep the original function for internal use #(#fn_attrs)* - #vis fn #fn_name(#param_name: ::mingling::ShellContext) -> ::mingling::Suggest { + #vis fn #fn_name(#inputs) #output { #fn_body } }; + let completion_entry = quote! { + Self::#previous_type_ident => <#struct_name as ::mingling::Completion>::comp(ctx), + }; + + let mut completions = crate::COMPLETIONS.lock().unwrap(); + let completion_str = completion_entry.to_string(); + if !completions.contains(&completion_str) { + completions.push(completion_str); + } + expanded.into() } -- cgit