Repository: TeXitoi/structopt (Rust)

Issue:
[E0282] Cannot infer the result type of `map` 
If there is a type `T` for which `FromStr` and `FromIterator` are implemented and is used in an option as `Vec<T>` or `Option<Vec<T>>`, the build will fail by the following error.

```
error[E0282]: type annotations needed
   --> tests/issues.rs:140:18
    |
140 |         opt_vec: Vec<u16>,
    |         ---------^^^^^^^^
    |         |        |
    |         |        cannot infer type for type parameter `B` declared on the associated function `map`
    |         this method call resolves to `T`
```

The whole example code is

```rust
    struct U16ish;
    impl FromStr for U16ish {
        type Err = ();
        fn from_str(_: &str) -> Result<Self, Self::Err> {
            unimplemented!()
        }
    }
    impl<'a> FromIterator<&'a U16ish> for Vec<u16> {
        fn from_iter<T: IntoIterator<Item = &'a U16ish>>(_: T) -> Self {
            unimplemented!()
        }
    }

    #[derive(StructOpt, Debug)]
    struct Opt {
        opt_vec: Vec<u16>,
        #[structopt(long)]
        opt_opt_vec: Option<Vec<u16>>,
    }
```

The reason of this build failure is the type of the `map` in the expanded code becomes ambiguous due to `U16ish`.

```rust
    fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
        Opt {
            opt_vec: matches.values_of("opt_vec").map_or_else(Vec::new, |v| {
                v.map(|s| ::std::str::FromStr::from_str(s).unwrap())
                    .collect()
            }),
        }
    }
```

This `map` has two possible types, thus the type inference needs an annotation and the build fails.
```rust
// u16
            opt_vec: matches.values_of("opt_vec").map_or_else(Vec::new, |v| {
                v.map::<u16, _>(|s| ::std::str::FromStr::from_str(s).unwrap())
                    .collect()
            }),
// Or, U16ish is also possible
            opt_vec: matches.values_of("opt_vec").map_or_else(Vec::new, |v| {
                v.map::<U16ish, _>(|s| ::std::str::FromStr::from_str(s).unwrap())
                    .collect()
            }),
```


This situation may sound unlikely at first, but it actually happens when you use Structopt with [Indicatif](https://github.com/mitsuhiko/indicatif) crate on Windows. Indicatif defines `Utf16Char` on Windows and it causes this error.

I've also submitted a PR #491  to fix this. Thanks!


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c35dd2..0a0f9c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # v0.3.23 (unreleased)
 
 * Update minimal rust version to 1.46 because of bitflags 1.3
+* Fixed [a bug that occurs when the type of `map` becomes ambiguous](https://github.com/TeXitoi/structopt/issues/490).
 
 # v0.3.22 (2021-07-04)
 
diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs
index b8e86de..b80ae17 100644
--- a/structopt-derive/src/lib.rs
+++ b/structopt-derive/src/lib.rs
@@ -330,6 +330,13 @@ fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs)
                 let flag = *attrs.parser().kind == ParserKind::FromFlag;
                 let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
                 let name = attrs.cased_name();
+                let convert_type = match **ty {
+                    Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
+                    Ty::OptionOption | Ty::OptionVec => {
+                        sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
+                    }
+                    _ => &field.ty,
+                };
                 let field_value = match **ty {
                     Ty::Bool => quote_spanned!(ty.span()=> #matches.is_present(#name)),
 
@@ -349,7 +356,7 @@ fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs)
                     Ty::OptionVec => quote_spanned! { ty.span()=>
                         if #matches.is_present(#name) {
                             Some(#matches.#values_of(#name)
-                                 .map_or_else(Vec::new, |v| v.map(#parse).collect()))
+                                 .map_or_else(Vec::new, |v| v.map::<#convert_type, _>(#parse).collect()))
                         } else {
                             None
                         }
@@ -357,7 +364,7 @@ fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs)
 
                     Ty::Vec => quote_spanned! { ty.span()=>
                         #matches.#values_of(#name)
-                            .map_or_else(Vec::new, |v| v.map(#parse).collect())
+                            .map_or_else(Vec::new, |v| v.map::<#convert_type, _>(#parse).collect())
                     },
 
                     Ty::Other if occurrences => quote_spanned! { ty.span()=>


Test patch:
diff --git a/tests/issues.rs b/tests/issues.rs
index 8b4ac4b..5860c49 100644
--- a/tests/issues.rs
+++ b/tests/issues.rs
@@ -115,3 +115,32 @@ fn issue_359() {
         Opt::from_iter(&["test", "only_one_arg"])
     );
 }
+
+#[test]
+fn issue_490() {
+    use std::iter::FromIterator;
+    use std::str::FromStr;
+    use structopt::StructOpt;
+
+    struct U16ish;
+    impl FromStr for U16ish {
+        type Err = ();
+        fn from_str(_: &str) -> Result<Self, Self::Err> {
+            unimplemented!()
+        }
+    }
+    impl<'a> FromIterator<&'a U16ish> for Vec<u16> {
+        fn from_iter<T: IntoIterator<Item = &'a U16ish>>(_: T) -> Self {
+            unimplemented!()
+        }
+    }
+
+    #[derive(StructOpt, Debug)]
+    struct Opt {
+        opt_vec: Vec<u16>,
+        #[structopt(long)]
+        opt_opt_vec: Option<Vec<u16>>,
+    }
+
+    // Assert that it compiles
+}