nandi/rustnimpublic Fork
⑂ 1687aba
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Type a closure passed to a generic parameter from the trait's blanket impl

`trace("alt", move |i| ..)` passes a closure where `impl Parser<I, O, E>` is
wanted, and a type variable says nothing about what a closure there looks
like -- so `?` and a value-position `match` inside it had no return type to
work against. Fourteen of winnow's thirty-two failures were that, counted
twice under two names.

What says it is the trait's blanket impl over a callable:

    impl<I,O,E,F> Parser<I,O,E> for F where F: FnMut(&mut I) -> Result<O,E>

That is what makes the closure a `Parser` in the first place, and it gives the
signature exactly. Recorded per trait, and applied where an argument's
parameter type is a type variable whose bound has one. Rust reads it the same
way.

The bound on an argument-position `impl Trait` now travels with the parameter
it becomes, since that is where the bound lives for these.

The `?` cases now fail deeper and more honestly: `?` needs `From<Error> for E`
and will not assume the conversion is the identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T20:25:30-07:00 Browse files
1687aba parent: 283afd5
modified src/lower.rs +135 -2
@@ -356,6 +356,16 @@ pub struct Lowerer {
356356 /// generic parameter: `alternatives.choice(i)` where the parameter is
357357 /// bound by `Alt<I, O, E>`.
358358 trait_methods: HashMap<(String, String), Sig>,
359+ /// Traits with a blanket impl over a callable: `impl<I,O,E,F> Parser<I,O,E>
360+ /// for F where F: FnMut(&mut I) -> Result<O,E>`. That impl is what makes a
361+ /// closure usable where the trait is expected, and it says what signature
362+ /// the closure must have -- which is the only thing that types a closure
363+ /// passed to a generic parameter.
364+ fn_blankets: HashMap<String, (Vec<String>, Vec<Nim>, Nim)>,
365+ /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit`
366+ /// runs from `&self`, so they are parked here and folded into `bounds` by
367+ /// whoever called it.
368+ pending_apit_bounds: std::cell::RefCell<Vec<(String, syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>)>>,
359369 /// The trait bounds on each generic parameter in scope, as `(trait,
360370 /// arguments)`.
361371 bounds: HashMap<String, Vec<(String, Vec<Nim>)>>,
@@ -451,6 +461,8 @@ impl Lowerer {
451461 const_gens: HashMap::new(),
452462 opaque_why: None,
453463 trait_methods: HashMap::new(),
464+ fn_blankets: HashMap::new(),
465+ pending_apit_bounds: std::cell::RefCell::new(Vec::new()),
454466 bounds: HashMap::new(),
455467 const_names_at: std::cell::Cell::new(None),
456468 opaque_fns: Vec::new(),
@@ -1073,6 +1085,7 @@ impl Lowerer {
10731085 // the trait branches below return early.
10741086 self.note_const_generics(&im.generics)?;
10751087 self.note_bounds(&im.generics);
1088+ self.note_fn_blanket(im);
10761089 // Associated consts are registered here for the same reason anything
10771090 // else is: an array length elsewhere may name one, and that name has
10781091 // to resolve before any body is lowered. `assoc_const` records them
@@ -1564,6 +1577,99 @@ impl Lowerer {
15641577 /// program mean something different — it only makes rustnim accept some
15651578 /// programs rustc would have rejected, which does not matter when the
15661579 /// input is known-good Rust.
1580+ /// The proc type a value of this type variable must have, if one of its
1581+ /// bounds is a trait implemented blanket-wise for a callable.
1582+ fn callable_bound(&self, t: &Nim) -> Option<Nim> {
1583+ let Nim::Named(name, _) = t else { return None };
1584+ let bs = self.bounds.get(name)?;
1585+ for (tr, targs) in bs {
1586+ let Some((tparams, args, ret)) = self.fn_blankets.get(tr) else { continue };
1587+ // The trait's parameters, bound to what this use site supplies.
1588+ let mut m: HashMap<String, Nim> = HashMap::new();
1589+ for (p, a) in tparams.iter().zip(targs.iter()) {
1590+ m.insert(p.clone(), a.clone());
1591+ }
1592+ let args: Vec<Nim> = args.iter().map(|a| Self::subst(a, &m)).collect();
1593+ let ret = Self::subst(ret, &m);
1594+ return Some(Nim::Proc(args, Box::new(ret)));
1595+ }
1596+ None
1597+ }
1598+
1599+ /// Record a trait's blanket impl over a callable, if it has one.
1600+ ///
1601+ /// `impl<I,O,E,F> Parser<I,O,E> for F where F: FnMut(&mut I) -> Result<O,E>`
1602+ /// is what makes every winnow combinator's closure a `Parser`, and it is
1603+ /// the only thing that says what signature such a closure has. Rust reads
1604+ /// it the same way.
1605+ fn note_fn_blanket(&mut self, im: &syn::ItemImpl) {
1606+ let Some((path, _)) = &im.trait_ else { return };
1607+ let Some(seg) = path.segments.last() else { return };
1608+ let tname = seg.ident.to_string();
1609+ // The impl is *for* a bare generic parameter of the impl itself.
1610+ let syn::Type::Path(sp) = &*im.self_ty else { return };
1611+ if sp.path.segments.len() != 1 {
1612+ return;
1613+ }
1614+ let me = sp.path.segments[0].ident.to_string();
1615+ if !Self::generics_of(&im.generics).iter().any(|g| *g == me) {
1616+ return;
1617+ }
1618+ // The trait's own arguments, in order, are what a use site supplies.
1619+ let targs: Vec<String> = match &seg.arguments {
1620+ syn::PathArguments::AngleBracketed(a) => a
1621+ .args
1622+ .iter()
1623+ .filter_map(|g| match g {
1624+ GenericArgument::Type(syn::Type::Path(p)) if p.path.segments.len() == 1 => {
1625+ Some(p.path.segments[0].ident.to_string())
1626+ }
1627+ _ => None,
1628+ })
1629+ .collect(),
1630+ _ => return,
1631+ };
1632+ // And the `Fn` bound on that parameter gives the signature.
1633+ let mut bounds: Vec<&syn::TypeParamBound> = Vec::new();
1634+ for p in &im.generics.params {
1635+ if let syn::GenericParam::Type(t) = p {
1636+ if t.ident == me {
1637+ bounds.extend(t.bounds.iter());
1638+ }
1639+ }
1640+ }
1641+ if let Some(w) = &im.generics.where_clause {
1642+ for pred in &w.predicates {
1643+ let syn::WherePredicate::Type(pt) = pred else { continue };
1644+ let syn::Type::Path(bp) = &pt.bounded_ty else { continue };
1645+ if bp.path.segments.len() == 1 && bp.path.segments[0].ident == me {
1646+ bounds.extend(pt.bounds.iter());
1647+ }
1648+ }
1649+ }
1650+ for b in bounds {
1651+ let syn::TypeParamBound::Trait(tb) = b else { continue };
1652+ let Some(fs) = tb.path.segments.last() else { continue };
1653+ if !matches!(fs.ident.to_string().as_str(), "Fn" | "FnMut" | "FnOnce") {
1654+ continue;
1655+ }
1656+ let syn::PathArguments::Parenthesized(pa) = &fs.arguments else { continue };
1657+ let Ok(args) = pa.inputs.iter().map(|a| self.map_ty(&a.ty)).collect::<Result<Vec<_>, _>>()
1658+ else {
1659+ continue;
1660+ };
1661+ let ret = match &pa.output {
1662+ ReturnType::Default => Nim::Unit,
1663+ ReturnType::Type(_, t) => match self.map_ty(t) {
1664+ Ok(t) => t,
1665+ Err(_) => continue,
1666+ },
1667+ };
1668+ self.fn_blankets.insert(tname, (targs, args, ret));
1669+ return;
1670+ }
1671+ }
1672+
15671673 /// Record the trait bounds on a signature's generic parameters, from both
15681674 /// the parameter list and the `where` clause.
15691675 ///
@@ -1573,6 +1679,19 @@ impl Lowerer {
15731679 /// instantiation without a bound to check. The bound is what lets rustnim
15741680 /// *type* the call in between.
15751681 fn note_bounds(&mut self, g: &syn::Generics) {
1682+ let pending = std::mem::take(&mut *self.pending_apit_bounds.borrow_mut());
1683+ for (name, bounds) in pending {
1684+ let mut tmp = syn::Generics::default();
1685+ tmp.params.push(syn::GenericParam::Type(syn::TypeParam {
1686+ attrs: Vec::new(),
1687+ ident: proc_macro2::Ident::new(&name, proc_macro2::Span::call_site()),
1688+ colon_token: None,
1689+ bounds,
1690+ default: None,
1691+ }));
1692+ self.note_bounds(&tmp);
1693+ }
1694+
15761695 let mut add = |me: &mut Self, name: String, bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>| {
15771696 for b in bounds {
15781697 let syn::TypeParamBound::Trait(tb) = b else { continue };
@@ -1963,10 +2082,16 @@ impl Lowerer {
19632082 /// they invent for a given position always carries the same name.
19642083 fn rewrite_apit(&self, ty: &syn::Type, next: &mut usize) -> syn::Type {
19652084 use syn::Type;
1966- if let Type::ImplTrait(_) = ty {
2085+ if let Type::ImplTrait(it) = ty {
19672086 if self.map_ty(ty).is_err() {
19682087 *next += 1;
19692088 let name = format!("ImplT{next}");
2089+ // The bound travels with the parameter it becomes: it is what
2090+ // says a closure passed here has to be callable, and with what
2091+ // signature. See `fn_blankets`.
2092+ self.pending_apit_bounds
2093+ .borrow_mut()
2094+ .push((name.clone(), it.bounds.clone()));
19702095 return syn::parse_str::<Type>(&name).expect("generated ident parses");
19712096 }
19722097 return ty.clone();
@@ -6268,7 +6393,15 @@ impl Lowerer {
62686393 let ptys: Vec<Nim> = callee.map(|s| s.params.clone()).unwrap_or_default();
62696394 let mut args = Vec::new();
62706395 for (i, a) in c.args.iter().enumerate() {
6271- let want = ptys.get(i).cloned().filter(|t| !Self::mentions_any(t, &cgen));
6396+ let raw = ptys.get(i).cloned();
6397+ // A parameter that is a type variable says nothing by itself --
6398+ // except when its bound is a trait with a blanket impl over a
6399+ // callable, which says exactly what a closure passed here must
6400+ // look like. That is how Rust types these too.
6401+ let want = match raw {
6402+ Some(t) if Self::mentions_any(&t, &cgen) => self.callable_bound(&t),
6403+ other => other,
6404+ };
62726405 args.push(self.expr_at(a, want.as_ref())?);
62736406 }
62746407 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
@@ -356,6 +356,16 @@ pub struct Lowerer {
356 /// generic parameter: `alternatives.choice(i)` where the parameter is356 /// generic parameter: `alternatives.choice(i)` where the parameter is
357 /// bound by `Alt<I, O, E>`.357 /// bound by `Alt<I, O, E>`.
358 trait_methods: HashMap<(String, String), Sig>,358 trait_methods: HashMap<(String, String), Sig>,
359+ /// Traits with a blanket impl over a callable: `impl<I,O,E,F> Parser<I,O,E>
360+ /// for F where F: FnMut(&mut I) -> Result<O,E>`. That impl is what makes a
361+ /// closure usable where the trait is expected, and it says what signature
362+ /// the closure must have -- which is the only thing that types a closure
363+ /// passed to a generic parameter.
364+ fn_blankets: HashMap<String, (Vec<String>, Vec<Nim>, Nim)>,
365+ /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit`
366+ /// runs from `&self`, so they are parked here and folded into `bounds` by
367+ /// whoever called it.
368+ pending_apit_bounds: std::cell::RefCell<Vec<(String, syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>)>>,
359 /// The trait bounds on each generic parameter in scope, as `(trait,369 /// The trait bounds on each generic parameter in scope, as `(trait,
360 /// arguments)`.370 /// arguments)`.
361 bounds: HashMap<String, Vec<(String, Vec<Nim>)>>,371 bounds: HashMap<String, Vec<(String, Vec<Nim>)>>,
@@ -451,6 +461,8 @@ impl Lowerer {
451 const_gens: HashMap::new(),461 const_gens: HashMap::new(),
452 opaque_why: None,462 opaque_why: None,
453 trait_methods: HashMap::new(),463 trait_methods: HashMap::new(),
464+ fn_blankets: HashMap::new(),
465+ pending_apit_bounds: std::cell::RefCell::new(Vec::new()),
454 bounds: HashMap::new(),466 bounds: HashMap::new(),
455 const_names_at: std::cell::Cell::new(None),467 const_names_at: std::cell::Cell::new(None),
456 opaque_fns: Vec::new(),468 opaque_fns: Vec::new(),
@@ -1073,6 +1085,7 @@ impl Lowerer {
1073 // the trait branches below return early.1085 // the trait branches below return early.
1074 self.note_const_generics(&im.generics)?;1086 self.note_const_generics(&im.generics)?;
1075 self.note_bounds(&im.generics);1087 self.note_bounds(&im.generics);
1088+ self.note_fn_blanket(im);
1076 // Associated consts are registered here for the same reason anything1089 // Associated consts are registered here for the same reason anything
1077 // else is: an array length elsewhere may name one, and that name has1090 // else is: an array length elsewhere may name one, and that name has
1078 // to resolve before any body is lowered. `assoc_const` records them1091 // to resolve before any body is lowered. `assoc_const` records them
@@ -1564,6 +1577,99 @@ impl Lowerer {
1564 /// program mean something different — it only makes rustnim accept some1577 /// program mean something different — it only makes rustnim accept some
1565 /// programs rustc would have rejected, which does not matter when the1578 /// programs rustc would have rejected, which does not matter when the
1566 /// input is known-good Rust.1579 /// input is known-good Rust.
1580+ /// The proc type a value of this type variable must have, if one of its
1581+ /// bounds is a trait implemented blanket-wise for a callable.
1582+ fn callable_bound(&self, t: &Nim) -> Option<Nim> {
1583+ let Nim::Named(name, _) = t else { return None };
1584+ let bs = self.bounds.get(name)?;
1585+ for (tr, targs) in bs {
1586+ let Some((tparams, args, ret)) = self.fn_blankets.get(tr) else { continue };
1587+ // The trait's parameters, bound to what this use site supplies.
1588+ let mut m: HashMap<String, Nim> = HashMap::new();
1589+ for (p, a) in tparams.iter().zip(targs.iter()) {
1590+ m.insert(p.clone(), a.clone());
1591+ }
1592+ let args: Vec<Nim> = args.iter().map(|a| Self::subst(a, &m)).collect();
1593+ let ret = Self::subst(ret, &m);
1594+ return Some(Nim::Proc(args, Box::new(ret)));
1595+ }
1596+ None
1597+ }
1598+
1599+ /// Record a trait's blanket impl over a callable, if it has one.
1600+ ///
1601+ /// `impl<I,O,E,F> Parser<I,O,E> for F where F: FnMut(&mut I) -> Result<O,E>`
1602+ /// is what makes every winnow combinator's closure a `Parser`, and it is
1603+ /// the only thing that says what signature such a closure has. Rust reads
1604+ /// it the same way.
1605+ fn note_fn_blanket(&mut self, im: &syn::ItemImpl) {
1606+ let Some((path, _)) = &im.trait_ else { return };
1607+ let Some(seg) = path.segments.last() else { return };
1608+ let tname = seg.ident.to_string();
1609+ // The impl is *for* a bare generic parameter of the impl itself.
1610+ let syn::Type::Path(sp) = &*im.self_ty else { return };
1611+ if sp.path.segments.len() != 1 {
1612+ return;
1613+ }
1614+ let me = sp.path.segments[0].ident.to_string();
1615+ if !Self::generics_of(&im.generics).iter().any(|g| *g == me) {
1616+ return;
1617+ }
1618+ // The trait's own arguments, in order, are what a use site supplies.
1619+ let targs: Vec<String> = match &seg.arguments {
1620+ syn::PathArguments::AngleBracketed(a) => a
1621+ .args
1622+ .iter()
1623+ .filter_map(|g| match g {
1624+ GenericArgument::Type(syn::Type::Path(p)) if p.path.segments.len() == 1 => {
1625+ Some(p.path.segments[0].ident.to_string())
1626+ }
1627+ _ => None,
1628+ })
1629+ .collect(),
1630+ _ => return,
1631+ };
1632+ // And the `Fn` bound on that parameter gives the signature.
1633+ let mut bounds: Vec<&syn::TypeParamBound> = Vec::new();
1634+ for p in &im.generics.params {
1635+ if let syn::GenericParam::Type(t) = p {
1636+ if t.ident == me {
1637+ bounds.extend(t.bounds.iter());
1638+ }
1639+ }
1640+ }
1641+ if let Some(w) = &im.generics.where_clause {
1642+ for pred in &w.predicates {
1643+ let syn::WherePredicate::Type(pt) = pred else { continue };
1644+ let syn::Type::Path(bp) = &pt.bounded_ty else { continue };
1645+ if bp.path.segments.len() == 1 && bp.path.segments[0].ident == me {
1646+ bounds.extend(pt.bounds.iter());
1647+ }
1648+ }
1649+ }
1650+ for b in bounds {
1651+ let syn::TypeParamBound::Trait(tb) = b else { continue };
1652+ let Some(fs) = tb.path.segments.last() else { continue };
1653+ if !matches!(fs.ident.to_string().as_str(), "Fn" | "FnMut" | "FnOnce") {
1654+ continue;
1655+ }
1656+ let syn::PathArguments::Parenthesized(pa) = &fs.arguments else { continue };
1657+ let Ok(args) = pa.inputs.iter().map(|a| self.map_ty(&a.ty)).collect::<Result<Vec<_>, _>>()
1658+ else {
1659+ continue;
1660+ };
1661+ let ret = match &pa.output {
1662+ ReturnType::Default => Nim::Unit,
1663+ ReturnType::Type(_, t) => match self.map_ty(t) {
1664+ Ok(t) => t,
1665+ Err(_) => continue,
1666+ },
1667+ };
1668+ self.fn_blankets.insert(tname, (targs, args, ret));
1669+ return;
1670+ }
1671+ }
1672+
1567 /// Record the trait bounds on a signature's generic parameters, from both1673 /// Record the trait bounds on a signature's generic parameters, from both
1568 /// the parameter list and the `where` clause.1674 /// the parameter list and the `where` clause.
1569 ///1675 ///
@@ -1573,6 +1679,19 @@ impl Lowerer {
1573 /// instantiation without a bound to check. The bound is what lets rustnim1679 /// instantiation without a bound to check. The bound is what lets rustnim
1574 /// *type* the call in between.1680 /// *type* the call in between.
1575 fn note_bounds(&mut self, g: &syn::Generics) {1681 fn note_bounds(&mut self, g: &syn::Generics) {
1682+ let pending = std::mem::take(&mut *self.pending_apit_bounds.borrow_mut());
1683+ for (name, bounds) in pending {
1684+ let mut tmp = syn::Generics::default();
1685+ tmp.params.push(syn::GenericParam::Type(syn::TypeParam {
1686+ attrs: Vec::new(),
1687+ ident: proc_macro2::Ident::new(&name, proc_macro2::Span::call_site()),
1688+ colon_token: None,
1689+ bounds,
1690+ default: None,
1691+ }));
1692+ self.note_bounds(&tmp);
1693+ }
1694+
1576 let mut add = |me: &mut Self, name: String, bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>| {1695 let mut add = |me: &mut Self, name: String, bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>| {
1577 for b in bounds {1696 for b in bounds {
1578 let syn::TypeParamBound::Trait(tb) = b else { continue };1697 let syn::TypeParamBound::Trait(tb) = b else { continue };
@@ -1963,10 +2082,16 @@ impl Lowerer {
1963 /// they invent for a given position always carries the same name.2082 /// they invent for a given position always carries the same name.
1964 fn rewrite_apit(&self, ty: &syn::Type, next: &mut usize) -> syn::Type {2083 fn rewrite_apit(&self, ty: &syn::Type, next: &mut usize) -> syn::Type {
1965 use syn::Type;2084 use syn::Type;
1966- if let Type::ImplTrait(_) = ty {2085+ if let Type::ImplTrait(it) = ty {
1967 if self.map_ty(ty).is_err() {2086 if self.map_ty(ty).is_err() {
1968 *next += 1;2087 *next += 1;
1969 let name = format!("ImplT{next}");2088 let name = format!("ImplT{next}");
2089+ // The bound travels with the parameter it becomes: it is what
2090+ // says a closure passed here has to be callable, and with what
2091+ // signature. See `fn_blankets`.
2092+ self.pending_apit_bounds
2093+ .borrow_mut()
2094+ .push((name.clone(), it.bounds.clone()));
1970 return syn::parse_str::<Type>(&name).expect("generated ident parses");2095 return syn::parse_str::<Type>(&name).expect("generated ident parses");
1971 }2096 }
1972 return ty.clone();2097 return ty.clone();
@@ -6268,7 +6393,15 @@ impl Lowerer {
6268 let ptys: Vec<Nim> = callee.map(|s| s.params.clone()).unwrap_or_default();6393 let ptys: Vec<Nim> = callee.map(|s| s.params.clone()).unwrap_or_default();
6269 let mut args = Vec::new();6394 let mut args = Vec::new();
6270 for (i, a) in c.args.iter().enumerate() {6395 for (i, a) in c.args.iter().enumerate() {
6271- let want = ptys.get(i).cloned().filter(|t| !Self::mentions_any(t, &cgen));6396+ let raw = ptys.get(i).cloned();
6397+ // A parameter that is a type variable says nothing by itself --
6398+ // except when its bound is a trait with a blanket impl over a
6399+ // callable, which says exactly what a closure passed here must
6400+ // look like. That is how Rust types these too.
6401+ let want = match raw {
6402+ Some(t) if Self::mentions_any(&t, &cgen) => self.callable_bound(&t),
6403+ other => other,
6404+ };
6272 args.push(self.expr_at(a, want.as_ref())?);6405 args.push(self.expr_at(a, want.as_ref())?);
6273 }6406 }
6274 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();6407 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();