aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/attr/completion.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src/attr/completion.rs')
-rw-r--r--mingling_macros/src/attr/completion.rs228
1 files changed, 119 insertions, 109 deletions
diff --git a/mingling_macros/src/attr/completion.rs b/mingling_macros/src/attr/completion.rs
index 6d94685..71acaae 100644
--- a/mingling_macros/src/attr/completion.rs
+++ b/mingling_macros/src/attr/completion.rs
@@ -34,33 +34,92 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre
let inputs = &sig.inputs;
let output = &sig.output;
- if inputs.is_empty() {
- return syn::Error::new(
- inputs.span(),
- "Completion function must have at least one parameter: `ctx: &ShellContext`",
- )
- .to_compile_error()
- .into();
- }
+ // Parameter classification:
+ // - Owned (non-reference) parameters are **Shell sources**: each is derived
+ // from `&ShellContext` via `From<&ShellContext>` (identity `From` covers
+ // `ShellContext` itself).
+ // - `&T` / `&mut T` parameters are resource injections (unchanged).
+ // - `&ShellContext` is rejected with a helpful message: use the owned
+ // `ShellContext` (or any other `From<&ShellContext>` type) instead.
+ let mut derived_stmts: Vec<proc_macro2::TokenStream> = Vec::new();
+ let mut call_args: Vec<proc_macro2::TokenStream> = Vec::new();
+ let mut resources = Vec::new();
- let first_arg = &inputs[0];
- let _ctx_type = match first_arg {
- FnArg::Typed(PatType { ty, .. }) => (**ty).clone(),
- FnArg::Receiver(_) => {
- return syn::Error::new(
- first_arg.span(),
- "Completion function cannot have self parameter",
- )
- .to_compile_error()
- .into();
- }
- };
- let fixed_ctx: Pat = syn::parse_quote!(ctx);
+ for (idx, arg) in inputs.iter().enumerate() {
+ match arg {
+ FnArg::Typed(PatType { pat, ty, .. }) => {
+ if let Type::Reference(ref_type) = &**ty {
+ // `&ShellContext` is no longer allowed: it clashes with the
+ // resource-injection semantics of references.
+ if is_shell_context_path(&ref_type.elem) {
+ return syn::Error::new(
+ ty.span(),
+ "`&ShellContext` is not supported; use the owned `ShellContext` \
+ (or any other type implementing `From<&ShellContext>`) as a value \
+ parameter",
+ )
+ .to_compile_error()
+ .into();
+ }
- let resources = match extract_resources_from_args(sig, 1) {
- Ok(r) => r,
- Err(e) => return e.to_compile_error().into(),
- };
+ // Reference: resource injection (requires a named binding).
+ let var_name = match &**pat {
+ Pat::Ident(pat_ident) => pat_ident.ident.clone(),
+ _ => {
+ return syn::Error::new(
+ pat.span(),
+ "Resource injection parameter must be a simple identifier",
+ )
+ .to_compile_error()
+ .into();
+ }
+ };
+
+ // Reference: resource injection.
+ let (inner_type, is_mut) = match &*ref_type.elem {
+ Type::Path(type_path) => {
+ let is_mut = ref_type.mutability.is_some();
+ (type_path.clone(), is_mut)
+ }
+ _ => {
+ return syn::Error::new(
+ ty.span(),
+ "Reference resource type must be a type path",
+ )
+ .to_compile_error()
+ .into();
+ }
+ };
+ resources.push(ResourceInjection {
+ var_name: var_name.clone(),
+ full_type: (**ty).clone(),
+ inner_type,
+ is_ref: true,
+ is_mut,
+ });
+ call_args.push(quote! { #var_name });
+ } else {
+ // Owned value: derive from `&ShellContext`. The parameter
+ // name is irrelevant (anonymous `_` is fine) since the
+ // derived binding is generated by this macro.
+ let derived_ident = Ident::new(&format!("__ctx_derived_{idx}"), pat.span());
+ derived_stmts.push(quote! {
+ let #derived_ident: #ty =
+ <#ty as ::std::convert::From<&::mingling::ShellContext>>::from(ctx);
+ });
+ call_args.push(quote! { #derived_ident });
+ }
+ }
+ FnArg::Receiver(_) => {
+ return syn::Error::new(
+ arg.span(),
+ "Completion function cannot have self parameter",
+ )
+ .to_compile_error()
+ .into();
+ }
+ }
+ }
let fn_body = &input_fn.block;
@@ -77,24 +136,11 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre
let struct_name = Ident::new(&internal_name, fn_name.span());
let program_type = crate::default_program_path();
- let has_resources = !resources.is_empty();
let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect();
let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), &program_type);
- let resource_args: Vec<_> = resources
- .iter()
- .map(|res| {
- let var_name = &res.var_name;
- quote! { #var_name }
- })
- .collect();
-
- let fn_call = if has_resources {
- quote! { #fn_name(#fixed_ctx, #(#resource_args),*) }
- } else {
- quote! { #fn_name(#fixed_ctx) }
- };
+ let fn_call = quote! { #fn_name(#(#call_args),*) };
let inner_call = if mut_resources.is_empty() {
fn_call
@@ -112,13 +158,30 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre
wrapped
};
- let comp_body = if has_resources {
+ let comp_body = quote! {
+ #(#derived_stmts)*
+ #(#immut_resource_stmts)*
+ #inner_call
+ };
+
+ // A `()` return (or no return type) means "no suggestions": map it to an
+ // empty `Suggest` instead of requiring `Into<Suggest>`.
+ let returns_unit = match &sig.output {
+ syn::ReturnType::Default => true,
+ syn::ReturnType::Type(_, ty) => {
+ matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty())
+ }
+ };
+ let return_stmt = if returns_unit {
quote! {
- #(#immut_resource_stmts)*
- #inner_call
+ { #comp_body };
+ ::mingling::Suggest::new()
}
} else {
- quote! { #inner_call }
+ quote! {
+ let __completion_result = { #comp_body };
+ ::std::convert::Into::into(__completion_result)
+ }
};
let expanded: proc_macro2::TokenStream = quote! {
@@ -130,8 +193,8 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre
impl ::mingling::Completion for #struct_name {
type Previous = #previous_type_path;
- fn comp(#fixed_ctx: &::mingling::ShellContext) #output {
- #comp_body
+ fn comp(ctx: &::mingling::ShellContext) -> ::mingling::Suggest {
+ #return_stmt
}
}
@@ -166,69 +229,16 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre
expanded.into()
}
-/// Extract resource injection parameters from function arguments (skipping the first N params).
-fn extract_resources_from_args(
- sig: &syn::Signature,
- skip: usize,
-) -> syn::Result<Vec<ResourceInjection>> {
- let mut resources = Vec::new();
- for arg in sig.inputs.iter().skip(skip) {
- match arg {
- FnArg::Typed(PatType { pat, ty, .. }) => {
- let var_name = match &**pat {
- Pat::Ident(pat_ident) => pat_ident.ident.clone(),
- _ => {
- return Err(syn::Error::new(
- pat.span(),
- "Resource injection parameter must be a simple identifier",
- ));
- }
- };
-
- let full_type = *(*ty).clone();
-
- let (inner_type, is_ref, is_mut) = match &full_type {
- Type::Reference(ref_type) => match &*ref_type.elem {
- Type::Path(type_path) => {
- let is_mut = ref_type.mutability.is_some();
- (type_path.clone(), true, is_mut)
- }
- _ => {
- return Err(syn::Error::new(
- ty.span(),
- "Reference resource type must be a type path",
- ));
- }
- },
- Type::Path(_) => {
- return Err(syn::Error::new(
- ty.span(),
- "Resource injection parameter must be a reference (`&T` or `&mut T`)",
- ));
- }
- _ => {
- return Err(syn::Error::new(
- ty.span(),
- "Resource injection type must be a type path or reference",
- ));
- }
- };
-
- resources.push(ResourceInjection {
- var_name,
- full_type,
- inner_type,
- is_ref,
- is_mut,
- });
- }
- FnArg::Receiver(_) => {
- return Err(syn::Error::new(
- arg.span(),
- "Resource injection parameter cannot be self",
- ));
- }
- }
+/// Returns `true` when the type is a path whose last segment is `ShellContext`
+/// (e.g. `ShellContext` or `mingling::ShellContext`).
+fn is_shell_context_path(ty: &Type) -> bool {
+ if let Type::Path(type_path) = ty {
+ type_path
+ .path
+ .segments
+ .last()
+ .is_some_and(|seg| seg.ident == "ShellContext")
+ } else {
+ false
}
- Ok(resources)
}