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>
4e4d09d parent: d459281 modified
DESIGN.md +41 -2 | @@ -677,8 +677,8 @@ not collide in one scope. | ||
| 677 | 677 | of 400 crates accepted.** A `macro_rules!` used to be a hard stop at item |
| 678 | 678 | level, failing a whole crate on sight. |
| 679 | 679 | |
| 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: | |
| 682 | 682 | the crates they unblocked hit their next blocker. That is now eight features |
| 683 | 683 | running. The capabilities are real and tested; the crate count is gated by |
| 684 | 684 | something else, and the remaining blockers say what: unsupported types (86) |
| @@ -717,6 +717,45 @@ much larger commitment than either previous shim. | ||
| 717 | 717 | declaration order, no whitespace, `null` for `None`, and a float keeps the |
| 718 | 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 | 759 | ## Proof of byte-identity for `base16ct` |
| 721 | 760 | |
| 722 | 761 | [`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 item | 677 | 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, 18 | 680 | +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 features | 682 | 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 by | 683 | 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 the | 717 | 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: exhaustive | 761 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
modified
src/lower.rs +101 -17 | @@ -542,7 +542,8 @@ impl Lowerer { | ||
| 542 | 542 | match item { |
| 543 | 543 | Item::Fn(f) => { |
| 544 | 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 | 547 | let name = f.sig.ident.to_string(); |
| 547 | 548 | let nim = self.fn_name(&self.cur_mod, &name); |
| 548 | 549 | self.forwards.push(self.head_of(&nim, &f.sig, None)?); |
| @@ -762,7 +763,7 @@ impl Lowerer { | ||
| 762 | 763 | } else { |
| 763 | 764 | 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 | 767 | self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); |
| 767 | 768 | self.methods |
| 768 | 769 | .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() }); |
| @@ -1170,7 +1171,10 @@ impl Lowerer { | ||
| 1170 | 1171 | // A method inside `impl<T> Foo<T>` is generic in the impl's |
| 1171 | 1172 | // parameters as well as its own. |
| 1172 | 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 | 1178 | if !params.contains(&g) { |
| 1175 | 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 | 1239 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| 1220 | 1240 | // `unsafe fn` marks a contract for callers; it does not change what |
| 1221 | 1241 | // the body means, so it lowers like any other proc. |
| @@ -1233,9 +1253,20 @@ impl Lowerer { | ||
| 1233 | 1253 | )); |
| 1234 | 1254 | } |
| 1235 | 1255 | let mut params = Vec::new(); |
| 1256 | + let mut fresh = 0usize; | |
| 1236 | 1257 | for a in &sig.inputs { |
| 1237 | 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 | 1272 | let ret = match &sig.output { |
| @@ -1452,7 +1483,8 @@ impl Lowerer { | ||
| 1452 | 1483 | )); |
| 1453 | 1484 | }; |
| 1454 | 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 | 1488 | self.func_named(&nim, &m.sig, &m.block, recv)?; |
| 1457 | 1489 | } |
| 1458 | 1490 | return Ok(()); |
| @@ -1603,7 +1635,7 @@ impl Lowerer { | ||
| 1603 | 1635 | let n = type_name(t.as_ref()?); |
| 1604 | 1636 | let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0; |
| 1605 | 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 | 1639 | } else { |
| 1608 | 1640 | None |
| 1609 | 1641 | } |
| @@ -1764,11 +1796,13 @@ impl Lowerer { | ||
| 1764 | 1796 | format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"), |
| 1765 | 1797 | format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="), |
| 1766 | 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)"), | |
| 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)"), | |
| 1772 | 1806 | ] { |
| 1773 | 1807 | self.line(&l); |
| 1774 | 1808 | } |
| @@ -2104,7 +2138,10 @@ impl Lowerer { | ||
| 2104 | 2138 | } |
| 2105 | 2139 | |
| 2106 | 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 | 2145 | if !gparams.contains(&g) { |
| 2109 | 2146 | gparams.push(g); |
| 2110 | 2147 | } |
| @@ -4851,7 +4888,44 @@ impl Lowerer { | ||
| 4851 | 4888 | _ => { |
| 4852 | 4889 | // A method defined in this file via `impl`, found by the |
| 4853 | 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 | 4929 | // Re-lower the arguments with the declared parameter types: |
| 4856 | 4930 | // a method's own signature says what width its literals are, |
| 4857 | 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 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 | + } | |
| 5461 | 5545 | } |
| 5462 | 5546 | |
| 5463 | 5547 | 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 | None | 764 | 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.methods | 768 | 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's | 1171 | // 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 what | 1240 | // `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 | None | 1640 | 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 the | 4889 | // 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 that | 5531 | +/// 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> { | ||
| 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 | 408 | Type::Infer(_) => Err("inferred type in a position that needs a name".into()), |
| 392 | 409 | 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 | +} | ||