Repository: diesel-rs/diesel (language: rust)

Issue:
Option field check in diesel_derives in incomplete
## Problem Description

The following code fails to compile:

```rust
diesel::table! {
    test_table (id) {
        id -> Int8,
        test -> Text,
    }
}

macro_rules! define {
    ($field_ty:ty) => {
        // Compilation error for S1
        #[derive(diesel::AsChangeset)]
        #[diesel(table_name = test_table)]
        pub struct S1 {
            pub test: $field_ty,
        }

        // Compilation is fine for S2
        #[derive(diesel::AsChangeset)]
        #[diesel(table_name = test_table)]
        pub struct S2 {
            pub test: Option<String>,
        }
    };
}

define!(Option<String>);
```
Playground link: https://www.rustexplorer.com/b/he8xgp

This is because the check for an `Option` field type in the proc-macro doesn't take into account the invisible delimiter introduced by the declarative macro:

https://github.com/diesel-rs/diesel/blob/5045fdb22307e3c7c8a8b9bd0b934dfa4736583b/diesel_derives/src/util.rs#L120-L138


Discussion / hints:

Fix patch:
diff --git a/diesel_derives/src/util.rs b/diesel_derives/src/util.rs
index 4f9930fcc113..f780bf37ad65 100644
--- a/diesel_derives/src/util.rs
+++ b/diesel_derives/src/util.rs
@@ -117,9 +117,18 @@ pub fn is_option_ty(ty: &Type) -> bool {
     option_ty_arg(ty).is_some()
 }
 
-fn option_ty_arg(ty: &Type) -> Option<&Type> {
+fn option_ty_arg(mut ty: &Type) -> Option<&Type> {
     use syn::PathArguments::AngleBracketed;
 
+    // Check the inner equivalent type
+    loop {
+        match ty {
+            Type::Group(group) => ty = &group.elem,
+            Type::Paren(paren) => ty = &paren.elem,
+            _ => break,
+        }
+    }
+
     match *ty {
         Type::Path(ref ty) => {
             let last_segment = ty.path.segments.iter().last().unwrap();


Test patch:
diff --git a/diesel_derives/tests/as_changeset.rs b/diesel_derives/tests/as_changeset.rs
index bcca5a77c33f..b4d7391a363f 100644
--- a/diesel_derives/tests/as_changeset.rs
+++ b/diesel_derives/tests/as_changeset.rs
@@ -525,3 +525,33 @@ fn option_fields_are_assigned_null_when_specified() {
     let actual = users::table.order(users::id).load(connection);
     assert_eq!(Ok(expected), actual);
 }
+
+#[test]
+#[allow(unused_parens)]
+fn option_fields_are_correctly_detected() {
+    diesel::table! {
+        test_table (id) {
+            id -> Int8,
+            test -> Text,
+        }
+    }
+
+    macro_rules! define {
+        ($field_ty:ty) => {
+            #[derive(diesel::AsChangeset)]
+            #[diesel(table_name = test_table)]
+            pub struct S1 {
+                pub test: (($field_ty)),
+            }
+
+            #[derive(diesel::AsChangeset)]
+            #[diesel(table_name = test_table)]
+            pub struct S2 {
+                pub test: (((Option<String>))),
+            }
+        };
+    }
+
+    // Causes a compile error if the field is not detected as `Option<T>`
+    define!((((Option<String>))));
+}