Repository: JelteF/derive_more (rust)

Issue:
'overflow evaluating the requirement' when deriving debug for item that contains itself
the following code fails to compile

```
#[derive(derive_more::Debug)]
struct Message {
    inner: Box<Message>,
}

fn main() {}
```

```
error[E0275]: overflow evaluating the requirement `Message: Debug`
 --> src/main.rs:1:10
  |
1 | #[derive(derive_more::Debug)]
  |          ^^^^^^^^^^^^^^^^^^
  |
  = note: required for `Box<Message>` to implement `Debug`
  = help: see issue #48214
  = note: this error originates in the derive macro `derive_more::Debug` (in Nightly builds, run with -Z macro-backtrace for more info)
```

the same error is reported with stuff like `Vec<Message>` and with enum instead of struct.

```
$ cargo --version --verbose
cargo 1.78.0 (54d8815d0 2024-03-26)
release: 1.78.0
commit-hash: 54d8815d04fa3816edc207bbc4dd36bf18014dbc
commit-date: 2024-03-26
host: x86_64-unknown-linux-gnu
libgit2: 1.7.2 (sys:0.18.2 vendored)
libcurl: 8.6.0-DEV (sys:0.4.72+curl-8.6.0 vendored ssl:OpenSSL/1.1.1w)
ssl: OpenSSL 1.1.1w  11 Sep 2023
os: Arch Linux Rolling Release [64-bit]
$ cat Cargo.toml
[package]
name = "derive-more-debug"
version = "0.1.0"
edition = "2021"

[dependencies]
derive_more = { version = "1.0.0-beta.6", features = ["debug"] }
```

Pull request:
Only put Display-like bounds on type variables
Only put Display-like bounds on type variables

Resolves #363
Requires #377
Related to #371

## Synopsis

The problem is that the `Display` derive adds a bounds for all types that
are used in the format string. But this is not necessary for types that
don't contain a type variable. And adding those bounds can result in
errors like for the following code:

```rust
#[derive(Display, Debug)]
#[display("{inner:?}")]
#[display(bounds(T: Display))]
struct OptionalBox<T> {
    inner: Option<Box<T>>,
}

#[derive(Display, Debug)]
#[display("{next}")]
struct ItemStruct {
    next: OptionalBox<ItemStruct>,
}
```

That code would generate the following error:

```text
error[E0275]: overflow evaluating the requirement `ItemStruct: derive_more::Display`
```

## Solution

This makes sure we don't add unnecessary bounds for Display-like
derives. It does so in the same way as #371 did for the Debug derive: By
only adding bounds when the type contains a type variable.

## Checklist

- [x] Tests are added/updated (if required)

Changelog is not updated because the 0.99 did not have this issue.


Patch:
diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs
index 524f021..212860d 100644
--- a/impl/src/fmt/debug.rs
+++ b/impl/src/fmt/debug.rs
@@ -11,7 +11,10 @@ use crate::utils::{
     Either, Spanning,
 };
 
-use super::{trait_name_to_attribute_name, ContainerAttributes, FmtAttribute};
+use super::{
+    trait_name_to_attribute_name, ContainerAttributes, ContainsGenericsExt as _,
+    FmtAttribute,
+};
 
 /// Expands a [`fmt::Debug`] derive macro.
 ///
@@ -24,7 +27,7 @@ pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result<TokenStream> {
         .unwrap_or_default();
     let ident = &input.ident;
 
-    let type_params: Vec<_> = input
+    let type_params = input
         .generics
         .params
         .iter()
@@ -32,7 +35,7 @@ pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result<TokenStream> {
             syn::GenericParam::Type(t) => Some(&t.ident),
             syn::GenericParam::Const(..) | syn::GenericParam::Lifetime(..) => None,
         })
