fix/pattern-matching-breaks-compilation (#4)

This PR solves the issue identified at https://codeberg.org/JasterV/test-context/issues/2.

Previously, when defining the function argument that implements the `TestContext` trait, only "Ident" patterns were accepted (That is, only names).

So, the following was valid:

```rust
#[test]
fn my_test(context: MyContext) {}
```

But the following would throw an error:

```rust
#[test]
fn my_test(MyContext { n }: MyContext {}
```

With this PR, we are now able to accept any kind of "pattern" such as struct pattern matching, enum pattern matching... etc.

We only really care about the "type", and not the "pattern", so this PR makes sure that the "pattern" part of the binding is left untouched.

Tests have been added to ensure that destructuring a struct compiles

Co-authored-by: JasterV <49537445+JasterV@users.noreply.github.com>
Reviewed-on: https://codeberg.org/JasterV/test-context/pulls/4
This commit is contained in:
Victor Martinez Montané 2026-04-22 17:26:08 +02:00
parent bfe19a44c6
commit dc77d78608
3 changed files with 24 additions and 32 deletions

View file

@ -93,13 +93,12 @@ fn refactor_input_body(
let result_name = format_ident!("wrapped_result"); let result_name = format_ident!("wrapped_result");
let body = &input.block; let body = &input.block;
let is_async = input.sig.asyncness.is_some(); let is_async = input.sig.asyncness.is_some();
let context_arg_name = context_arg.name; let context_pattern = context_arg.pattern;
let context_binding = match context_arg.mode { let context_binding = match context_arg.mode {
ContextArgMode::Owned => quote! { let #context_arg_name = __context; }, ContextArgMode::Owned => quote! { let #context_pattern = __context; },
ContextArgMode::OwnedMut => quote! { let mut #context_arg_name = __context; }, ContextArgMode::Reference => quote! { let #context_pattern = &__context; },
ContextArgMode::Reference => quote! { let #context_arg_name = &__context; }, ContextArgMode::MutableReference => quote! { let #context_pattern = &mut __context; },
ContextArgMode::MutableReference => quote! { let #context_arg_name = &mut __context; },
}; };
let body = if args.skip_teardown && is_async { let body = if args.skip_teardown && is_async {

View file

@ -3,8 +3,8 @@ use syn::FnArg;
#[derive(Clone)] #[derive(Clone)]
pub struct ContextArg { pub struct ContextArg {
/// The identifier name used for the context argument. /// The original pattern (left side of a `pattern: type` expression).
pub name: syn::Ident, pub pattern: syn::Pat,
/// The mode in which the context was passed to the test function. /// The mode in which the context was passed to the test function.
pub mode: ContextArgMode, pub mode: ContextArgMode,
} }
@ -13,8 +13,6 @@ pub struct ContextArg {
pub enum ContextArgMode { pub enum ContextArgMode {
/// The argument was passed as an owned value (`ContextType`). Only valid with `skip_teardown`. /// The argument was passed as an owned value (`ContextType`). Only valid with `skip_teardown`.
Owned, Owned,
/// The argument was passed as an owned value (mut `ContextType`). Only valid with `skip_teardown`.
OwnedMut,
/// The argument was passed as an immutable reference (`&ContextType`). /// The argument was passed as an immutable reference (`&ContextType`).
Reference, Reference,
/// The argument was passed as a mutable reference (`&mut ContextType`). /// The argument was passed as a mutable reference (`&mut ContextType`).
@ -25,7 +23,6 @@ impl ContextArgMode {
pub fn is_owned(&self) -> bool { pub fn is_owned(&self) -> bool {
match self { match self {
ContextArgMode::Owned => true, ContextArgMode::Owned => true,
ContextArgMode::OwnedMut => true,
ContextArgMode::Reference => false, ContextArgMode::Reference => false,
ContextArgMode::MutableReference => false, ContextArgMode::MutableReference => false,
} }
@ -40,15 +37,14 @@ pub enum TestArg {
impl TestArg { impl TestArg {
pub fn parse_arg_with_expected_context(arg: FnArg, expected_context_type: &syn::Type) -> Self { pub fn parse_arg_with_expected_context(arg: FnArg, expected_context_type: &syn::Type) -> Self {
let syn::FnArg::Typed(pat_type) = &arg else { let pat_type = match arg {
return Self::Any(arg); FnArg::Typed(pat_type) => pat_type,
}; FnArg::Receiver(_) => return TestArg::Any(arg),
let syn::Pat::Ident(pat_ident) = &*pat_type.pat else {
return Self::Any(arg);
}; };
// fn example(pattern: arg_type)
let arg_type = &*pat_type.ty; let arg_type = &*pat_type.ty;
let pattern = &*pat_type.pat;
// Check for mutable/immutable reference // Check for mutable/immutable reference
if let syn::Type::Reference(type_ref) = arg_type if let syn::Type::Reference(type_ref) = arg_type
@ -61,28 +57,19 @@ impl TestArg {
}; };
return TestArg::Context(ContextArg { return TestArg::Context(ContextArg {
name: pat_ident.ident.clone(), pattern: pattern.to_owned(),
mode, mode,
}); });
} }
if !types_equal(arg_type, expected_context_type) { if types_equal(arg_type, expected_context_type) {
return TestArg::Any(arg); return TestArg::Context(ContextArg {
pattern: pattern.to_owned(),
mode: ContextArgMode::Owned,
});
} }
// To determine mutability for an owned type, we check the identifier pattern. TestArg::Any(FnArg::Typed(pat_type))
let mode = if pat_ident.mutability.is_some() {
// This catches signatures like: `mut my_ctx: ContextType`
ContextArgMode::OwnedMut
} else {
// This catches signatures like: `my_ctx: ContextType`
ContextArgMode::Owned
};
TestArg::Context(ContextArg {
name: pat_ident.ident.clone(),
mode,
})
} }
} }

View file

@ -25,6 +25,12 @@ fn test_sync_setup(ctx: &mut Context) {
assert_eq!(ctx.n, 1); assert_eq!(ctx.n, 1);
} }
#[test_context(Context)]
#[test]
fn test_pattern_match_setup(Context { n }: &mut Context) {
assert_eq!(*n, 1);
}
#[test_context(Context)] #[test_context(Context)]
#[test] #[test]
#[should_panic(expected = "Number changed")] #[should_panic(expected = "Number changed")]