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

Issue:
Compilation error with const-generic in custom sql type 
## Setup


### Versions

- **Rust:** 1.53.0
- **Diesel:** 1.4.7
- **Database:** Postgres
- **Operating System** Ubuntu x86_64

### Feature Flags

- **diesel:** [ "postgres" ]

## Problem Description
Compilation error occurs when trying to build custom sql type with a const-generic.

### What are you trying to accomplish?
Using diesel to derive a rust generic sized array stored in postgres backed by a postgres array.

### What is the expected output?
Compilation occurs successfully

### What is the actual output?
Compilation fails when trying to derive `AsExpression`.

### Are you seeing any additional errors?
No. I can implement `ToSql<Array<Float8>, Pg>` and `FromSql<Array<Float8>, Pg>` without errors as well.

### Steps to reproduce

Create a new project with the following files:

`**src/main.rs**`
```rust
use diesel::{
    sql_types::{Array, Float8},
    AsExpression,
};

#[derive(AsExpression)]
#[sql_type = "Array<Float8>"]
struct DoubleArray<const N: usize>([f64; N]);

fn main() {
    println!("Hello, world!");
}

```

`**Cargo.toml**`
```toml
[package]
name = "test-rs"
version = "0.1.0"
edition = "2018"

[dependencies]
diesel = { version = "1.4" , features = ["postgres"] }
```

Execute: `cargo check` and see the following errors

> error[E0412]: cannot find type `N` in this scope

> error[E0747]: unresolved item provided when a constant was expected

I can also provide additional files like `schema.rs` if needed, but it doesn't seem necessary to produce the bug.

## Checklist

- [ x ] I have already looked over the [issue tracker](https://github.com/diesel-rs/diesel/issues)  and the [discussion forum](https://github.com/diesel-rs/diesel/discussions)  for similar possible closed issues.
- [ x ] This issue can be reproduced on Rust's stable channel. (Your issue will be
  closed if this is not the case)
- [ x  ] This issue can be reproduced without requiring a third party crate


Discussion / hints:
Followup: I may be able to take a look at this issue myself, but I don't have much experience with the diesel codebase. Where should I start?

Fix patch:
diff --git a/diesel_derives/src/as_expression.rs b/diesel_derives/src/as_expression.rs
index 427819e80635..695b9041ca28 100644
--- a/diesel_derives/src/as_expression.rs
+++ b/diesel_derives/src/as_expression.rs
@@ -18,13 +18,15 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
     let (impl_generics, ..) = item.generics.split_for_impl();
     let lifetimes = item.generics.lifetimes().collect::<Vec<_>>();
     let ty_params = item.generics.type_params().collect::<Vec<_>>();
+    let const_params = item.generics.const_params().collect::<Vec<_>>();
     let struct_ty = ty_for_foreign_derive(&item, &flags)?;
 
     let tokens = sql_types.map(|sql_type| {
         let lifetimes = &lifetimes;
         let ty_params = &ty_params;
+        let const_params = &const_params;
         let tokens = quote!(
-            impl<'expr, #(#lifetimes,)* #(#ty_params,)*> AsExpression<#sql_type>
+            impl<'expr, #(#lifetimes,)* #(#ty_params,)* #(#const_params,)*> AsExpression<#sql_type>
                 for &'expr #struct_ty
             {
                 type Expression = Bound<#sql_type, Self>;
@@ -34,7 +36,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
                 }
             }
 
-            impl<'expr, #(#lifetimes,)* #(#ty_params,)*> AsExpression<Nullable<#sql_type>>
+            impl<'expr, #(#lifetimes,)* #(#ty_params,)* #(#const_params,)*> AsExpression<Nullable<#sql_type>>
                 for &'expr #struct_ty
             {
                 type Expression = Bound<Nullable<#sql_type>, Self>;
@@ -44,7 +46,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
                 }
             }
 
-            impl<'expr2, 'expr, #(#lifetimes,)* #(#ty_params,)*> AsExpression<#sql_type>
+            impl<'expr2, 'expr, #(#lifetimes,)* #(#ty_params,)* #(#const_params,)*> AsExpression<#sql_type>
                 for &'expr2 &'expr #struct_ty
             {
                 type Expression = Bound<#sql_type, Self>;
@@ -54,7 +56,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
                 }
             }
 
-            impl<'expr2, 'expr, #(#lifetimes,)* #(#ty_params,)*> AsExpression<Nullable<#sql_type>>
+            impl<'expr2, 'expr, #(#lifetimes,)* #(#ty_params,)* #(#const_params,)*> AsExpression<Nullable<#sql_type>>
                 for &'expr2 &'expr #struct_ty
             {
                 type Expression = Bound<Nullable<#sql_type>, Self>;
@@ -64,7 +66,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
                 }
             }
 
-            impl<#(#lifetimes,)* #(#ty_params,)* __DB> diesel::serialize::ToSql<Nullable<#sql_type>, __DB>
+            impl<#(#lifetimes,)* #(#ty_params,)* __DB,  #(#const_params,)*> diesel::serialize::ToSql<Nullable<#sql_type>, __DB>
                 for #struct_ty
             where
                 __DB: diesel::backend::Backend,