-        .collect();
+        .collect::<Vec<_>>();
 
     let (bounds, body) = match &input.data {
         syn::Data::Struct(s) => {
@@ -355,7 +358,7 @@ impl<'a> Expansion<'a> {
         if let Some(fmt) = self.attr.fmt.as_ref() {
             out.extend(fmt.bounded_types(self.fields).filter_map(
                 |(ty, trait_name)| {
-                    if !self.contains_generic_param(ty) {
+                    if !ty.contains_generics(self.type_params) {
                         return None;
                     }
 
@@ -369,7 +372,7 @@ impl<'a> Expansion<'a> {
             self.fields.iter().try_fold(out, |mut out, field| {
                 let ty = &field.ty;
 
-                if !self.contains_generic_param(ty) {
+                if !ty.contains_generics(self.type_params) {
                     return Ok(out);
                 }
 
@@ -392,105 +395,4 @@ impl<'a> Expansion<'a> {
             })
         }
     }
-
-    /// Checks whether the provided [`syn::Path`] contains any of these [`Expansion::type_params`].
-    fn path_contains_generic_param(&self, path: &syn::Path) -> bool {
-        path.segments
-            .iter()
-            .any(|segment| match &segment.arguments {
-                syn::PathArguments::None => false,
-                syn::PathArguments::AngleBracketed(
-                    syn::AngleBracketedGenericArguments { args, .. },
-                ) => args.iter().any(|generic| match generic {
-                    syn::GenericArgument::Type(ty)
-                    | syn::GenericArgument::AssocType(syn::AssocType { ty, .. }) => {
-                        self.contains_generic_param(ty)
-                    }
-
-                    syn::GenericArgument::Lifetime(_)
-                    | syn::GenericArgument::Const(_)
-                    | syn::GenericArgument::AssocConst(_)
-                    | syn::GenericArgument::Constraint(_) => false,
-                    _ => unimplemented!(
-                        "syntax is not supported by `derive_more`, please report a bug",
-                    ),
-                }),
-                syn::PathArguments::Parenthesized(
-                    syn::ParenthesizedGenericArguments { inputs, output, .. },
-                ) => {
-                    inputs.iter().any(|ty| self.contains_generic_param(ty))
-                        || match output {
-                            syn::ReturnType::Default => false,
-                            syn::ReturnType::Type(_, ty) => {
-                                self.contains_generic_param(ty)
-                            }
-                        }
-                }
-            })
-    }
-
-    /// Checks whether the provided [`syn::Type`] contains any of these [`Expansion::type_params`].
-    fn contains_generic_param(&self, ty: &syn::Type) -> bool {
-        if self.type_params.is_empty() {
-            return false;
-        }
-        match ty {
-            syn::Type::Path(syn::TypePath { qself, path }) => {
-                if let Some(qself) = qself {
-                    if self.contains_generic_param(&qself.ty) {
-                        return true;
-                    }
-                }
-
-                if let Some(ident) = path.get_ident() {
-                    self.type_params.iter().any(|param| *param == ident)
-                } else {
-                    self.path_contains_generic_param(path)
-                }
-            }
-
-            syn::Type::Array(syn::TypeArray { elem, .. })
-            | syn::Type::Group(syn::TypeGroup { elem, .. })
-            | syn::Type::Paren(syn::TypeParen { elem, .. })
-            | syn::Type::Ptr(syn::TypePtr { elem, .. })
-            | syn::Type::Reference(syn::TypeReference { elem, .. })
-            | syn::Type::Slice(syn::TypeSlice { elem, .. }) => {
-                self.contains_generic_param(elem)
-            }
-
-            syn::Type::BareFn(syn::TypeBareFn { inputs, output, .. }) => {
-                inputs
-                    .iter()
-                    .any(|arg| self.contains_generic_param(&arg.ty))
-                    || match output {
-                        syn::ReturnType::Default => false,
-                        syn::ReturnType::Type(_, ty) => self.contains_generic_param(ty),
-                    }
-            }
-            syn::Type::Tuple(syn::TypeTuple { elems, .. }) => {
-                elems.iter().any(|ty| self.contains_generic_param(ty))
-            }
-
-            syn::Type::ImplTrait(_) => false,
-            syn::Type::Infer(_) => false,
-            syn::Type::Macro(_) => false,
-            syn::Type::Never(_) => false,
-            syn::Type::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
-                bounds.iter().any(|bound| match bound {
-                    syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) => {
-                        self.path_contains_generic_param(path)
-                    }
-                    syn::TypeParamBound::Lifetime(_) => false,
-                    syn::TypeParamBound::Verbatim(_) => false,
-                    _ => unimplemented!(
-                        "syntax is not supported by `derive_more`, please report a bug",
-                    ),
-                })
-            }
-            syn::Type::Verbatim(_) => false,
-            _ => unimplemented!(
-                "syntax is not supported by `derive_more`, please report a bug",
-            ),
-        }
-    }
 }
diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs
index 9e86bd5..429e78b 100644
--- a/impl/src/fmt/display.rs
+++ b/impl/src/fmt/display.rs
@@ -9,7 +9,10 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
 
 use crate::utils::{attr::ParseMultiple as _, Spanning};
 
-use super::{trait_name_to_attribute_name, ContainerAttributes, FmtAttribute};
+use super::{
+    trait_name_to_attribute_name, ContainerAttributes, ContainsGenericsExt as _,
+    FmtAttribute,
+};
 
 /// Expands a [`fmt::Display`]-like derive macro.
 ///
@@ -32,7 +35,17 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenSt
     let trait_ident = format_ident!("{trait_name}");
     let ident = &input.ident;
 
-    let ctx = (&attrs, ident, &trait_ident, &attr_name);
+    let type_params = input
+        .generics
+        .params
+        .iter()
+        .filter_map(|p| match p {
+            syn::GenericParam::Type(t) => Some(&t.ident),
+            syn::GenericParam::Const(..) | syn::GenericParam::Lifetime(..) => None,
+        })
+        .collect::<Vec<_>>();
+
+    let ctx: ExpansionCtx = (&attrs, &type_params, ident, &trait_ident, &attr_name);
     let (bounds, body) = match &input.data {
         syn::Data::Struct(s) => expand_struct(s, ctx),
         syn::Data::Enum(e) => expand_enum(e, ctx),
@@ -62,6 +75,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenSt
 
 /// Type alias for an expansion context:
 /// - [`ContainerAttributes`].
+/// - Type parameters. Slice of [`syn::Ident`].
 /// - Struct/enum/union [`syn::Ident`].
 /// - Derived trait [`syn::Ident`].
 /// - Attribute name [`syn::Ident`].
@@ -69,6 +83,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenSt
 /// [`syn::Ident`]: struct@syn::Ident
 type ExpansionCtx<'a> = (
     &'a ContainerAttributes,
+    &'a [&'a syn::Ident],
     &'a syn::Ident,
     &'a syn::Ident,
     &'a syn::Ident,
@@ -77,12 +92,13 @@ type ExpansionCtx<'a> = (
 /// Expands a [`fmt::Display`]-like derive macro for the provided struct.
 fn expand_struct(
     s: &syn::DataStruct,
-    (attrs, ident, trait_ident, _): ExpansionCtx<'_>,
+    (attrs, type_params, ident, trait_ident, _): ExpansionCtx<'_>,
 ) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
     let s = Expansion {
         shared_attr: None,
         attrs,
         fields: &s.fields,
+        type_params,
         trait_ident,
         ident,
     };
@@ -111,7 +127,7 @@ fn expand_struct(
 /// Expands a [`fmt`]-like derive macro for the provided enum.
 fn expand_enum(
     e: &syn::DataEnum,
-    (container_attrs, _, trait_ident, attr_name): ExpansionCtx<'_>,
+    (container_attrs, type_params, _, trait_ident, attr_name): ExpansionCtx<'_>,
 ) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
     if let Some(shared_fmt) = &container_attrs.fmt {
         if shared_fmt
@@ -153,6 +169,7 @@ fn expand_enum(
                 shared_attr: container_attrs.fmt.as_ref(),
                 attrs: &attrs,
                 fields: &variant.fields,
+                type_params,
                 trait_ident,
                 ident,
             };
@@ -190,7 +207,7 @@ fn expand_enum(
 /// Expands a [`fmt::Display`]-like derive macro for the provided union.
 fn expand_union(
     u: &syn::DataUnion,
-    (attrs, _, _, attr_name): ExpansionCtx<'_>,
+    (attrs, _, _, _, attr_name): ExpansionCtx<'_>,
 ) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
     let fmt = &attrs.fmt.as_ref().ok_or_else(|| {
         syn::Error::new(
@@ -227,6 +244,9 @@ struct Expansion<'a> {
     /// Struct or enum [`syn::Fields`].
     fields: &'a syn::Fields,
 
+    /// Type parameters in this struct or enum.
+    type_params: &'a [&'a syn::Ident],
+
     /// [`fmt`] trait [`syn::Ident`].
     ///
     /// [`syn::Ident`]: struct@syn::Ident
@@ -338,28 +358,37 @@ impl<'a> Expansion<'a> {
             if let Some(fmt) = &self.attrs.fmt {
                 bounds.extend(
                     fmt.bounded_types(self.fields)
-                        .map(|(ty, trait_name)| {
+                        .filter_map(|(ty, trait_name)| {
+                            if !ty.contains_generics(self.type_params) {
+                                return None;
+                            }
                             let trait_ident = format_ident!("{trait_name}");
 
-                            parse_quote! { #ty: derive_more::core::fmt::#trait_ident }
+                            Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
                         })
                         .chain(self.attrs.bounds.0.clone()),
                 );
             } else {
-                bounds.extend(self.fields.iter().next().map(|f| {
+                bounds.extend(self.fields.iter().next().and_then(|f| {
                     let ty = &f.ty;
+                    if !ty.contains_generics(self.type_params) {
+                        return None;
+                    }
                     let trait_ident = &self.trait_ident;
-                    parse_quote! { #ty: derive_more::core::fmt::#trait_ident }
-                }))
+                    Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
+                }));
             };
         }
 
         if let Some(shared_fmt) = &self.shared_attr {
-            bounds.extend(shared_fmt.bounded_types(self.fields).map(
+            bounds.extend(shared_fmt.bounded_types(self.fields).filter_map(
                 |(ty, trait_name)| {
+                    if !ty.contains_generics(self.type_params) {
+                        return None;
+                    }
                     let trait_ident = format_ident!("{trait_name}");
 
-                    parse_quote! { #ty: derive_more::core::fmt::#trait_ident }
+                    Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
                 },
             ));
         }
diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs
index bd559c3..62b57f0 100644
--- a/impl/src/fmt/mod.rs
+++ b/impl/src/fmt/mod.rs
@@ -523,6 +523,121 @@ where
     }
 }
 
+trait ContainsGenericsExt {
+    /// Checks whether this definition contains any of the provided `type_params`.
+    fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool;
+}
+
+impl ContainsGenericsExt for syn::Type {
+    fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool {
+        if type_params.is_empty() {
+            return false;
+        }
+        match self {
+            Self::Path(syn::TypePath { qself, path }) => {
+                if let Some(qself) = qself {
+                    if qself.ty.contains_generics(type_params) {
+                        return true;
+                    }
+                }
+
+                if let Some(ident) = path.get_ident() {
+                    type_params.iter().any(|param| *param == ident)
+                } else {
+                    path.contains_generics(type_params)
+                }
+            }
+
+            Self::Array(syn::TypeArray { elem, .. })
+            | Self::Group(syn::TypeGroup { elem, .. })
+            | Self::Paren(syn::TypeParen { elem, .. })
+            | Self::Ptr(syn::TypePtr { elem, .. })
+            | Self::Reference(syn::TypeReference { elem, .. })
+            | Self::Slice(syn::TypeSlice { elem, .. }) => {
+                elem.contains_generics(type_params)
+            }
+
+            Self::BareFn(syn::TypeBareFn { inputs, output, .. }) => {
+                inputs
+                    .iter()
+                    .any(|arg| arg.ty.contains_generics(type_params))
+                    || match output {
+                        syn::ReturnType::Default => false,
+                        syn::ReturnType::Type(_, ty) => {
+                            ty.contains_generics(type_params)
+                        }
+                    }
+            }
+
+            Self::Tuple(syn::TypeTuple { elems, .. }) => {
+                elems.iter().any(|ty| ty.contains_generics(type_params))
+            }
+
+            Self::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
+                bounds.iter().any(|bound| match bound {
+                    syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) => {
+                        path.contains_generics(type_params)
+                    }
+                    syn::TypeParamBound::Lifetime(..)
+                    | syn::TypeParamBound::Verbatim(..) => false,
+                    _ => unimplemented!(
+                        "syntax is not supported by `derive_more`, please report a bug",
+                    ),
+                })
+            }
+
+            Self::ImplTrait(..)
+            | Self::Infer(..)
+            | Self::Macro(..)
+            | Self::Never(..)
+            | Self::Verbatim(..) => false,
+            _ => unimplemented!(
+                "syntax is not supported by `derive_more`, please report a bug",
+            ),
+        }
+    }
+}
+
+impl ContainsGenericsExt for syn::Path {
+    fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool {
+        if type_params.is_empty() {
+            return false;
+        }
+        self.segments
+            .iter()
+            .any(|segment| match &segment.arguments {
+                syn::PathArguments::None => false,
+                syn::PathArguments::AngleBracketed(
+                    syn::AngleBracketedGenericArguments { args, .. },
+                ) => args.iter().any(|generic| match generic {
+                    syn::GenericArgument::Type(ty)
+                    | syn::GenericArgument::AssocType(syn::AssocType { ty, .. }) => {
+                        ty.contains_generics(type_params)
+                    }
+
+                    syn::GenericArgument::Lifetime(..)
+                    | syn::GenericArgument::Const(..)
+                    | syn::GenericArgument::AssocConst(..)
+                    | syn::GenericArgument::Constraint(..) => false,
+                    _ => unimplemented!(
+                        "syntax is not supported by `derive_more`, please report a bug",
+                    ),
+                }),
+                syn::PathArguments::Parenthesized(
+                    syn::ParenthesizedGenericArguments { inputs, output, .. },
+                ) => {
+                    inputs.iter().any(|ty| ty.contains_generics(type_params))
+                        || match output {
+                            syn::ReturnType::Default => false,
+                            syn::ReturnType::Type(_, ty) => {
+                                ty.contains_generics(type_params)
+                            }
+                        }
+                }
+            })
+    }
+}
+
 #[cfg(test)]
 mod fmt_attribute_spec {
     use itertools::Itertools as _;


Test patch:
diff --git a/tests/display.rs b/tests/display.rs
index 2e3f062..60de818 100644
--- a/tests/display.rs
+++ b/tests/display.rs
@@ -2314,3 +2314,184 @@ mod generic {
         }
     }
 }
+
+// See: https://github.com/JelteF/derive_more/issues/363
+mod type_variables {
+    mod our_alloc {
+        #[cfg(not(feature = "std"))]
+        pub use alloc::{boxed::Box, format, vec::Vec};
+        #[cfg(feature = "std")]
+        pub use std::{boxed::Box, format, vec::Vec};
+    }
+
+    use our_alloc::{format, Box};
+
+    // We want `Vec` in scope to test that code generation works if it is there.
+    #[allow(unused_imports)]
+    use our_alloc::Vec;
+
+    use derive_more::Display;
+
+    #[derive(Display, Debug)]
+    #[display("{inner:?}")]
+    #[display(bounds(T: Display))]
+    struct OptionalBox<T> {
+        inner: Option<Box<T>>,
+    }
+
+    #[derive(Display, Debug)]
+    #[display("{next}")]
+    struct ItemStruct {
+        next: OptionalBox<ItemStruct>,
+    }
+
+    #[derive(Display)]
+    #[derive(Debug)]
+    struct ItemTuple(OptionalBox<ItemTuple>);
+
+    #[derive(Display)]
+    #[derive(Debug)]
+    #[display("Item({_0})")]
+    struct ItemTupleContainerFmt(OptionalBox<ItemTupleContainerFmt>);
+
+    #[derive(Display, Debug)]
+    #[display("{next}")]
+    enum ItemEnumOuterFormat {
+        Variant1 {
+            next: OptionalBox<ItemEnumOuterFormat>,
+        },
+        Variant2 {
+            next: OptionalBox<i32>,
+        },
+    }
+
+    #[derive(Display, Debug)]
+    enum ItemEnumInnerFormat {
+        #[display("{next} {inner}")]
+        Node {
+            next: OptionalBox<ItemEnumInnerFormat>,
+            inner: i32,
+        },
+        #[display("{inner}")]
+        Leaf { inner: i32 },
+    }
+
+    #[derive(Display)]
+    #[derive(Debug)]
+    #[display("{next:?}, {real:?}")]
+    struct VecMeansDifferent<Vec> {
+        next: our_alloc::Vec<i32>,
+        real: Vec,
+    }
+
+    #[derive(Display)]
+    #[derive(Debug)]
+    #[display("{t:?}")]
+    struct Array<T> {
+        t: [T; 10],
+    }
+
+    mod parens {
+        #![allow(unused_parens)] // test that type is found even in parentheses
+
+        use derive_more::Display;
+
+        #[derive(Display)]
+        struct Paren<T> {
+            t: (T),
+        }
+    }
+
+    #[derive(Display)]
+    struct ParenthesizedGenericArgumentsInput<T> {
+        t: dyn Fn(T) -> i32,
+    }
+
+    #[derive(Display)]
+    struct ParenthesizedGenericArgumentsOutput<T> {
+        t: dyn Fn(i32) -> T,
+    }
+
+    #[derive(Display)]
+    struct Ptr<T> {
+        t: *const T,
+    }
+
+    #[derive(Display)]
+    struct Reference<'a, T> {
+        t: &'a T,
+    }
+
+    #[derive(Display)]
+    struct Slice<'a, T> {
+        t: &'a [T],
+    }
+
+    #[derive(Display)]
+    struct BareFn<T> {
+        t: Box<fn(T) -> T>,
+    }
+
+    #[derive(Display)]
+    struct Tuple<T> {
+        t: Box<(T, T)>,
+    }
+
+    trait MyTrait<T> {}
+
+    #[derive(Display)]
+    struct TraitObject<T> {
+        t: Box<dyn MyTrait<T>>,
+    }
+
+    #[test]
+    fn assert() {
+        assert_eq!(
+            format!(
+                "{}",
+                ItemStruct {
+                    next: OptionalBox {
+                        inner: Some(Box::new(ItemStruct {
+                            next: OptionalBox { inner: None },
+                        })),
+                    },
+                },
+            ),
+            "Some(ItemStruct { next: OptionalBox { inner: None } })",
+        );
+
+        assert_eq!(
+            format!(
+                "{}",
+                ItemTuple(OptionalBox {
+                    inner: Some(Box::new(ItemTuple(OptionalBox { inner: None }))),
+                }),
+            ),
+            "Some(ItemTuple(OptionalBox { inner: None }))",
+        );
+
+        assert_eq!(
+            format!(
+                "{}",
+                ItemTupleContainerFmt(OptionalBox {
+                    inner: Some(Box::new(ItemTupleContainerFmt(OptionalBox {
+                        inner: None,
+                    }))),
+                }),
+            ),
+            "Item(Some(ItemTupleContainerFmt(OptionalBox { inner: None })))",
+        );
+
+        let item = ItemEnumOuterFormat::Variant1 {
+            next: OptionalBox {
+                inner: Some(Box::new(ItemEnumOuterFormat::Variant2 {
+                    next: OptionalBox { inner: None },
+                })),
+            },
+        };
+        assert_eq!(
+            format!("{item}"),
+            "Some(Variant2 { next: OptionalBox { inner: None } })",
+        )
+    }
+}