nandi/rustnimpublic Fork 0
4e4d09d
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.

Lower argument-position `impl Trait`; it was a generic parameter all along

The survey's largest remaining category, 86 crates blocked on "unsupported
types", turned out to be one message repeated: `unsupported `impl Trait`
type`. Not a category, one construct, and the message named neither the trait
nor the position.

Counting the occurrences splits it: 2,268 in argument position against 690 in
return position. Argument position is Rust's own desugaring of a generic --
`fn f(x: impl T)` is `fn f<A: T>(x: A)` -- and since bounds are already
dropped, what is left is a fresh type parameter that Nim instantiates
structurally at the call site exactly as Rust does. 86 blockers down to 59.
Return position is genuinely different, since the caller cannot name the
type, and is still rejected -- but the message now says which trait it was
and that argument position would have been fine.

That required renaming trait methods. The proc was rs<Trait>_<Type>_<method>,
which cannot be called on a generic receiver because there is no type to put
in the name yet. It is now rs<Trait>_<method>, so every impl of one trait
method shares a name and Nim overloads on the first parameter, which is how
the call resolves at instantiation; two traits declaring the same method
still cannot collide because the trait is in the name.

The exception is a receiverless trait method. Default::default() takes no
self, so two impls would differ only in return type and Nim reports
"overloaded 'rsDefault_default' leads to ambiguous calls". Those keep the
type in the name. Caught by the cosmic-theme case, which has Default impls
for two types; the bitflags shim's operators were renamed to match.

Survey unchanged at 18 of 400 accepted, 12 Nim-compilable. Ninth feature with
that result.

46 differential cases, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T23:59:22-07:00 Browse files
4e4d09d parent: d459281
modified DESIGN.md +41 -2
@@ -677,8 +677,8 @@ not collide in one scope.
677677 of 400 crates accepted.** A `macro_rules!` used to be a hard stop at item
678678 level, failing a whole crate on sight.
679679
680-Repetition and multi-rule then each moved it by **zero** — 18 before, 18
681-after, 12 compilable throughout — for the reason every previous feature did:
680+Repetition, multi-rule and argument-position `impl Trait` then each moved it
681+by **zero** — 18 before, 18 after, 12 compilable throughout — for the reason every previous feature did:
682682 the crates they unblocked hit their next blocker. That is now eight features
683683 running. The capabilities are real and tested; the crate count is gated by
684684 something else, and the remaining blockers say what: unsupported types (86)
@@ -717,6 +717,45 @@ much larger commitment than either previous shim.
717717 declaration order, no whitespace, `null` for `None`, and a float keeps the
718718 `.0` that `Display` drops — `{"x":-3,"ratio":2.0,"maybe":null}`.
719719
720+## What "unsupported type" was hiding
721+
722+The survey's largest remaining category was 86 crates blocked on "unsupported
723+types". Opening it: **every one was the same message**, `unsupported
724+\`impl Trait\` type` — one construct, not a category. The label was doing no
725+work and the message named neither the trait nor the position.
726+
727+Counting `impl Trait` in the sample splits it decisively:
728+
729+| | count | |
730+|---|---|---|
731+| argument position, `x: impl T` | 2,268 | **is a generic parameter** |
732+| return position, `-> impl T` | 690 | an opaque type |
733+
734+Argument position *is* Rust's own desugaring of a generic: `fn f(x: impl T)`
735+is `fn f<A: T>(x: A)`. Bounds are already dropped (see Generics), so what is
736+left is a fresh parameter, and Nim instantiates it structurally at the call
737+site exactly as Rust does. That is now what happens — 86 blockers down to 59.
738+
739+Return position is genuinely different: the caller cannot name the type, and
740+Nim has no equivalent. It is still rejected, but the message now says which
741+trait and that argument position would have been fine.
742+
743+### Trait methods are named by trait, not by type
744+
745+Making that work required a change with its own reasoning. A trait method's
746+proc was `rs<Trait>_<Type>_<method>`, which cannot be called on a generic
747+receiver — there is no type to put in the name yet. It is now
748+`rs<Trait>_<method>`, so every impl of one trait method shares a name and Nim
749+overloads on the first parameter, which is how the call resolves at
750+instantiation. Two traits declaring the same method still cannot collide,
751+because the trait is in the name.
752+
753+The exception is a trait method with **no receiver**. `Default::default()`
754+takes none, so two impls would differ only in return type, which Nim cannot
755+resolve — `overloaded 'rsDefault_default' leads to ambiguous calls`. Those
756+keep the type in the name. The `bitflags!` shim's operators were renamed to
757+match, since they are trait methods with a receiver.
758+
720759 ## Proof of byte-identity for `base16ct`
721760
722761 [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive
@@ -677,8 +677,8 @@ not collide in one scope.
677 of 400 crates accepted.** A `macro_rules!` used to be a hard stop at item677 of 400 crates accepted.** A `macro_rules!` used to be a hard stop at item
678 level, failing a whole crate on sight.678 level, failing a whole crate on sight.
679 679
680-Repetition and multi-rule then each moved it by **zero** — 18 before, 18680+Repetition, multi-rule and argument-position `impl Trait` then each moved it
681-after, 12 compilable throughout — for the reason every previous feature did:681+by **zero** — 18 before, 18 after, 12 compilable throughout — for the reason every previous feature did:
682 the crates they unblocked hit their next blocker. That is now eight features682 the crates they unblocked hit their next blocker. That is now eight features
683 running. The capabilities are real and tested; the crate count is gated by683 running. The capabilities are real and tested; the crate count is gated by
684 something else, and the remaining blockers say what: unsupported types (86)684 something else, and the remaining blockers say what: unsupported types (86)
@@ -717,6 +717,45 @@ much larger commitment than either previous shim.
717 declaration order, no whitespace, `null` for `None`, and a float keeps the717 declaration order, no whitespace, `null` for `None`, and a float keeps the
718 `.0` that `Display` drops — `{"x":-3,"ratio":2.0,"maybe":null}`.718 `.0` that `Display` drops — `{"x":-3,"ratio":2.0,"maybe":null}`.
719 719
720+## What "unsupported type" was hiding
721+
722+The survey's largest remaining category was 86 crates blocked on "unsupported
723+types". Opening it: **every one was the same message**, `unsupported
724+\`impl Trait\` type` — one construct, not a category. The label was doing no
725+work and the message named neither the trait nor the position.
726+
727+Counting `impl Trait` in the sample splits it decisively:
728+
729+| | count | |
730+|---|---|---|
731+| argument position, `x: impl T` | 2,268 | **is a generic parameter** |
732+| return position, `-> impl T` | 690 | an opaque type |
733+
734+Argument position *is* Rust's own desugaring of a generic: `fn f(x: impl T)`
735+is `fn f<A: T>(x: A)`. Bounds are already dropped (see Generics), so what is
736+left is a fresh parameter, and Nim instantiates it structurally at the call
737+site exactly as Rust does. That is now what happens — 86 blockers down to 59.
738+
739+Return position is genuinely different: the caller cannot name the type, and
740+Nim has no equivalent. It is still rejected, but the message now says which
741+trait and that argument position would have been fine.
742+
743+### Trait methods are named by trait, not by type
744+
745+Making that work required a change with its own reasoning. A trait method's
746+proc was `rs<Trait>_<Type>_<method>`, which cannot be called on a generic
747+receiver — there is no type to put in the name yet. It is now
748+`rs<Trait>_<method>`, so every impl of one trait method shares a name and Nim
749+overloads on the first parameter, which is how the call resolves at
750+instantiation. Two traits declaring the same method still cannot collide,
751+because the trait is in the name.
752+
753+The exception is a trait method with **no receiver**. `Default::default()`
754+takes none, so two impls would differ only in return type, which Nim cannot
755+resolve — `overloaded 'rsDefault_default' leads to ambiguous calls`. Those
756+keep the type in the name. The `bitflags!` shim's operators were renamed to
757+match, since they are trait methods with a receiver.
758+
720 ## Proof of byte-identity for `base16ct`759 ## Proof of byte-identity for `base16ct`
721 760
722 [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive761 [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive
modified src/lower.rs +101 -17
@@ -542,7 +542,8 @@ impl Lowerer {
542542 match item {
543543 Item::Fn(f) => {
544544 let (params, ret) = self.signature(&f.sig)?;
545- let gen_names = Self::generics_of(&f.sig.generics);
545+ let mut gen_names = Self::generics_of(&f.sig.generics);
546+ gen_names.extend(self.impl_trait_params(&f.sig));
546547 let name = f.sig.ident.to_string();
547548 let nim = self.fn_name(&self.cur_mod, &name);
548549 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
@@ -762,7 +763,7 @@ impl Lowerer {
762763 } else {
763764 None
764765 };
765- let nim = trait_method_name(&tyname, &tr, &mname);
766+ let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some());
766767 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
767768 self.methods
768769 .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });
@@ -1170,7 +1171,10 @@ impl Lowerer {
11701171 // A method inside `impl<T> Foo<T>` is generic in the impl's
11711172 // parameters as well as its own.
11721173 let mut params = self.impl_generics.clone();
1173- for g in Self::generics_of(&sig.generics) {
1174+ for g in Self::generics_of(&sig.generics)
1175+ .into_iter()
1176+ .chain(self.impl_trait_params(sig))
1177+ {
11741178 if !params.contains(&g) {
11751179 params.push(g);
11761180 }
@@ -1216,6 +1220,22 @@ impl Lowerer {
12161220 })
12171221 }
12181222
1223+ /// `impl Trait` in argument position *is* a generic parameter — that is
1224+ /// Rust's own desugaring, `fn f(x: impl T)` for `fn f<A: T>(x: A)`. The
1225+ /// bound is dropped like any other, so what is left is a fresh parameter.
1226+ /// In return position it is an opaque type instead, and has no equivalent.
1227+ fn impl_trait_params(&self, sig: &syn::Signature) -> Vec<String> {
1228+ let mut out = Vec::new();
1229+ for a in &sig.inputs {
1230+ if let FnArg::Typed(t) = a {
1231+ if matches!(&*t.ty, syn::Type::ImplTrait(_)) && self.map_ty(&t.ty).is_err() {
1232+ out.push(format!("ImplT{}", out.len() + 1));
1233+ }
1234+ }
1235+ }
1236+ out
1237+ }
1238+
12191239 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
12201240 // `unsafe fn` marks a contract for callers; it does not change what
12211241 // the body means, so it lowers like any other proc.
@@ -1233,9 +1253,20 @@ impl Lowerer {
12331253 ));
12341254 }
12351255 let mut params = Vec::new();
1256+ let mut fresh = 0usize;
12361257 for a in &sig.inputs {
12371258 if let FnArg::Typed(t) = a {
1238- params.push(self.map_ty(&t.ty)?);
1259+ match self.map_ty(&t.ty) {
1260+ Ok(n) => params.push(n),
1261+ Err(e) => {
1262+ if matches!(&*t.ty, syn::Type::ImplTrait(_)) {
1263+ fresh += 1;
1264+ params.push(Nim::Named(format!("ImplT{fresh}"), vec![]));
1265+ } else {
1266+ return Err(e);
1267+ }
1268+ }
1269+ }
12391270 }
12401271 }
12411272 let ret = match &sig.output {
@@ -1452,7 +1483,8 @@ impl Lowerer {
14521483 ));
14531484 };
14541485 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1455- let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1486+ let nim =
1487+ trait_method_name(&tyname, &tr, &m.sig.ident.to_string(), recv.is_some());
14561488 self.func_named(&nim, &m.sig, &m.block, recv)?;
14571489 }
14581490 return Ok(());
@@ -1603,7 +1635,7 @@ impl Lowerer {
16031635 let n = type_name(t.as_ref()?);
16041636 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
16051637 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1606- Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1638+ Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1, true))
16071639 } else {
16081640 None
16091641 }
@@ -1764,11 +1796,13 @@ impl Lowerer {
17641796 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),
17651797 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),
17661798 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),
1767- format!("proc rsBitOr_{name}_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1768- format!("proc rsBitAnd_{name}_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1769- format!("proc rsBitXor_{name}_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1770- format!("proc rsSub_{name}_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1771- format!("proc rsNot_{name}_not*(a: {name}): {name} = {name}_complement(a)"),
1799+ // Named as a trait method with a receiver is: by trait, not by
1800+ // type, so Nim overloads on the operand.
1801+ format!("proc rsBitOr_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1802+ format!("proc rsBitAnd_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1803+ format!("proc rsBitXor_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1804+ format!("proc rsSub_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1805+ format!("proc rsNot_not*(a: {name}): {name} = {name}_complement(a)"),
17721806 ] {
17731807 self.line(&l);
17741808 }
@@ -2104,7 +2138,10 @@ impl Lowerer {
21042138 }
21052139
21062140 let mut gparams = self.impl_generics.clone();
2107- for g in Self::generics_of(&sig.generics) {
2141+ for g in Self::generics_of(&sig.generics)
2142+ .into_iter()
2143+ .chain(self.impl_trait_params(sig))
2144+ {
21082145 if !gparams.contains(&g) {
21092146 gparams.push(g);
21102147 }
@@ -4851,7 +4888,44 @@ impl Lowerer {
48514888 _ => {
48524889 // A method defined in this file via `impl`, found by the
48534890 // receiver's type rather than by name alone.
4854- let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
4891+ // A receiver whose type is a generic parameter is resolved by
4892+ // Nim at instantiation, so the call just needs the right proc
4893+ // name. Every impl of a trait method shares one, so any
4894+ // candidate with this method name gives it.
4895+ let generic_recv = rt
4896+ .as_ref()
4897+ .map(type_name)
4898+ .is_some_and(|n| self.fn_generics.iter().any(|g| *g == n));
4899+ let key = if generic_recv {
4900+ let mut names: Vec<String> = self
4901+ .statics
4902+ .iter()
4903+ .filter(|((_, m), _)| *m == name)
4904+ .map(|(_, v)| v.clone())
4905+ .collect();
4906+ names.sort();
4907+ names.dedup();
4908+ match names.len() {
4909+ 1 => {
4910+ let k = self
4911+ .methods
4912+ .keys()
4913+ .find(|(_, m)| *m == name)
4914+ .cloned();
4915+ k
4916+ }
4917+ 0 => None,
4918+ _ => {
4919+ return Err(format!(
4920+ "`.{name}()` on a generic receiver is ambiguous: \
4921+ {} different procs are named for it",
4922+ names.len()
4923+ ))
4924+ }
4925+ }
4926+ } else {
4927+ rt.as_ref().map(|t| (type_name(t), name.clone()))
4928+ };
48554929 // Re-lower the arguments with the declared parameter types:
48564930 // a method's own signature says what width its literals are,
48574931 // which the receiver's type does not.
@@ -5454,10 +5528,20 @@ fn operator_trait(t: &str) -> Option<&'static str> {
54545528 })
54555529 }
54565530
5457-/// The Nim proc name for a trait method, qualified by trait and type so that
5458-/// two traits declaring the same method name cannot collide.
5459-fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
5460- format!("rs{}_{}_{}", tr, ty, m)
5531+/// The Nim proc name for a trait method. Qualified by the *trait* but not by
5532+/// the type: every impl of one trait method shares a name and Nim overloads on
5533+/// the first parameter, which is how a call on a generic receiver resolves at
5534+/// instantiation. Two traits declaring the same method name still cannot
5535+/// collide, because the trait is in the name.
5536+fn trait_method_name(ty: &str, tr: &str, m: &str, has_self: bool) -> String {
5537+ if has_self {
5538+ format!("rs{}_{}", tr, m)
5539+ } else {
5540+ // No receiver to overload on -- `Default::default()` takes none -- so
5541+ // two impls would differ only in return type, which Nim cannot
5542+ // resolve. The type goes in the name instead.
5543+ format!("rs{}_{}_{}", tr, ty, m)
5544+ }
54615545 }
54625546
54635547 fn is_fmt_trait(t: &str) -> bool {
@@ -542,7 +542,8 @@ impl Lowerer {
542 match item {542 match item {
543 Item::Fn(f) => {543 Item::Fn(f) => {
544 let (params, ret) = self.signature(&f.sig)?;544 let (params, ret) = self.signature(&f.sig)?;
545- let gen_names = Self::generics_of(&f.sig.generics);545+ let mut gen_names = Self::generics_of(&f.sig.generics);
546+ gen_names.extend(self.impl_trait_params(&f.sig));
546 let name = f.sig.ident.to_string();547 let name = f.sig.ident.to_string();
547 let nim = self.fn_name(&self.cur_mod, &name);548 let nim = self.fn_name(&self.cur_mod, &name);
548 self.forwards.push(self.head_of(&nim, &f.sig, None)?);549 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
@@ -762,7 +763,7 @@ impl Lowerer {
762 } else {763 } else {
763 None764 None
764 };765 };
765- let nim = trait_method_name(&tyname, &tr, &mname);766+ let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some());
766 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);767 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
767 self.methods768 self.methods
768 .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });769 .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });
@@ -1170,7 +1171,10 @@ impl Lowerer {
1170 // A method inside `impl<T> Foo<T>` is generic in the impl's1171 // A method inside `impl<T> Foo<T>` is generic in the impl's
1171 // parameters as well as its own.1172 // parameters as well as its own.
1172 let mut params = self.impl_generics.clone();1173 let mut params = self.impl_generics.clone();
1173- for g in Self::generics_of(&sig.generics) {1174+ for g in Self::generics_of(&sig.generics)
1175+ .into_iter()
1176+ .chain(self.impl_trait_params(sig))
1177+ {
1174 if !params.contains(&g) {1178 if !params.contains(&g) {
1175 params.push(g);1179 params.push(g);
1176 }1180 }
@@ -1216,6 +1220,22 @@ impl Lowerer {
1216 })1220 })
1217 }1221 }
1218 1222
1223+ /// `impl Trait` in argument position *is* a generic parameter — that is
1224+ /// Rust's own desugaring, `fn f(x: impl T)` for `fn f<A: T>(x: A)`. The
1225+ /// bound is dropped like any other, so what is left is a fresh parameter.
1226+ /// In return position it is an opaque type instead, and has no equivalent.
1227+ fn impl_trait_params(&self, sig: &syn::Signature) -> Vec<String> {
1228+ let mut out = Vec::new();
1229+ for a in &sig.inputs {
1230+ if let FnArg::Typed(t) = a {
1231+ if matches!(&*t.ty, syn::Type::ImplTrait(_)) && self.map_ty(&t.ty).is_err() {
1232+ out.push(format!("ImplT{}", out.len() + 1));
1233+ }
1234+ }
1235+ }
1236+ out
1237+ }
1238+
1219 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {1239 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
1220 // `unsafe fn` marks a contract for callers; it does not change what1240 // `unsafe fn` marks a contract for callers; it does not change what
1221 // the body means, so it lowers like any other proc.1241 // the body means, so it lowers like any other proc.
@@ -1233,9 +1253,20 @@ impl Lowerer {
1233 ));1253 ));
1234 }1254 }
1235 let mut params = Vec::new();1255 let mut params = Vec::new();
1256+ let mut fresh = 0usize;
1236 for a in &sig.inputs {1257 for a in &sig.inputs {
1237 if let FnArg::Typed(t) = a {1258 if let FnArg::Typed(t) = a {
1238- params.push(self.map_ty(&t.ty)?);1259+ match self.map_ty(&t.ty) {
1260+ Ok(n) => params.push(n),
1261+ Err(e) => {
1262+ if matches!(&*t.ty, syn::Type::ImplTrait(_)) {
1263+ fresh += 1;
1264+ params.push(Nim::Named(format!("ImplT{fresh}"), vec![]));
1265+ } else {
1266+ return Err(e);
1267+ }
1268+ }
1269+ }
1239 }1270 }
1240 }1271 }
1241 let ret = match &sig.output {1272 let ret = match &sig.output {
@@ -1452,7 +1483,8 @@ impl Lowerer {
1452 ));1483 ));
1453 };1484 };
1454 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };1485 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1455- let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());1486+ let nim =
1487+ trait_method_name(&tyname, &tr, &m.sig.ident.to_string(), recv.is_some());
1456 self.func_named(&nim, &m.sig, &m.block, recv)?;1488 self.func_named(&nim, &m.sig, &m.block, recv)?;
1457 }1489 }
1458 return Ok(());1490 return Ok(());
@@ -1603,7 +1635,7 @@ impl Lowerer {
1603 let n = type_name(t.as_ref()?);1635 let n = type_name(t.as_ref()?);
1604 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;1636 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1605 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {1637 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1606- Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))1638+ Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1, true))
1607 } else {1639 } else {
1608 None1640 None
1609 }1641 }
@@ -1764,11 +1796,13 @@ impl Lowerer {
1764 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),1796 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),
1765 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),1797 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),
1766 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),1798 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),
1767- format!("proc rsBitOr_{name}_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),1799+ // Named as a trait method with a receiver is: by trait, not by
1768- format!("proc rsBitAnd_{name}_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),1800+ // type, so Nim overloads on the operand.
1769- format!("proc rsBitXor_{name}_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),1801+ format!("proc rsBitOr_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1770- format!("proc rsSub_{name}_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),1802+ format!("proc rsBitAnd_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1771- format!("proc rsNot_{name}_not*(a: {name}): {name} = {name}_complement(a)"),1803+ format!("proc rsBitXor_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1804+ format!("proc rsSub_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1805+ format!("proc rsNot_not*(a: {name}): {name} = {name}_complement(a)"),
1772 ] {1806 ] {
1773 self.line(&l);1807 self.line(&l);
1774 }1808 }
@@ -2104,7 +2138,10 @@ impl Lowerer {
2104 }2138 }
2105 2139
2106 let mut gparams = self.impl_generics.clone();2140 let mut gparams = self.impl_generics.clone();
2107- for g in Self::generics_of(&sig.generics) {2141+ for g in Self::generics_of(&sig.generics)
2142+ .into_iter()
2143+ .chain(self.impl_trait_params(sig))
2144+ {
2108 if !gparams.contains(&g) {2145 if !gparams.contains(&g) {
2109 gparams.push(g);2146 gparams.push(g);
2110 }2147 }
@@ -4851,7 +4888,44 @@ impl Lowerer {
4851 _ => {4888 _ => {
4852 // A method defined in this file via `impl`, found by the4889 // A method defined in this file via `impl`, found by the
4853 // receiver's type rather than by name alone.4890 // receiver's type rather than by name alone.
4854- let key = rt.as_ref().map(|t| (type_name(t), name.clone()));4891+ // A receiver whose type is a generic parameter is resolved by
4892+ // Nim at instantiation, so the call just needs the right proc
4893+ // name. Every impl of a trait method shares one, so any
4894+ // candidate with this method name gives it.
4895+ let generic_recv = rt
4896+ .as_ref()
4897+ .map(type_name)
4898+ .is_some_and(|n| self.fn_generics.iter().any(|g| *g == n));
4899+ let key = if generic_recv {
4900+ let mut names: Vec<String> = self
4901+ .statics
4902+ .iter()
4903+ .filter(|((_, m), _)| *m == name)
4904+ .map(|(_, v)| v.clone())
4905+ .collect();
4906+ names.sort();
4907+ names.dedup();
4908+ match names.len() {
4909+ 1 => {
4910+ let k = self
4911+ .methods
4912+ .keys()
4913+ .find(|(_, m)| *m == name)
4914+ .cloned();
4915+ k
4916+ }
4917+ 0 => None,
4918+ _ => {
4919+ return Err(format!(
4920+ "`.{name}()` on a generic receiver is ambiguous: \
4921+ {} different procs are named for it",
4922+ names.len()
4923+ ))
4924+ }
4925+ }
4926+ } else {
4927+ rt.as_ref().map(|t| (type_name(t), name.clone()))
4928+ };
4855 // Re-lower the arguments with the declared parameter types:4929 // Re-lower the arguments with the declared parameter types:
4856 // a method's own signature says what width its literals are,4930 // a method's own signature says what width its literals are,
4857 // which the receiver's type does not.4931 // which the receiver's type does not.
@@ -5454,10 +5528,20 @@ fn operator_trait(t: &str) -> Option<&'static str> {
5454 })5528 })
5455 }5529 }
5456 5530
5457-/// The Nim proc name for a trait method, qualified by trait and type so that5531+/// The Nim proc name for a trait method. Qualified by the *trait* but not by
5458-/// two traits declaring the same method name cannot collide.5532+/// the type: every impl of one trait method shares a name and Nim overloads on
5459-fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {5533+/// the first parameter, which is how a call on a generic receiver resolves at
5460- format!("rs{}_{}_{}", tr, ty, m)5534+/// instantiation. Two traits declaring the same method name still cannot
5535+/// collide, because the trait is in the name.
5536+fn trait_method_name(ty: &str, tr: &str, m: &str, has_self: bool) -> String {
5537+ if has_self {
5538+ format!("rs{}_{}", tr, m)
5539+ } else {
5540+ // No receiver to overload on -- `Default::default()` takes none -- so
5541+ // two impls would differ only in return type, which Nim cannot
5542+ // resolve. The type goes in the name instead.
5543+ format!("rs{}_{}_{}", tr, ty, m)
5544+ }
5461 }5545 }
5462 5546
5463 fn is_fmt_trait(t: &str) -> bool {5547 fn is_fmt_trait(t: &str) -> bool {
modified src/ty.rs +18 -1
@@ -386,7 +386,24 @@ pub fn map(t: &Type) -> Result<Nim, String> {
386386 }
387387 }
388388 }
389- Err("unsupported `impl Trait` type".into())
389+ // Named so the message says which trait and, by implication, that
390+ // the caller is in return position -- argument position is handled
391+ // by the lowering, which turns it into a generic parameter.
392+ let named = i
393+ .bounds
394+ .iter()
395+ .find_map(|b| match b {
396+ TypeParamBound::Trait(tb) => {
397+ tb.path.segments.last().map(|s| s.ident.to_string())
398+ }
399+ _ => None,
400+ })
401+ .unwrap_or_else(|| "?".into());
402+ Err(format!(
403+ "`impl {named}` in return position is an opaque type: the caller \
404+ cannot name it, and Nim has no equivalent. In argument position \
405+ `impl {named}` lowers fine, as the generic parameter it is"
406+ ))
390407 }
391408 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
392409 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
@@ -386,7 +386,24 @@ pub fn map(t: &Type) -> Result<Nim, String> {
386 }386 }
387 }387 }
388 }388 }
389- Err("unsupported `impl Trait` type".into())389+ // Named so the message says which trait and, by implication, that
390+ // the caller is in return position -- argument position is handled
391+ // by the lowering, which turns it into a generic parameter.
392+ let named = i
393+ .bounds
394+ .iter()
395+ .find_map(|b| match b {
396+ TypeParamBound::Trait(tb) => {
397+ tb.path.segments.last().map(|s| s.ident.to_string())
398+ }
399+ _ => None,
400+ })
401+ .unwrap_or_else(|| "?".into());
402+ Err(format!(
403+ "`impl {named}` in return position is an opaque type: the caller \
404+ cannot name it, and Nim has no equivalent. In argument position \
405+ `impl {named}` lowers fine, as the generic parameter it is"
406+ ))
390 }407 }
391 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),408 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
392 other => Err(format!("unsupported type form: {:?}", discriminant(other))),409 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
added tests/cases/040-impl-trait-args.rs +57 -0
new file mode 100644
@@ -0,0 +1,57 @@
1+// `impl Trait` in argument position *is* a generic parameter -- that is Rust's
2+// own desugaring, `fn f(x: impl T)` for `fn f<A: T>(x: A)`. The bound is
3+// dropped like any other bound, so what is left is a fresh parameter, and Nim
4+// instantiates it structurally at the call site.
5+//
6+// In return position it is an opaque type instead: the caller cannot name it,
7+// and there is no Nim equivalent, so that is still rejected.
8+
9+use core::fmt::Debug;
10+
11+trait Area {
12+ fn area(&self) -> i32;
13+}
14+
15+struct Sq {
16+ side: i32,
17+}
18+struct Rect {
19+ w: i32,
20+ h: i32,
21+}
22+
23+impl Area for Sq {
24+ fn area(&self) -> i32 {
25+ self.side * self.side
26+ }
27+}
28+impl Area for Rect {
29+ fn area(&self) -> i32 {
30+ self.w * self.h
31+ }
32+}
33+
34+fn describe(shape: impl Area) -> i32 {
35+ shape.area()
36+}
37+
38+fn twice(shape: impl Area) -> i32 {
39+ shape.area() * 2
40+}
41+
42+fn both(a: impl Area, b: impl Area) -> i32 {
43+ a.area() + b.area()
44+}
45+
46+fn show(x: impl Debug) -> i32 {
47+ let _ = x;
48+ 1
49+}
50+
51+fn main() {
52+ println!("{}", describe(Sq { side: 4 }));
53+ println!("{}", describe(Rect { w: 3, h: 5 }));
54+ println!("{}", twice(Sq { side: 3 }));
55+ println!("{}", both(Sq { side: 2 }, Rect { w: 10, h: 10 }));
56+ println!("{}", show(7i32));
57+}
new file mode 100644
@@ -0,0 +1,57 @@
1+// `impl Trait` in argument position *is* a generic parameter -- that is Rust's
2+// own desugaring, `fn f(x: impl T)` for `fn f<A: T>(x: A)`. The bound is
3+// dropped like any other bound, so what is left is a fresh parameter, and Nim
4+// instantiates it structurally at the call site.
5+//
6+// In return position it is an opaque type instead: the caller cannot name it,
7+// and there is no Nim equivalent, so that is still rejected.
8+
9+use core::fmt::Debug;
10+
11+trait Area {
12+ fn area(&self) -> i32;
13+}
14+
15+struct Sq {
16+ side: i32,
17+}
18+struct Rect {
19+ w: i32,
20+ h: i32,
21+}
22+
23+impl Area for Sq {
24+ fn area(&self) -> i32 {
25+ self.side * self.side
26+ }
27+}
28+impl Area for Rect {
29+ fn area(&self) -> i32 {
30+ self.w * self.h
31+ }
32+}
33+
34+fn describe(shape: impl Area) -> i32 {
35+ shape.area()
36+}
37+
38+fn twice(shape: impl Area) -> i32 {
39+ shape.area() * 2
40+}
41+
42+fn both(a: impl Area, b: impl Area) -> i32 {
43+ a.area() + b.area()
44+}
45+
46+fn show(x: impl Debug) -> i32 {
47+ let _ = x;
48+ 1
49+}
50+
51+fn main() {
52+ println!("{}", describe(Sq { side: 4 }));
53+ println!("{}", describe(Rect { w: 3, h: 5 }));
54+ println!("{}", twice(Sq { side: 3 }));
55+ println!("{}", both(Sq { side: 2 }, Rect { w: 10, h: 10 }));
56+ println!("{}", show(7i32));
57+}