Infer a generic struct's parameters from its field values
`Quad { b, g, r, a }` with no annotation: Nim cannot infer an object's generic
parameters from a constructor's field values, so rustnim refused. But the
field values do say what they are -- unifying the declared field types against
the actual ones binds the parameters, which is where Rust reads them from too.
The expectation still wins when there is one. The refusal remains when neither
says, and now says that rather than blaming Nim.
tests/cases/069.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>d6e6f38 parent: 1250a56 modified
src/lower.rs +24 -5 | @@ -5841,6 +5841,7 @@ impl Lowerer { | ||
| 5841 | 5841 | other => other.to_string(), |
| 5842 | 5842 | }; |
| 5843 | 5843 | let mut parts = Vec::new(); |
| 5844 | + let mut field_tys: Vec<(Nim, Nim)> = Vec::new(); | |
| 5844 | 5845 | for f in &s.fields { |
| 5845 | 5846 | let fname = match &f.member { |
| 5846 | 5847 | syn::Member::Named(n) => n.to_string(), |
| @@ -5852,6 +5853,9 @@ impl Lowerer { | ||
| 5852 | 5853 | .and_then(|fs| fs.iter().find(|(n, _)| *n == fname)) |
| 5853 | 5854 | .map(|(_, t)| t.clone()); |
| 5854 | 5855 | let v = self.expr_at(&f.expr, want.as_ref())?; |
| 5856 | + if let (Some(d), Some(a)) = (&want, &v.ty) { | |
| 5857 | + field_tys.push((d.clone(), a.clone())); | |
| 5858 | + } | |
| 5855 | 5859 | parts.push(format!("{}: {}", ident(&fname), v.code)); |
| 5856 | 5860 | } |
| 5857 | 5861 | // Nim cannot infer an object's generic parameters from a |
| @@ -5865,11 +5869,26 @@ impl Lowerer { | ||
| 5865 | 5869 | Nim::Named(name.clone(), a.clone()) |
| 5866 | 5870 | } |
| 5867 | 5871 | _ => { |
| 5868 | - return Err(format!( | |
| 5869 | - "`{name} {{ .. }}` is generic, and Nim cannot infer \ | |
| 5870 | - its parameters from the field values; annotate the \ | |
| 5871 | - binding or the return type" | |
| 5872 | - )) | |
| 5872 | + // Failing an expectation, the field values say | |
| 5873 | + // what the parameters are -- which is where Rust | |
| 5874 | + // reads them from too. | |
| 5875 | + let mut bound: HashMap<String, Nim> = HashMap::new(); | |
| 5876 | + for (d, a) in &field_tys { | |
| 5877 | + Self::unify(d, a, &gp, &mut bound); | |
| 5878 | + } | |
| 5879 | + let args: Vec<Nim> = gp | |
| 5880 | + .iter() | |
| 5881 | + .map(|g| bound.get(g).cloned()) | |
| 5882 | + .collect::<Option<Vec<_>>>() | |
| 5883 | + .ok_or_else(|| { | |
| 5884 | + format!( | |
| 5885 | + "`{name} {{ .. }}` is generic, and neither the \ | |
| 5886 | + expected type nor the field values say what \ | |
| 5887 | + its parameters are; annotate the binding or \ | |
| 5888 | + the return type" | |
| 5889 | + ) | |
| 5890 | + })?; | |
| 5891 | + Nim::Named(name.clone(), args) | |
| 5873 | 5892 | } |
| 5874 | 5893 | } |
| 5875 | 5894 | }; |
| @@ -5841,6 +5841,7 @@ impl Lowerer { | |||
| 5841 | other => other.to_string(), | 5841 | other => other.to_string(), |
| 5842 | }; | 5842 | }; |
| 5843 | let mut parts = Vec::new(); | 5843 | let mut parts = Vec::new(); |
| 5844 | + let mut field_tys: Vec<(Nim, Nim)> = Vec::new(); | ||
| 5844 | for f in &s.fields { | 5845 | for f in &s.fields { |
| 5845 | let fname = match &f.member { | 5846 | let fname = match &f.member { |
| 5846 | syn::Member::Named(n) => n.to_string(), | 5847 | syn::Member::Named(n) => n.to_string(), |
| @@ -5852,6 +5853,9 @@ impl Lowerer { | |||
| 5852 | .and_then(|fs| fs.iter().find(|(n, _)| *n == fname)) | 5853 | .and_then(|fs| fs.iter().find(|(n, _)| *n == fname)) |
| 5853 | .map(|(_, t)| t.clone()); | 5854 | .map(|(_, t)| t.clone()); |
| 5854 | let v = self.expr_at(&f.expr, want.as_ref())?; | 5855 | let v = self.expr_at(&f.expr, want.as_ref())?; |
| 5856 | + if let (Some(d), Some(a)) = (&want, &v.ty) { | ||
| 5857 | + field_tys.push((d.clone(), a.clone())); | ||
| 5858 | + } | ||
| 5855 | parts.push(format!("{}: {}", ident(&fname), v.code)); | 5859 | parts.push(format!("{}: {}", ident(&fname), v.code)); |
| 5856 | } | 5860 | } |
| 5857 | // Nim cannot infer an object's generic parameters from a | 5861 | // Nim cannot infer an object's generic parameters from a |
| @@ -5865,11 +5869,26 @@ impl Lowerer { | |||
| 5865 | Nim::Named(name.clone(), a.clone()) | 5869 | Nim::Named(name.clone(), a.clone()) |
| 5866 | } | 5870 | } |
| 5867 | _ => { | 5871 | _ => { |
| 5868 | - return Err(format!( | 5872 | + // Failing an expectation, the field values say |
| 5869 | - "`{name} {{ .. }}` is generic, and Nim cannot infer \ | 5873 | + // what the parameters are -- which is where Rust |
| 5870 | - its parameters from the field values; annotate the \ | 5874 | + // reads them from too. |
| 5871 | - binding or the return type" | 5875 | + let mut bound: HashMap<String, Nim> = HashMap::new(); |
| 5872 | - )) | 5876 | + for (d, a) in &field_tys { |
| 5877 | + Self::unify(d, a, &gp, &mut bound); | ||
| 5878 | + } | ||
| 5879 | + let args: Vec<Nim> = gp | ||
| 5880 | + .iter() | ||
| 5881 | + .map(|g| bound.get(g).cloned()) | ||
| 5882 | + .collect::<Option<Vec<_>>>() | ||
| 5883 | + .ok_or_else(|| { | ||
| 5884 | + format!( | ||
| 5885 | + "`{name} {{ .. }}` is generic, and neither the \ | ||
| 5886 | + expected type nor the field values say what \ | ||
| 5887 | + its parameters are; annotate the binding or \ | ||
| 5888 | + the return type" | ||
| 5889 | + ) | ||
| 5890 | + })?; | ||
| 5891 | + Nim::Named(name.clone(), args) | ||
| 5873 | } | 5892 | } |
| 5874 | } | 5893 | } |
| 5875 | }; | 5894 | }; |
added
tests/cases/069-generic-struct-infer.rs +30 -0 | new file mode 100644 | ||
| @@ -0,0 +1,30 @@ | ||
| 1 | +// A generic struct literal with no annotation. Nim cannot infer an object's | |
| 2 | +// parameters from a constructor's field values, but the field values do say | |
| 3 | +// what they are — which is where Rust reads them from too. | |
| 4 | +struct Quad<T> { | |
| 5 | + r: T, | |
| 6 | + g: T, | |
| 7 | + b: T, | |
| 8 | +} | |
| 9 | + | |
| 10 | +struct Pair<A, B> { | |
| 11 | + left: A, | |
| 12 | + right: B, | |
| 13 | +} | |
| 14 | + | |
| 15 | +fn sum(q: Quad<u32>) -> u32 { | |
| 16 | + q.r + q.g + q.b | |
| 17 | +} | |
| 18 | + | |
| 19 | +fn main() { | |
| 20 | + let r = 1u32; | |
| 21 | + let g = 2u32; | |
| 22 | + let b = 3u32; | |
| 23 | + println!("{}", sum(Quad { r, g, b })); | |
| 24 | + | |
| 25 | + let p = Pair { left: 7u8, right: String::from("x") }; | |
| 26 | + println!("{} {}", p.left, p.right); | |
| 27 | + | |
| 28 | + let f = Quad { r: 1.5f64, g: 2.5, b: 3.0 }; | |
| 29 | + println!("{}", f.r + f.g + f.b); | |
| 30 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | +// A generic struct literal with no annotation. Nim cannot infer an object's | ||
| 2 | +// parameters from a constructor's field values, but the field values do say | ||
| 3 | +// what they are — which is where Rust reads them from too. | ||
| 4 | +struct Quad<T> { | ||
| 5 | + r: T, | ||
| 6 | + g: T, | ||
| 7 | + b: T, | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +struct Pair<A, B> { | ||
| 11 | + left: A, | ||
| 12 | + right: B, | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +fn sum(q: Quad<u32>) -> u32 { | ||
| 16 | + q.r + q.g + q.b | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +fn main() { | ||
| 20 | + let r = 1u32; | ||
| 21 | + let g = 2u32; | ||
| 22 | + let b = 3u32; | ||
| 23 | + println!("{}", sum(Quad { r, g, b })); | ||
| 24 | + | ||
| 25 | + let p = Pair { left: 7u8, right: String::from("x") }; | ||
| 26 | + println!("{} {}", p.left, p.right); | ||
| 27 | + | ||
| 28 | + let f = Quad { r: 1.5f64, g: 2.5, b: 3.0 }; | ||
| 29 | + println!("{}", f.r + f.g + f.b); | ||
| 30 | +} | ||