Dispatch a method call on a generic parameter through its trait bound
`alternatives.choice(i)` where `Alternatives: Alt<Input, Output, Error>` is
how every winnow combinator is written, and rustnim had no way to type it:
traits were collected for nothing but a default-body check, and nothing
recorded which bounds a generic parameter carried.
Both are recorded now, and the call resolves through them. The lowering needs
no dispatch mechanism of its own: trait methods with a receiver were already
named `rs{Trait}_{method}` and overloaded on the receiver type, so Nim picks
the right one at instantiation -- which is exactly where Rust picks it, at
monomorphisation. The bound is what lets rustnim *type* the call in between,
and the trait's declared signature gives the return type once the bound's
arguments are substituted in.
`opaque_of` also has to put the signature's type parameters in scope, or a
call on one of them is not recognised as a call on a generic at all. That was
enough to resolve `alt` and the combinators shaped like it.
`Sig` derives Clone; it was cloned by reference before, which the borrow
checker was right to object to.
tests/cases/060, with the bound written both on the parameter list and in a
`where` clause.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>f3e08c6 parent: 11fe65c modified
src/lower.rs +138 -2 | @@ -185,6 +185,7 @@ impl Val { | ||
| 185 | 185 | } |
| 186 | 186 | } |
| 187 | 187 | |
| 188 | +#[derive(Clone)] | |
| 188 | 189 | struct Sig { |
| 189 | 190 | params: Vec<Nim>, |
| 190 | 191 | ret: Nim, |
| @@ -347,6 +348,14 @@ pub struct Lowerer { | ||
| 347 | 348 | /// Const generic parameters, by name, with their Nim type. Nim's `static` |
| 348 | 349 | /// parameter is the same thing: a generic parameter that is a value. |
| 349 | 350 | const_gens: HashMap<String, String>, |
| 351 | + /// Methods a `trait` declares, by `(trait, method)`, with the trait's own | |
| 352 | + /// generic parameters. Used to type a call on a value whose type is a | |
| 353 | + /// generic parameter: `alternatives.choice(i)` where the parameter is | |
| 354 | + /// bound by `Alt<I, O, E>`. | |
| 355 | + trait_methods: HashMap<(String, String), Sig>, | |
| 356 | + /// The trait bounds on each generic parameter in scope, as `(trait, | |
| 357 | + /// arguments)`. | |
| 358 | + bounds: HashMap<String, Vec<(String, Vec<Nim>)>>, | |
| 350 | 359 | /// Combined table size when `CONST_NAMES` was last rebuilt. |
| 351 | 360 | const_names_at: std::cell::Cell<Option<usize>>, |
| 352 | 361 | /// Bodies of functions whose return type is a return-position `impl |
| @@ -437,6 +446,8 @@ impl Lowerer { | ||
| 437 | 446 | emitted_local: std::collections::HashSet::new(), |
| 438 | 447 | emitted_assoc: std::collections::HashSet::new(), |
| 439 | 448 | const_gens: HashMap::new(), |
| 449 | + trait_methods: HashMap::new(), | |
| 450 | + bounds: HashMap::new(), | |
| 440 | 451 | const_names_at: std::cell::Cell::new(None), |
| 441 | 452 | opaque_fns: Vec::new(), |
| 442 | 453 | opaque_ret: HashMap::new(), |
| @@ -855,11 +866,35 @@ impl Lowerer { | ||
| 855 | 866 | return Ok(()); |
| 856 | 867 | } |
| 857 | 868 | match item { |
| 869 | + Item::Trait(t) => { | |
| 870 | + // The declarations, so a call on a generic parameter bound by | |
| 871 | + // this trait can be typed. Nim resolves such a call at | |
| 872 | + // instantiation, the way Rust resolves it at monomorphisation, | |
| 873 | + // so what is needed here is the *signature*, not a body. | |
| 874 | + let tname = t.ident.to_string(); | |
| 875 | + let tgen = Self::generics_of(&t.generics); | |
| 876 | + let outer = std::mem::replace(&mut self.impl_generics, tgen.clone()); | |
| 877 | + for it in &t.items { | |
| 878 | + if let syn::TraitItem::Fn(f) = it { | |
| 879 | + if let Ok((mut params, ret)) = self.signature(&f.sig) { | |
| 880 | + if takes_self(&f.sig) { | |
| 881 | + params.insert(0, Nim::Named("Self".into(), Vec::new())); | |
| 882 | + } | |
| 883 | + self.trait_methods.insert( | |
| 884 | + (tname.clone(), f.sig.ident.to_string()), | |
| 885 | + Sig { params, ret, generics: tgen.clone() }, | |
| 886 | + ); | |
| 887 | + } | |
| 888 | + } | |
| 889 | + } | |
| 890 | + self.impl_generics = outer; | |
| 891 | + } | |
| 858 | 892 | Item::Fn(f) => { |
| 859 | 893 | // A body may declare its own types, and a signature below may |
| 860 | 894 | // name one, so these come first. |
| 861 | 895 | self.collect_body_items(&f.block)?; |
| 862 | 896 | self.note_const_generics(&f.sig.generics)?; |
| 897 | + self.note_bounds(&f.sig.generics); | |
| 863 | 898 | let (params, ret) = self.signature(&f.sig)?; |
| 864 | 899 | if matches!(ret, Nim::Future(_)) { |
| 865 | 900 | self.uses_async = true; |
| @@ -1033,6 +1068,7 @@ impl Lowerer { | ||
| 1033 | 1068 | // Before anything else: a method body may declare its own types, and |
| 1034 | 1069 | // the trait branches below return early. |
| 1035 | 1070 | self.note_const_generics(&im.generics)?; |
| 1071 | + self.note_bounds(&im.generics); | |
| 1036 | 1072 | // Associated consts are registered here for the same reason anything |
| 1037 | 1073 | // else is: an array length elsewhere may name one, and that name has |
| 1038 | 1074 | // to resolve before any body is lowered. `assoc_const` records them |
| @@ -1043,6 +1079,7 @@ impl Lowerer { | ||
| 1043 | 1079 | syn::ImplItem::Fn(m) => { |
| 1044 | 1080 | self.collect_body_items(&m.block)?; |
| 1045 | 1081 | self.note_const_generics(&m.sig.generics)?; |
| 1082 | + self.note_bounds(&m.sig.generics); | |
| 1046 | 1083 | } |
| 1047 | 1084 | syn::ImplItem::Const(c) => { |
| 1048 | 1085 | let t = self.map_ty(&c.ty)?.owned(); |
| @@ -1523,6 +1560,55 @@ impl Lowerer { | ||
| 1523 | 1560 | /// program mean something different — it only makes rustnim accept some |
| 1524 | 1561 | /// programs rustc would have rejected, which does not matter when the |
| 1525 | 1562 | /// input is known-good Rust. |
| 1563 | + /// Record the trait bounds on a signature's generic parameters, from both | |
| 1564 | + /// the parameter list and the `where` clause. | |
| 1565 | + /// | |
| 1566 | + /// These are what says that `alternatives.choice(i)` is legal: `choice` | |
| 1567 | + /// belongs to `Alt`, and the parameter is bound by it. Rust checks the | |
| 1568 | + /// bound and then monomorphises; Nim's generics resolve the same call at | |
| 1569 | + /// instantiation without a bound to check. The bound is what lets rustnim | |
| 1570 | + /// *type* the call in between. | |
| 1571 | + fn note_bounds(&mut self, g: &syn::Generics) { | |
| 1572 | + let mut add = |me: &mut Self, name: String, bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>| { | |
| 1573 | + for b in bounds { | |
| 1574 | + let syn::TypeParamBound::Trait(tb) = b else { continue }; | |
| 1575 | + let Some(seg) = tb.path.segments.last() else { continue }; | |
| 1576 | + let args: Vec<Nim> = match &seg.arguments { | |
| 1577 | + syn::PathArguments::AngleBracketed(a) => a | |
| 1578 | + .args | |
| 1579 | + .iter() | |
| 1580 | + .filter_map(|x| match x { | |
| 1581 | + GenericArgument::Type(t) => me.map_ty(t).ok(), | |
| 1582 | + _ => None, | |
| 1583 | + }) | |
| 1584 | + .collect(), | |
| 1585 | + _ => Vec::new(), | |
| 1586 | + }; | |
| 1587 | + me.bounds | |
| 1588 | + .entry(name.clone()) | |
| 1589 | + .or_default() | |
| 1590 | + .push((seg.ident.to_string(), args)); | |
| 1591 | + } | |
| 1592 | + }; | |
| 1593 | + for p in &g.params { | |
| 1594 | + if let syn::GenericParam::Type(t) = p { | |
| 1595 | + let n = t.ident.to_string(); | |
| 1596 | + add(self, n, &t.bounds); | |
| 1597 | + } | |
| 1598 | + } | |
| 1599 | + if let Some(w) = &g.where_clause { | |
| 1600 | + for pred in &w.predicates { | |
| 1601 | + let syn::WherePredicate::Type(pt) = pred else { continue }; | |
| 1602 | + let syn::Type::Path(tp) = &pt.bounded_ty else { continue }; | |
| 1603 | + if tp.path.segments.len() != 1 { | |
| 1604 | + continue; | |
| 1605 | + } | |
| 1606 | + let n = tp.path.segments[0].ident.to_string(); | |
| 1607 | + add(self, n, &pt.bounds); | |
| 1608 | + } | |
| 1609 | + } | |
| 1610 | + } | |
| 1611 | + | |
| 1526 | 1612 | fn generics_of(g: &syn::Generics) -> Vec<String> { |
| 1527 | 1613 | g.params |
| 1528 | 1614 | .iter() |
| @@ -2116,6 +2202,16 @@ impl Lowerer { | ||
| 2116 | 2202 | } |
| 2117 | 2203 | let outer_self = self.self_ty.clone(); |
| 2118 | 2204 | self.self_ty = self_ty.cloned().or(outer_self.clone()); |
| 2205 | + // The signature's type parameters have to be in scope, or a method | |
| 2206 | + // call on one of them is not recognised as a call on a generic. | |
| 2207 | + let mut gp = self.impl_generics.clone(); | |
| 2208 | + for g in Self::generics_of(&sig.generics) { | |
| 2209 | + if !gp.contains(&g) { | |
| 2210 | + gp.push(g); | |
| 2211 | + } | |
| 2212 | + } | |
| 2213 | + gp.extend(self.impl_trait_params(sig)); | |
| 2214 | + let outer_fg = std::mem::replace(&mut self.fn_generics, gp); | |
| 2119 | 2215 | let mut i = 0usize; |
| 2120 | 2216 | for a in &sig.inputs { |
| 2121 | 2217 | if let FnArg::Typed(t) = a { |
| @@ -2140,6 +2236,7 @@ impl Lowerer { | ||
| 2140 | 2236 | self.hoisted.truncate(saved_hoisted); |
| 2141 | 2237 | self.pop_scope(); |
| 2142 | 2238 | self.self_ty = outer_self; |
| 2239 | + self.fn_generics = outer_fg; | |
| 2143 | 2240 | found |
| 2144 | 2241 | } |
| 2145 | 2242 | |
| @@ -6977,10 +7074,49 @@ impl Lowerer { | ||
| 6977 | 7074 | // Nim at instantiation, so the call just needs the right proc |
| 6978 | 7075 | // name. Every impl of a trait method shares one, so any |
| 6979 | 7076 | // candidate with this method name gives it. |
| 6980 | - let generic_recv = rt | |
| 7077 | + let recv_param = rt | |
| 6981 | 7078 | .as_ref() |
| 6982 | 7079 | .map(type_name) |
| 6983 | - .is_some_and(|n| self.fn_generics.iter().any(|g| *g == n)); | |
| 7080 | + .filter(|n| self.fn_generics.iter().any(|g| g == n)); | |
| 7081 | + | |
| 7082 | + // The receiver's type is a generic parameter and one of its | |
| 7083 | + // trait bounds declares this method. That bound is what makes | |
| 7084 | + // the call legal in Rust, and it names the proc: every impl of | |
| 7085 | + // a trait method shares one name and overloads on the | |
| 7086 | + // receiver, so Nim picks the right one at instantiation -- | |
| 7087 | + // which is where Rust picks it too. | |
| 7088 | + if let Some(param) = &recv_param { | |
| 7089 | + let bs = self.bounds.get(param).cloned().unwrap_or_default(); | |
| 7090 | + let mut found = None; | |
| 7091 | + for (tr, targs) in &bs { | |
| 7092 | + if let Some(sig) = self.trait_methods.get(&(tr.clone(), name.clone())) { | |
| 7093 | + found = Some((tr.clone(), targs.clone(), sig.clone())); | |
| 7094 | + break; | |
| 7095 | + } | |
| 7096 | + } | |
| 7097 | + if let Some((tr, targs, sig)) = found { | |
| 7098 | + let mut sm: HashMap<String, Nim> = HashMap::new(); | |
| 7099 | + for (p, a) in sig.generics.iter().zip(targs.iter()) { | |
| 7100 | + sm.insert(p.clone(), a.clone()); | |
| 7101 | + } | |
| 7102 | + let mut codes = vec![recv.code.clone()]; | |
| 7103 | + for (i, a) in m.args.iter().enumerate() { | |
| 7104 | + let want = sig | |
| 7105 | + .params | |
| 7106 | + .get(i + 1) | |
| 7107 | + .map(|t| Self::subst(t, &sm)) | |
| 7108 | + .filter(|t| !Self::mentions_any(t, &sig.generics)); | |
| 7109 | + codes.push(self.expr_at(a, want.as_ref())?.code); | |
| 7110 | + } | |
| 7111 | + let ret = Self::subst(&sig.ret, &sm); | |
| 7112 | + return Ok(Val::new( | |
| 7113 | + format!("{}({})", trait_method_name(param, &tr, &name, true), codes.join(", ")), | |
| 7114 | + Some(ret), | |
| 7115 | + )); | |
| 7116 | + } | |
| 7117 | + } | |
| 7118 | + | |
| 7119 | + let generic_recv = recv_param.is_some(); | |
| 6984 | 7120 | let key = if generic_recv { |
| 6985 | 7121 | let mut names: Vec<String> = self |
| 6986 | 7122 | .statics |
| @@ -185,6 +185,7 @@ impl Val { | |||
| 185 | } | 185 | } |
| 186 | } | 186 | } |
| 187 | 187 | ||
| 188 | +#[derive(Clone)] | ||
| 188 | struct Sig { | 189 | struct Sig { |
| 189 | params: Vec<Nim>, | 190 | params: Vec<Nim>, |
| 190 | ret: Nim, | 191 | ret: Nim, |
| @@ -347,6 +348,14 @@ pub struct Lowerer { | |||
| 347 | /// Const generic parameters, by name, with their Nim type. Nim's `static` | 348 | /// Const generic parameters, by name, with their Nim type. Nim's `static` |
| 348 | /// parameter is the same thing: a generic parameter that is a value. | 349 | /// parameter is the same thing: a generic parameter that is a value. |
| 349 | const_gens: HashMap<String, String>, | 350 | const_gens: HashMap<String, String>, |
| 351 | + /// Methods a `trait` declares, by `(trait, method)`, with the trait's own | ||
| 352 | + /// generic parameters. Used to type a call on a value whose type is a | ||
| 353 | + /// generic parameter: `alternatives.choice(i)` where the parameter is | ||
| 354 | + /// bound by `Alt<I, O, E>`. | ||
| 355 | + trait_methods: HashMap<(String, String), Sig>, | ||
| 356 | + /// The trait bounds on each generic parameter in scope, as `(trait, | ||
| 357 | + /// arguments)`. | ||
| 358 | + bounds: HashMap<String, Vec<(String, Vec<Nim>)>>, | ||
| 350 | /// Combined table size when `CONST_NAMES` was last rebuilt. | 359 | /// Combined table size when `CONST_NAMES` was last rebuilt. |
| 351 | const_names_at: std::cell::Cell<Option<usize>>, | 360 | const_names_at: std::cell::Cell<Option<usize>>, |
| 352 | /// Bodies of functions whose return type is a return-position `impl | 361 | /// Bodies of functions whose return type is a return-position `impl |
| @@ -437,6 +446,8 @@ impl Lowerer { | |||
| 437 | emitted_local: std::collections::HashSet::new(), | 446 | emitted_local: std::collections::HashSet::new(), |
| 438 | emitted_assoc: std::collections::HashSet::new(), | 447 | emitted_assoc: std::collections::HashSet::new(), |
| 439 | const_gens: HashMap::new(), | 448 | const_gens: HashMap::new(), |
| 449 | + trait_methods: HashMap::new(), | ||
| 450 | + bounds: HashMap::new(), | ||
| 440 | const_names_at: std::cell::Cell::new(None), | 451 | const_names_at: std::cell::Cell::new(None), |
| 441 | opaque_fns: Vec::new(), | 452 | opaque_fns: Vec::new(), |
| 442 | opaque_ret: HashMap::new(), | 453 | opaque_ret: HashMap::new(), |
| @@ -855,11 +866,35 @@ impl Lowerer { | |||
| 855 | return Ok(()); | 866 | return Ok(()); |
| 856 | } | 867 | } |
| 857 | match item { | 868 | match item { |
| 869 | + Item::Trait(t) => { | ||
| 870 | + // The declarations, so a call on a generic parameter bound by | ||
| 871 | + // this trait can be typed. Nim resolves such a call at | ||
| 872 | + // instantiation, the way Rust resolves it at monomorphisation, | ||
| 873 | + // so what is needed here is the *signature*, not a body. | ||
| 874 | + let tname = t.ident.to_string(); | ||
| 875 | + let tgen = Self::generics_of(&t.generics); | ||
| 876 | + let outer = std::mem::replace(&mut self.impl_generics, tgen.clone()); | ||
| 877 | + for it in &t.items { | ||
| 878 | + if let syn::TraitItem::Fn(f) = it { | ||
| 879 | + if let Ok((mut params, ret)) = self.signature(&f.sig) { | ||
| 880 | + if takes_self(&f.sig) { | ||
| 881 | + params.insert(0, Nim::Named("Self".into(), Vec::new())); | ||
| 882 | + } | ||
| 883 | + self.trait_methods.insert( | ||
| 884 | + (tname.clone(), f.sig.ident.to_string()), | ||
| 885 | + Sig { params, ret, generics: tgen.clone() }, | ||
| 886 | + ); | ||
| 887 | + } | ||
| 888 | + } | ||
| 889 | + } | ||
| 890 | + self.impl_generics = outer; | ||
| 891 | + } | ||
| 858 | Item::Fn(f) => { | 892 | Item::Fn(f) => { |
| 859 | // A body may declare its own types, and a signature below may | 893 | // A body may declare its own types, and a signature below may |
| 860 | // name one, so these come first. | 894 | // name one, so these come first. |
| 861 | self.collect_body_items(&f.block)?; | 895 | self.collect_body_items(&f.block)?; |
| 862 | self.note_const_generics(&f.sig.generics)?; | 896 | self.note_const_generics(&f.sig.generics)?; |
| 897 | + self.note_bounds(&f.sig.generics); | ||
| 863 | let (params, ret) = self.signature(&f.sig)?; | 898 | let (params, ret) = self.signature(&f.sig)?; |
| 864 | if matches!(ret, Nim::Future(_)) { | 899 | if matches!(ret, Nim::Future(_)) { |
| 865 | self.uses_async = true; | 900 | self.uses_async = true; |
| @@ -1033,6 +1068,7 @@ impl Lowerer { | |||
| 1033 | // Before anything else: a method body may declare its own types, and | 1068 | // Before anything else: a method body may declare its own types, and |
| 1034 | // the trait branches below return early. | 1069 | // the trait branches below return early. |
| 1035 | self.note_const_generics(&im.generics)?; | 1070 | self.note_const_generics(&im.generics)?; |
| 1071 | + self.note_bounds(&im.generics); | ||
| 1036 | // Associated consts are registered here for the same reason anything | 1072 | // Associated consts are registered here for the same reason anything |
| 1037 | // else is: an array length elsewhere may name one, and that name has | 1073 | // else is: an array length elsewhere may name one, and that name has |
| 1038 | // to resolve before any body is lowered. `assoc_const` records them | 1074 | // to resolve before any body is lowered. `assoc_const` records them |
| @@ -1043,6 +1079,7 @@ impl Lowerer { | |||
| 1043 | syn::ImplItem::Fn(m) => { | 1079 | syn::ImplItem::Fn(m) => { |
| 1044 | self.collect_body_items(&m.block)?; | 1080 | self.collect_body_items(&m.block)?; |
| 1045 | self.note_const_generics(&m.sig.generics)?; | 1081 | self.note_const_generics(&m.sig.generics)?; |
| 1082 | + self.note_bounds(&m.sig.generics); | ||
| 1046 | } | 1083 | } |
| 1047 | syn::ImplItem::Const(c) => { | 1084 | syn::ImplItem::Const(c) => { |
| 1048 | let t = self.map_ty(&c.ty)?.owned(); | 1085 | let t = self.map_ty(&c.ty)?.owned(); |
| @@ -1523,6 +1560,55 @@ impl Lowerer { | |||
| 1523 | /// program mean something different — it only makes rustnim accept some | 1560 | /// program mean something different — it only makes rustnim accept some |
| 1524 | /// programs rustc would have rejected, which does not matter when the | 1561 | /// programs rustc would have rejected, which does not matter when the |
| 1525 | /// input is known-good Rust. | 1562 | /// input is known-good Rust. |
| 1563 | + /// Record the trait bounds on a signature's generic parameters, from both | ||
| 1564 | + /// the parameter list and the `where` clause. | ||
| 1565 | + /// | ||
| 1566 | + /// These are what says that `alternatives.choice(i)` is legal: `choice` | ||
| 1567 | + /// belongs to `Alt`, and the parameter is bound by it. Rust checks the | ||
| 1568 | + /// bound and then monomorphises; Nim's generics resolve the same call at | ||
| 1569 | + /// instantiation without a bound to check. The bound is what lets rustnim | ||
| 1570 | + /// *type* the call in between. | ||
| 1571 | + fn note_bounds(&mut self, g: &syn::Generics) { | ||
| 1572 | + let mut add = |me: &mut Self, name: String, bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>| { | ||
| 1573 | + for b in bounds { | ||
| 1574 | + let syn::TypeParamBound::Trait(tb) = b else { continue }; | ||
| 1575 | + let Some(seg) = tb.path.segments.last() else { continue }; | ||
| 1576 | + let args: Vec<Nim> = match &seg.arguments { | ||
| 1577 | + syn::PathArguments::AngleBracketed(a) => a | ||
| 1578 | + .args | ||
| 1579 | + .iter() | ||
| 1580 | + .filter_map(|x| match x { | ||
| 1581 | + GenericArgument::Type(t) => me.map_ty(t).ok(), | ||
| 1582 | + _ => None, | ||
| 1583 | + }) | ||
| 1584 | + .collect(), | ||
| 1585 | + _ => Vec::new(), | ||
| 1586 | + }; | ||
| 1587 | + me.bounds | ||
| 1588 | + .entry(name.clone()) | ||
| 1589 | + .or_default() | ||
| 1590 | + .push((seg.ident.to_string(), args)); | ||
| 1591 | + } | ||
| 1592 | + }; | ||
| 1593 | + for p in &g.params { | ||
| 1594 | + if let syn::GenericParam::Type(t) = p { | ||
| 1595 | + let n = t.ident.to_string(); | ||
| 1596 | + add(self, n, &t.bounds); | ||
| 1597 | + } | ||
| 1598 | + } | ||
| 1599 | + if let Some(w) = &g.where_clause { | ||
| 1600 | + for pred in &w.predicates { | ||
| 1601 | + let syn::WherePredicate::Type(pt) = pred else { continue }; | ||
| 1602 | + let syn::Type::Path(tp) = &pt.bounded_ty else { continue }; | ||
| 1603 | + if tp.path.segments.len() != 1 { | ||
| 1604 | + continue; | ||
| 1605 | + } | ||
| 1606 | + let n = tp.path.segments[0].ident.to_string(); | ||
| 1607 | + add(self, n, &pt.bounds); | ||
| 1608 | + } | ||
| 1609 | + } | ||
| 1610 | + } | ||
| 1611 | + | ||
| 1526 | fn generics_of(g: &syn::Generics) -> Vec<String> { | 1612 | fn generics_of(g: &syn::Generics) -> Vec<String> { |
| 1527 | g.params | 1613 | g.params |
| 1528 | .iter() | 1614 | .iter() |
| @@ -2116,6 +2202,16 @@ impl Lowerer { | |||
| 2116 | } | 2202 | } |
| 2117 | let outer_self = self.self_ty.clone(); | 2203 | let outer_self = self.self_ty.clone(); |
| 2118 | self.self_ty = self_ty.cloned().or(outer_self.clone()); | 2204 | self.self_ty = self_ty.cloned().or(outer_self.clone()); |
| 2205 | + // The signature's type parameters have to be in scope, or a method | ||
| 2206 | + // call on one of them is not recognised as a call on a generic. | ||
| 2207 | + let mut gp = self.impl_generics.clone(); | ||
| 2208 | + for g in Self::generics_of(&sig.generics) { | ||
| 2209 | + if !gp.contains(&g) { | ||
| 2210 | + gp.push(g); | ||
| 2211 | + } | ||
| 2212 | + } | ||
| 2213 | + gp.extend(self.impl_trait_params(sig)); | ||
| 2214 | + let outer_fg = std::mem::replace(&mut self.fn_generics, gp); | ||
| 2119 | let mut i = 0usize; | 2215 | let mut i = 0usize; |
| 2120 | for a in &sig.inputs { | 2216 | for a in &sig.inputs { |
| 2121 | if let FnArg::Typed(t) = a { | 2217 | if let FnArg::Typed(t) = a { |
| @@ -2140,6 +2236,7 @@ impl Lowerer { | |||
| 2140 | self.hoisted.truncate(saved_hoisted); | 2236 | self.hoisted.truncate(saved_hoisted); |
| 2141 | self.pop_scope(); | 2237 | self.pop_scope(); |
| 2142 | self.self_ty = outer_self; | 2238 | self.self_ty = outer_self; |
| 2239 | + self.fn_generics = outer_fg; | ||
| 2143 | found | 2240 | found |
| 2144 | } | 2241 | } |
| 2145 | 2242 | ||
| @@ -6977,10 +7074,49 @@ impl Lowerer { | |||
| 6977 | // Nim at instantiation, so the call just needs the right proc | 7074 | // Nim at instantiation, so the call just needs the right proc |
| 6978 | // name. Every impl of a trait method shares one, so any | 7075 | // name. Every impl of a trait method shares one, so any |
| 6979 | // candidate with this method name gives it. | 7076 | // candidate with this method name gives it. |
| 6980 | - let generic_recv = rt | 7077 | + let recv_param = rt |
| 6981 | .as_ref() | 7078 | .as_ref() |
| 6982 | .map(type_name) | 7079 | .map(type_name) |
| 6983 | - .is_some_and(|n| self.fn_generics.iter().any(|g| *g == n)); | 7080 | + .filter(|n| self.fn_generics.iter().any(|g| g == n)); |
| 7081 | + | ||
| 7082 | + // The receiver's type is a generic parameter and one of its | ||
| 7083 | + // trait bounds declares this method. That bound is what makes | ||
| 7084 | + // the call legal in Rust, and it names the proc: every impl of | ||
| 7085 | + // a trait method shares one name and overloads on the | ||
| 7086 | + // receiver, so Nim picks the right one at instantiation -- | ||
| 7087 | + // which is where Rust picks it too. | ||
| 7088 | + if let Some(param) = &recv_param { | ||
| 7089 | + let bs = self.bounds.get(param).cloned().unwrap_or_default(); | ||
| 7090 | + let mut found = None; | ||
| 7091 | + for (tr, targs) in &bs { | ||
| 7092 | + if let Some(sig) = self.trait_methods.get(&(tr.clone(), name.clone())) { | ||
| 7093 | + found = Some((tr.clone(), targs.clone(), sig.clone())); | ||
| 7094 | + break; | ||
| 7095 | + } | ||
| 7096 | + } | ||
| 7097 | + if let Some((tr, targs, sig)) = found { | ||
| 7098 | + let mut sm: HashMap<String, Nim> = HashMap::new(); | ||
| 7099 | + for (p, a) in sig.generics.iter().zip(targs.iter()) { | ||
| 7100 | + sm.insert(p.clone(), a.clone()); | ||
| 7101 | + } | ||
| 7102 | + let mut codes = vec![recv.code.clone()]; | ||
| 7103 | + for (i, a) in m.args.iter().enumerate() { | ||
| 7104 | + let want = sig | ||
| 7105 | + .params | ||
| 7106 | + .get(i + 1) | ||
| 7107 | + .map(|t| Self::subst(t, &sm)) | ||
| 7108 | + .filter(|t| !Self::mentions_any(t, &sig.generics)); | ||
| 7109 | + codes.push(self.expr_at(a, want.as_ref())?.code); | ||
| 7110 | + } | ||
| 7111 | + let ret = Self::subst(&sig.ret, &sm); | ||
| 7112 | + return Ok(Val::new( | ||
| 7113 | + format!("{}({})", trait_method_name(param, &tr, &name, true), codes.join(", ")), | ||
| 7114 | + Some(ret), | ||
| 7115 | + )); | ||
| 7116 | + } | ||
| 7117 | + } | ||
| 7118 | + | ||
| 7119 | + let generic_recv = recv_param.is_some(); | ||
| 6984 | let key = if generic_recv { | 7120 | let key = if generic_recv { |
| 6985 | let mut names: Vec<String> = self | 7121 | let mut names: Vec<String> = self |
| 6986 | .statics | 7122 | .statics |
added
tests/cases/060-generic-trait-dispatch.rs +56 -0 | new file mode 100644 | ||
| @@ -0,0 +1,56 @@ | ||
| 1 | +// A method called on a value whose type is a generic parameter. The trait | |
| 2 | +// bound is what makes the call legal in Rust, and it is what names the proc | |
| 3 | +// here: every impl of a trait method shares one name and overloads on the | |
| 4 | +// receiver, so Nim resolves the call at instantiation — which is where Rust | |
| 5 | +// resolves it too. | |
| 6 | +trait Shape { | |
| 7 | + fn area(&self) -> u32; | |
| 8 | + fn scaled(&self, by: u32) -> u32; | |
| 9 | +} | |
| 10 | + | |
| 11 | +struct Square { | |
| 12 | + side: u32, | |
| 13 | +} | |
| 14 | + | |
| 15 | +struct Rect { | |
| 16 | + w: u32, | |
| 17 | + h: u32, | |
| 18 | +} | |
| 19 | + | |
| 20 | +impl Shape for Square { | |
| 21 | + fn area(&self) -> u32 { | |
| 22 | + self.side * self.side | |
| 23 | + } | |
| 24 | + fn scaled(&self, by: u32) -> u32 { | |
| 25 | + self.area() * by | |
| 26 | + } | |
| 27 | +} | |
| 28 | + | |
| 29 | +impl Shape for Rect { | |
| 30 | + fn area(&self) -> u32 { | |
| 31 | + self.w * self.h | |
| 32 | + } | |
| 33 | + fn scaled(&self, by: u32) -> u32 { | |
| 34 | + self.area() * by | |
| 35 | + } | |
| 36 | +} | |
| 37 | + | |
| 38 | +// The bound is on the parameter list. | |
| 39 | +fn describe<S: Shape>(s: &S) -> u32 { | |
| 40 | + s.area() | |
| 41 | +} | |
| 42 | + | |
| 43 | +// The bound is in a `where` clause, which is the same thing said elsewhere. | |
| 44 | +fn twice<S>(s: &S) -> u32 | |
| 45 | +where | |
| 46 | + S: Shape, | |
| 47 | +{ | |
| 48 | + s.scaled(2) | |
| 49 | +} | |
| 50 | + | |
| 51 | +fn main() { | |
| 52 | + let sq = Square { side: 4 }; | |
| 53 | + let r = Rect { w: 3, h: 5 }; | |
| 54 | + println!("{} {}", describe(&sq), describe(&r)); | |
| 55 | + println!("{} {}", twice(&sq), twice(&r)); | |
| 56 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | +// A method called on a value whose type is a generic parameter. The trait | ||
| 2 | +// bound is what makes the call legal in Rust, and it is what names the proc | ||
| 3 | +// here: every impl of a trait method shares one name and overloads on the | ||
| 4 | +// receiver, so Nim resolves the call at instantiation — which is where Rust | ||
| 5 | +// resolves it too. | ||
| 6 | +trait Shape { | ||
| 7 | + fn area(&self) -> u32; | ||
| 8 | + fn scaled(&self, by: u32) -> u32; | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +struct Square { | ||
| 12 | + side: u32, | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +struct Rect { | ||
| 16 | + w: u32, | ||
| 17 | + h: u32, | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +impl Shape for Square { | ||
| 21 | + fn area(&self) -> u32 { | ||
| 22 | + self.side * self.side | ||
| 23 | + } | ||
| 24 | + fn scaled(&self, by: u32) -> u32 { | ||
| 25 | + self.area() * by | ||
| 26 | + } | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +impl Shape for Rect { | ||
| 30 | + fn area(&self) -> u32 { | ||
| 31 | + self.w * self.h | ||
| 32 | + } | ||
| 33 | + fn scaled(&self, by: u32) -> u32 { | ||
| 34 | + self.area() * by | ||
| 35 | + } | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +// The bound is on the parameter list. | ||
| 39 | +fn describe<S: Shape>(s: &S) -> u32 { | ||
| 40 | + s.area() | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +// The bound is in a `where` clause, which is the same thing said elsewhere. | ||
| 44 | +fn twice<S>(s: &S) -> u32 | ||
| 45 | +where | ||
| 46 | + S: Shape, | ||
| 47 | +{ | ||
| 48 | + s.scaled(2) | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +fn main() { | ||
| 52 | + let sq = Square { side: 4 }; | ||
| 53 | + let r = Rect { w: 3, h: 5 }; | ||
| 54 | + println!("{} {}", describe(&sq), describe(&r)); | ||
| 55 | + println!("{} {}", twice(&sq), twice(&r)); | ||
| 56 | +} | ||