diff --git a/diesel_derives/src/from_sql_row.rs b/diesel_derives/src/from_sql_row.rs
index 81a56b83d14b..eb8758db633d 100644
--- a/diesel_derives/src/from_sql_row.rs
+++ b/diesel_derives/src/from_sql_row.rs
@@ -9,8 +9,6 @@ pub fn derive(mut item: syn::DeriveInput) -> Result<TokenStream, Diagnostic> {
         MetaItem::with_name(&item.attrs, "diesel").unwrap_or_else(|| MetaItem::empty("diesel"));
     let struct_ty = ty_for_foreign_derive(&item, &flags)?;
 
-    item.generics.params.push(parse_quote!(__ST));
-    item.generics.params.push(parse_quote!(__DB));
     {
         let where_clause = item
             .generics
@@ -26,12 +24,17 @@ pub fn derive(mut item: syn::DeriveInput) -> Result<TokenStream, Diagnostic> {
             .predicates
             .push(parse_quote!(Self: FromSql<__ST, __DB>));
     }
-    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
+    let (_, _, where_clause) = item.generics.split_for_impl();
+
+    let lifetimes = item.generics.lifetimes().collect::<Vec<_>>();
+    let ty_params = item.generics.type_params().collect::<Vec<_>>();
+    let const_params = item.generics.const_params().collect::<Vec<_>>();
 
     Ok(wrap_in_dummy_mod(quote! {
         use diesel::deserialize::{self, FromSql, Queryable};
 
-        impl #impl_generics Queryable<__ST, __DB> for #struct_ty
+        // Need to put __ST and __DB after lifetimes but before const params
+        impl<#(#lifetimes,)* __ST, __DB, #(#ty_params,)* #(#const_params,)*> Queryable<__ST, __DB> for #struct_ty
         #where_clause
         {
             type Row = Self;
diff --git a/diesel_derives/src/meta.rs b/diesel_derives/src/meta.rs
index 0cbfc8e5c341..976c44d337a4 100644
--- a/diesel_derives/src/meta.rs
+++ b/diesel_derives/src/meta.rs
@@ -181,8 +181,10 @@ impl MetaItem {
 
     pub fn ty_value(&self) -> Result<syn::Type, Diagnostic> {
         let str = self.lit_str_value()?;
-        str.parse()
-            .map_err(|_| str.span().error("Invalid Rust type"))
+        str.parse().map_err(|_| {
+            str.span()
+                .error(format!("Invalid Rust type: `{}`", str.value()))
+        })
     }
 
     pub fn expect_str_value(&self) -> String {


Test patch:
diff --git a/diesel_compile_tests/tests/fail/as_expression_bad_sql_type.stderr b/diesel_compile_tests/tests/fail/as_expression_bad_sql_type.stderr
index 329d9b557555..45749c265d46 100644
--- a/diesel_compile_tests/tests/fail/as_expression_bad_sql_type.stderr
+++ b/diesel_compile_tests/tests/fail/as_expression_bad_sql_type.stderr
@@ -10,13 +10,13 @@ error: `sql_type` must be in the form `sql_type = "value"`
 6 | #[sql_type]
   |   ^^^^^^^^
 
-error: Invalid Rust type
+error: Invalid Rust type: `@%&&*`
  --> $DIR/as_expression_bad_sql_type.rs:7:14
   |
 7 | #[sql_type = "@%&&*"]
   |              ^^^^^^^
 
-error: Invalid Rust type
+error: Invalid Rust type: `1omg`
  --> $DIR/as_expression_bad_sql_type.rs:8:14
   |
 8 | #[sql_type = "1omg"]
diff --git a/diesel_derives/tests/as_expression.rs b/diesel_derives/tests/as_expression.rs
new file mode 100644
index 000000000000..cc714ff22c85
--- /dev/null
+++ b/diesel_derives/tests/as_expression.rs
@@ -0,0 +1,61 @@
+use diesel::backend::Backend;
+use diesel::deserialize::FromSql;
+use diesel::expression::AsExpression;
+use diesel::serialize::{Output, ToSql};
+use diesel::sql_types::Text;
+use diesel::*;
+use std::convert::TryInto;
+use std::io::Write;
+
+use helpers::connection;
+
+table! {
+    my_structs (foo) {
+        foo -> Integer,
+        bar -> Text,
+    }
+}
+
+#[derive(Debug, AsExpression, FromSqlRow, Clone, Copy, PartialEq)]
+#[sql_type = "Text"]
+struct StringArray<const N: usize>(pub [u8; N]);
+
+impl<DB, const N: usize> FromSql<Text, DB> for StringArray<N>
+where
+    DB: Backend,
+    String: FromSql<Text, DB>,
+{
+    fn from_sql(bytes: backend::RawValue<DB>) -> deserialize::Result<Self> {
+        let string = <String as FromSql<Text, DB>>::from_sql(bytes)?;
+        let bytes_array: [u8; N] = string.into_bytes().try_into().unwrap();
+        Ok(StringArray(bytes_array))
+    }
+}
+
+impl<DB, const N: usize> ToSql<Text, DB> for StringArray<N>
+where
+    DB: Backend,
+    String: ToSql<Text, DB>,
+{
+    fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
+        let string = std::str::from_utf8(&self.0).unwrap().to_owned();
+
+        string.to_sql(out)
+    }
+}
+
+#[test]
+fn struct_with_sql_type() {
+    #[derive(Debug, Clone, PartialEq, Queryable, Selectable)]
+    #[table_name = "my_structs"]
+    struct MyStruct {
+        foo: i32,
+        bar: StringArray<4>,
+    }
+
+    let conn = &mut connection();
+    let data = my_structs::table
+        .select(MyStruct::as_select())
+        .get_result(conn);
+    assert!(data.is_err());
+}
diff --git a/diesel_derives/tests/tests.rs b/diesel_derives/tests/tests.rs
index 561d0375a2a0..0d09bf99ab2e 100644
--- a/diesel_derives/tests/tests.rs
+++ b/diesel_derives/tests/tests.rs
@@ -9,6 +9,7 @@ mod helpers;
 mod schema;
 
 mod as_changeset;
+mod as_expression;
 mod associations;
 mod identifiable;
 mod insertable;