Resolve return-position `impl Trait` from the body that produces it
Rust's opaque return type is a *single* concrete type -- the one the body produces. The caller is not allowed to name it, but it is not ambiguous, so reading it back off the body is exact rather than a guess. That is what this does, and it is why `-> impl Display` no longer has to be refused. The signature pass marks such a return type; a new pass between signatures and bodies lowers each body's result expression into a scratch buffer, keeps the *type*, and throws the text away. Using the real lowering rather than a separate syntactic guess is the point: the answer is then the same one the body will actually produce. Repeated to a fixpoint, since one such function may return the result of another. Forward declarations had to become deferred. They were rendered during collection, which is before any of this is known, so they are now held as the signature they came from -- along with the impl's generic parameters and `Self`, which are no longer current at render time -- and rendered afterwards. An unresolved one is an error, and so is two functions with identical signature text whose opaque types differ, since the resolution is keyed on that text. Found by the dependency run. Return-position `impl Trait` is not rare: 60 `-> impl Parser` in the graph, plus `Display`, `Stream`, `PathData` and `RangeBounds`. tests/cases/056. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
47de6c6 parent: ae1d333 modified
src/lower.rs +216 -12 | @@ -33,6 +33,26 @@ const NIM_KEYWORDS: &[&str] = &[ | ||
| 33 | 33 | /// inside `system`, which is a confusing place for it to surface. |
| 34 | 34 | const NIM_SHADOWED: &[&str] = &["new"]; |
| 35 | 35 | |
| 36 | +/// Placeholder for a return-position `impl Trait` between the signature pass | |
| 37 | +/// and `resolve_opaque`. It never reaches the output: an unresolved one is an | |
| 38 | +/// error. | |
| 39 | +const OPAQUE: &str = "RsOpaque"; | |
| 40 | + | |
| 41 | +/// Which table an opaque return type has to be written back into. | |
| 42 | +#[derive(Clone, Debug)] | |
| 43 | +enum Fwd { | |
| 44 | + Text(String), | |
| 45 | + /// Also carries the impl's generic parameters and `Self` type: rendering | |
| 46 | + /// happens after collection, when that context is no longer current. | |
| 47 | + Head(String, Box<syn::Signature>, Option<Nim>, Vec<String>, Option<Nim>), | |
| 48 | +} | |
| 49 | + | |
| 50 | +#[derive(Clone, Debug)] | |
| 51 | +enum OpaqueKey { | |
| 52 | + Free(String, String), | |
| 53 | + Method(String, String), | |
| 54 | +} | |
| 55 | + | |
| 36 | 56 | fn ident(name: &str) -> String { |
| 37 | 57 | if NIM_KEYWORDS.contains(&name) || NIM_SHADOWED.contains(&name) { |
| 38 | 58 | return format!("{name}_r"); |
| @@ -277,7 +297,10 @@ pub struct Lowerer { | ||
| 277 | 297 | /// bodies. Rust has no declaration-before-use rule and Nim does, so every |
| 278 | 298 | /// proc is declared up front rather than the input being reordered -- |
| 279 | 299 | /// which would not work for mutual recursion anyway. |
| 280 | - forwards: Vec<String>, | |
| 300 | + /// Forward declarations. Held unrendered where they come from a | |
| 301 | + /// signature, because a return-position `impl Trait` in one is not | |
| 302 | + /// resolved until after every signature has been collected. | |
| 303 | + forwards: Vec<Fwd>, | |
| 281 | 304 | /// Element type a `vec![..]` should build, from the binding's annotation. |
| 282 | 305 | vec_expect: Option<Nim>, |
| 283 | 306 | /// While lowering a formatting impl: the `Formatter` parameter's name. |
| @@ -317,6 +340,14 @@ pub struct Lowerer { | ||
| 317 | 340 | /// Const generic parameters, by name, with their Nim type. Nim's `static` |
| 318 | 341 | /// parameter is the same thing: a generic parameter that is a value. |
| 319 | 342 | const_gens: HashMap<String, String>, |
| 343 | + /// Bodies of functions whose return type is a return-position `impl | |
| 344 | + /// Trait`, keyed the same way as `fns`/`methods`, so `resolve_opaque` can | |
| 345 | + /// read the concrete type back off the body. | |
| 346 | + opaque_fns: Vec<(OpaqueKey, syn::Signature, syn::Block)>, | |
| 347 | + /// Resolved opaque return types, keyed by the signature's source text. | |
| 348 | + /// `signature` is called again on the emission pass and has only the | |
| 349 | + /// signature to go on, so that is what the key is made of. | |
| 350 | + opaque_ret: HashMap<String, Nim>, | |
| 320 | 351 | /// Module names supplied as separate input files. A `mod x;` naming one |
| 321 | 352 | /// of these is satisfied by that file having been passed in. |
| 322 | 353 | pub modules: Vec<String>, |
| @@ -391,6 +422,8 @@ impl Lowerer { | ||
| 391 | 422 | local_items: HashMap::new(), |
| 392 | 423 | emitted_local: std::collections::HashSet::new(), |
| 393 | 424 | const_gens: HashMap::new(), |
| 425 | + opaque_fns: Vec::new(), | |
| 426 | + opaque_ret: HashMap::new(), | |
| 394 | 427 | modules: Vec::new(), |
| 395 | 428 | emitted: 0, |
| 396 | 429 | features: Vec::new(), |
| @@ -489,6 +522,16 @@ impl Lowerer { | ||
| 489 | 522 | self.collect(item)?; |
| 490 | 523 | } |
| 491 | 524 | } |
| 525 | + | |
| 526 | + // Pass 1.5: resolve return-position `impl Trait`. | |
| 527 | + // | |
| 528 | + // Rust's opaque return type is a single concrete type -- the one the | |
| 529 | + // body produces. The caller is not allowed to name it, but it is not | |
| 530 | + // ambiguous, so reading it off the body is exact rather than a guess. | |
| 531 | + // Repeated to a fixpoint because one such function may return the | |
| 532 | + // result of another. | |
| 533 | + self.resolve_opaque()?; | |
| 534 | + | |
| 492 | 535 | // Pass 2: type definitions, which every signature may mention. |
| 493 | 536 | for (m, f) in files { |
| 494 | 537 | self.cur_mod = m.clone(); |
| @@ -502,6 +545,17 @@ impl Lowerer { | ||
| 502 | 545 | // reordering the input would not handle mutual recursion anyway. |
| 503 | 546 | if !self.forwards.is_empty() { |
| 504 | 547 | for f in self.forwards.clone() { |
| 548 | + let f = match f { | |
| 549 | + Fwd::Text(t) => t, | |
| 550 | + Fwd::Head(nim, sig, recv, gens, self_ty) => { | |
| 551 | + let og = std::mem::replace(&mut self.impl_generics, gens); | |
| 552 | + let os = std::mem::replace(&mut self.self_ty, self_ty); | |
| 553 | + let r = self.head_of(&nim, &sig, recv.as_ref()); | |
| 554 | + self.impl_generics = og; | |
| 555 | + self.self_ty = os; | |
| 556 | + r? | |
| 557 | + } | |
| 558 | + }; | |
| 505 | 559 | self.line(&f); |
| 506 | 560 | } |
| 507 | 561 | self.blank(); |
| @@ -663,9 +717,19 @@ impl Lowerer { | ||
| 663 | 717 | gen_names.extend(self.impl_trait_params(&f.sig)); |
| 664 | 718 | let name = f.sig.ident.to_string(); |
| 665 | 719 | let nim = self.fn_name(&self.cur_mod, &name); |
| 666 | - self.forwards.push(self.head_of(&nim, &f.sig, None)?); | |
| 720 | + self.forwards.push(Fwd::Head(nim.clone(), Box::new(f.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone())); | |
| 667 | 721 | self.fns |
| 668 | - .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() }); | |
| 722 | + .insert( | |
| 723 | + (self.cur_mod.clone(), name.clone()), | |
| 724 | + Sig { params, ret: ret.clone(), generics: gen_names.clone() }, | |
| 725 | + ); | |
| 726 | + if ret == Nim::Named(OPAQUE.into(), Vec::new()) { | |
| 727 | + self.opaque_fns.push(( | |
| 728 | + OpaqueKey::Free(self.cur_mod.clone(), name), | |
| 729 | + f.sig.clone(), | |
| 730 | + (*f.block).clone(), | |
| 731 | + )); | |
| 732 | + } | |
| 669 | 733 | } |
| 670 | 734 | Item::Struct(s) => { |
| 671 | 735 | self.note_const_generics(&s.generics)?; |
| @@ -856,11 +920,11 @@ impl Lowerer { | ||
| 856 | 920 | return Ok(()); |
| 857 | 921 | } |
| 858 | 922 | if is_fmt_trait(&tr) { |
| 859 | - self.forwards.push(format!( | |
| 923 | + self.forwards.push(Fwd::Text(format!( | |
| 860 | 924 | "proc {}*(self: {}): string", |
| 861 | 925 | fmt_proc(&tr), |
| 862 | 926 | self_ty.render() |
| 863 | - )); | |
| 927 | + ))); | |
| 864 | 928 | self.fmt_impls.insert((tyname, tr), ()); |
| 865 | 929 | return Ok(()); |
| 866 | 930 | } |
| @@ -876,7 +940,7 @@ impl Lowerer { | ||
| 876 | 940 | // The source type may be a tuple, whose rendering has |
| 877 | 941 | // parens and commas in it; a Nim identifier may not. |
| 878 | 942 | let name = format!("rsFrom{}{}", mangle(&tyname), mangle(&type_name(&src))); |
| 879 | - self.forwards.push(self.head_of(&name, &m.sig, None)?); | |
| 943 | + self.forwards.push(Fwd::Head(name.clone(), Box::new(m.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone())); | |
| 880 | 944 | self.from_types |
| 881 | 945 | .insert((type_name(&src), tyname.clone()), src.clone()); |
| 882 | 946 | self.from_impls |
| @@ -917,10 +981,17 @@ impl Lowerer { | ||
| 917 | 981 | None |
| 918 | 982 | }; |
| 919 | 983 | let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some()); |
| 920 | - self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); | |
| 984 | + self.forwards.push(Fwd::Head(nim.clone(), Box::new(m.sig.clone()), recv.clone(), self.impl_generics.clone(), self.self_ty.clone())); | |
| 921 | 985 | self.methods |
| 922 | - .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() }); | |
| 923 | - self.statics.insert((tyname.clone(), mname), nim); | |
| 986 | + .insert((tyname.clone(), mname.clone()), Sig { params, ret: ret.clone(), generics: gen_names.clone() }); | |
| 987 | + self.statics.insert((tyname.clone(), mname.clone()), nim); | |
| 988 | + if ret == Nim::Named(OPAQUE.into(), Vec::new()) { | |
| 989 | + self.opaque_fns.push(( | |
| 990 | + OpaqueKey::Method(tyname.clone(), mname), | |
| 991 | + m.sig.clone(), | |
| 992 | + m.block.clone(), | |
| 993 | + )); | |
| 994 | + } | |
| 924 | 995 | } |
| 925 | 996 | return Ok(()); |
| 926 | 997 | } |
| @@ -937,12 +1008,24 @@ impl Lowerer { | ||
| 937 | 1008 | } |
| 938 | 1009 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; |
| 939 | 1010 | let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); |
| 940 | - let head = self.head_of(&nim, &m.sig, recv.as_ref())?; | |
| 941 | - self.forwards.push(head); | |
| 1011 | + self.forwards.push(Fwd::Head( | |
| 1012 | + nim.clone(), | |
| 1013 | + Box::new(m.sig.clone()), | |
| 1014 | + recv.clone(), | |
| 1015 | + self.impl_generics.clone(), | |
| 1016 | + self.self_ty.clone(), | |
| 1017 | + )); | |
| 942 | 1018 | self.methods |
| 943 | - .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() }); | |
| 1019 | + .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret: ret.clone(), generics: gen_names.clone() }); | |
| 944 | 1020 | self.statics |
| 945 | 1021 | .insert((tyname.clone(), m.sig.ident.to_string()), nim); |
| 1022 | + if ret == Nim::Named(OPAQUE.into(), Vec::new()) { | |
| 1023 | + self.opaque_fns.push(( | |
| 1024 | + OpaqueKey::Method(tyname.clone(), m.sig.ident.to_string()), | |
| 1025 | + m.sig.clone(), | |
| 1026 | + m.block.clone(), | |
| 1027 | + )); | |
| 1028 | + } | |
| 946 | 1029 | } |
| 947 | 1030 | } |
| 948 | 1031 | } |
| @@ -1588,6 +1671,115 @@ impl Lowerer { | ||
| 1588 | 1671 | Ok(()) |
| 1589 | 1672 | } |
| 1590 | 1673 | |
| 1674 | + | |
| 1675 | + /// Give every return-position `impl Trait` its concrete type, taken from | |
| 1676 | + /// the body that produces it. | |
| 1677 | + /// | |
| 1678 | + /// The body is lowered into a scratch buffer that is then thrown away -- | |
| 1679 | + /// only the *type* of the result expression is kept. Doing it with the | |
| 1680 | + /// real lowering rather than a separate syntactic guess is what makes the | |
| 1681 | + /// answer the same one the body will actually produce. | |
| 1682 | + fn resolve_opaque(&mut self) -> Result<(), String> { | |
| 1683 | + let opaque = Nim::Named(OPAQUE.into(), Vec::new()); | |
| 1684 | + let pending = std::mem::take(&mut self.opaque_fns); | |
| 1685 | + let mut left = pending.clone(); | |
| 1686 | + // One function's opaque type may be another's; each round resolves at | |
| 1687 | + // least one or nothing more can be learned. | |
| 1688 | + for _ in 0..pending.len().max(1) { | |
| 1689 | + let mut progressed = false; | |
| 1690 | + let mut still = Vec::new(); | |
| 1691 | + for (key, sig, block) in left { | |
| 1692 | + match self.opaque_of(&sig, &block) { | |
| 1693 | + Some(t) if t != opaque => { | |
| 1694 | + let sk = quote::quote!(#sig).to_string(); | |
| 1695 | + if let Some(prev) = self.opaque_ret.get(&sk) { | |
| 1696 | + if prev != &t { | |
| 1697 | + return Err(format!( | |
| 1698 | + "two functions share the signature `{}` but \ | |
| 1699 | + their opaque return types are different \ | |
| 1700 | + (`{}` and `{}`); rustnim keys the \ | |
| 1701 | + resolution on the signature text, so it \ | |
| 1702 | + cannot tell them apart", | |
| 1703 | + sk, | |
| 1704 | + prev.render(), | |
| 1705 | + t.render() | |
| 1706 | + )); | |
| 1707 | + } | |
| 1708 | + } | |
| 1709 | + self.opaque_ret.insert(sk, t.clone()); | |
| 1710 | + match &key { | |
| 1711 | + OpaqueKey::Free(m, n) => { | |
| 1712 | + if let Some(s) = self.fns.get_mut(&(m.clone(), n.clone())) { | |
| 1713 | + s.ret = t; | |
| 1714 | + } | |
| 1715 | + } | |
| 1716 | + OpaqueKey::Method(ty, n) => { | |
| 1717 | + if let Some(s) = self.methods.get_mut(&(ty.clone(), n.clone())) { | |
| 1718 | + s.ret = t; | |
| 1719 | + } | |
| 1720 | + } | |
| 1721 | + } | |
| 1722 | + progressed = true; | |
| 1723 | + } | |
| 1724 | + _ => still.push((key, sig, block)), | |
| 1725 | + } | |
| 1726 | + } | |
| 1727 | + left = still; | |
| 1728 | + if left.is_empty() || !progressed { | |
| 1729 | + break; | |
| 1730 | + } | |
| 1731 | + } | |
| 1732 | + if let Some((key, sig, _)) = left.first() { | |
| 1733 | + let what = match key { | |
| 1734 | + OpaqueKey::Free(_, n) => n.clone(), | |
| 1735 | + OpaqueKey::Method(t, n) => format!("{t}::{n}"), | |
| 1736 | + }; | |
| 1737 | + let ret = match &sig.output { | |
| 1738 | + ReturnType::Type(_, t) => quote::quote!(#t).to_string(), | |
| 1739 | + ReturnType::Default => String::new(), | |
| 1740 | + }; | |
| 1741 | + return Err(format!( | |
| 1742 | + "`fn {what}() -> {ret}`: the concrete type behind the opaque \ | |
| 1743 | + return type could not be read off the body. Rust allows only \ | |
| 1744 | + one such type per function, so it is there -- rustnim could \ | |
| 1745 | + not work out what it is" | |
| 1746 | + )); | |
| 1747 | + } | |
| 1748 | + Ok(()) | |
| 1749 | + } | |
| 1750 | + | |
| 1751 | + /// The type of what a body returns, lowered for real and then discarded. | |
| 1752 | + fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { | |
| 1753 | + let tail = block.stmts.iter().rev().find_map(|st| match st { | |
| 1754 | + Stmt::Expr(e, None) => Some(e.clone()), | |
| 1755 | + _ => None, | |
| 1756 | + })?; | |
| 1757 | + // Parameters have to be in scope, or every mention of one is an | |
| 1758 | + // unknown name and the result types as nothing. | |
| 1759 | + let (ptys, _) = self.signature(sig).ok()?; | |
| 1760 | + self.push_scope(); | |
| 1761 | + let mut i = 0usize; | |
| 1762 | + for a in &sig.inputs { | |
| 1763 | + if let FnArg::Typed(t) = a { | |
| 1764 | + if let Pat::Ident(id) = &*t.pat { | |
| 1765 | + if let Some(ty) = ptys.get(i) { | |
| 1766 | + self.bind(&id.ident.to_string(), ty.clone().owned()); | |
| 1767 | + } | |
| 1768 | + } | |
| 1769 | + i += 1; | |
| 1770 | + } | |
| 1771 | + } | |
| 1772 | + let saved_out = self.out.clone(); | |
| 1773 | + let saved_indent = self.indent; | |
| 1774 | + let saved_hoisted = self.hoisted.clone(); | |
| 1775 | + let r = self.expr(&tail); | |
| 1776 | + self.out = saved_out; | |
| 1777 | + self.indent = saved_indent; | |
| 1778 | + self.hoisted = saved_hoisted; | |
| 1779 | + self.pop_scope(); | |
| 1780 | + r.ok().and_then(|v| v.ty) | |
| 1781 | + } | |
| 1782 | + | |
| 1591 | 1783 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| 1592 | 1784 | // `unsafe fn` marks a contract for callers; it does not change what |
| 1593 | 1785 | // the body means, so it lowers like any other proc. |
| @@ -1612,6 +1804,18 @@ impl Lowerer { | ||
| 1612 | 1804 | // which is a pragma rather than a type, so the return type here is |
| 1613 | 1805 | // the absence of one -- and the pragma is added at the proc head. |
| 1614 | 1806 | ReturnType::Type(_, t) if matches!(&**t, syn::Type::Never(_)) => Nim::Unit, |
| 1807 | + // Return-position `impl Trait` names one concrete type -- the one | |
| 1808 | + // the body produces. Rust hides which; it does not permit two. So | |
| 1809 | + // the type is recoverable from the body, and `resolve_opaque` | |
| 1810 | + // recovers it. Marked here, resolved between passes. | |
| 1811 | + ReturnType::Type(_, t) | |
| 1812 | + if matches!(&**t, syn::Type::ImplTrait(_)) && self.map_ty(t).is_err() => | |
| 1813 | + { | |
| 1814 | + match self.opaque_ret.get("e::quote!(#sig).to_string()) { | |
| 1815 | + Some(t) => t.clone(), | |
| 1816 | + None => Nim::Named(OPAQUE.into(), Vec::new()), | |
| 1817 | + } | |
| 1818 | + } | |
| 1615 | 1819 | ReturnType::Type(_, t) => { |
| 1616 | 1820 | let n = self.map_ty(t)?; |
| 1617 | 1821 | if returns_borrow(t) { n } else { n.owned() } |
| @@ -33,6 +33,26 @@ const NIM_KEYWORDS: &[&str] = &[ | |||
| 33 | /// inside `system`, which is a confusing place for it to surface. | 33 | /// inside `system`, which is a confusing place for it to surface. |
| 34 | const NIM_SHADOWED: &[&str] = &["new"]; | 34 | const NIM_SHADOWED: &[&str] = &["new"]; |
| 35 | 35 | ||
| 36 | +/// Placeholder for a return-position `impl Trait` between the signature pass | ||
| 37 | +/// and `resolve_opaque`. It never reaches the output: an unresolved one is an | ||
| 38 | +/// error. | ||
| 39 | +const OPAQUE: &str = "RsOpaque"; | ||
| 40 | + | ||
| 41 | +/// Which table an opaque return type has to be written back into. | ||
| 42 | +#[derive(Clone, Debug)] | ||
| 43 | +enum Fwd { | ||
| 44 | + Text(String), | ||
| 45 | + /// Also carries the impl's generic parameters and `Self` type: rendering | ||
| 46 | + /// happens after collection, when that context is no longer current. | ||
| 47 | + Head(String, Box<syn::Signature>, Option<Nim>, Vec<String>, Option<Nim>), | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +#[derive(Clone, Debug)] | ||
| 51 | +enum OpaqueKey { | ||
| 52 | + Free(String, String), | ||
| 53 | + Method(String, String), | ||
| 54 | +} | ||
| 55 | + | ||
| 36 | fn ident(name: &str) -> String { | 56 | fn ident(name: &str) -> String { |
| 37 | if NIM_KEYWORDS.contains(&name) || NIM_SHADOWED.contains(&name) { | 57 | if NIM_KEYWORDS.contains(&name) || NIM_SHADOWED.contains(&name) { |
| 38 | return format!("{name}_r"); | 58 | return format!("{name}_r"); |
| @@ -277,7 +297,10 @@ pub struct Lowerer { | |||
| 277 | /// bodies. Rust has no declaration-before-use rule and Nim does, so every | 297 | /// bodies. Rust has no declaration-before-use rule and Nim does, so every |
| 278 | /// proc is declared up front rather than the input being reordered -- | 298 | /// proc is declared up front rather than the input being reordered -- |
| 279 | /// which would not work for mutual recursion anyway. | 299 | /// which would not work for mutual recursion anyway. |
| 280 | - forwards: Vec<String>, | 300 | + /// Forward declarations. Held unrendered where they come from a |
| 301 | + /// signature, because a return-position `impl Trait` in one is not | ||
| 302 | + /// resolved until after every signature has been collected. | ||
| 303 | + forwards: Vec<Fwd>, | ||
| 281 | /// Element type a `vec![..]` should build, from the binding's annotation. | 304 | /// Element type a `vec![..]` should build, from the binding's annotation. |
| 282 | vec_expect: Option<Nim>, | 305 | vec_expect: Option<Nim>, |
| 283 | /// While lowering a formatting impl: the `Formatter` parameter's name. | 306 | /// While lowering a formatting impl: the `Formatter` parameter's name. |
| @@ -317,6 +340,14 @@ pub struct Lowerer { | |||
| 317 | /// Const generic parameters, by name, with their Nim type. Nim's `static` | 340 | /// Const generic parameters, by name, with their Nim type. Nim's `static` |
| 318 | /// parameter is the same thing: a generic parameter that is a value. | 341 | /// parameter is the same thing: a generic parameter that is a value. |
| 319 | const_gens: HashMap<String, String>, | 342 | const_gens: HashMap<String, String>, |
| 343 | + /// Bodies of functions whose return type is a return-position `impl | ||
| 344 | + /// Trait`, keyed the same way as `fns`/`methods`, so `resolve_opaque` can | ||
| 345 | + /// read the concrete type back off the body. | ||
| 346 | + opaque_fns: Vec<(OpaqueKey, syn::Signature, syn::Block)>, | ||
| 347 | + /// Resolved opaque return types, keyed by the signature's source text. | ||
| 348 | + /// `signature` is called again on the emission pass and has only the | ||
| 349 | + /// signature to go on, so that is what the key is made of. | ||
| 350 | + opaque_ret: HashMap<String, Nim>, | ||
| 320 | /// Module names supplied as separate input files. A `mod x;` naming one | 351 | /// Module names supplied as separate input files. A `mod x;` naming one |
| 321 | /// of these is satisfied by that file having been passed in. | 352 | /// of these is satisfied by that file having been passed in. |
| 322 | pub modules: Vec<String>, | 353 | pub modules: Vec<String>, |
| @@ -391,6 +422,8 @@ impl Lowerer { | |||
| 391 | local_items: HashMap::new(), | 422 | local_items: HashMap::new(), |
| 392 | emitted_local: std::collections::HashSet::new(), | 423 | emitted_local: std::collections::HashSet::new(), |
| 393 | const_gens: HashMap::new(), | 424 | const_gens: HashMap::new(), |
| 425 | + opaque_fns: Vec::new(), | ||
| 426 | + opaque_ret: HashMap::new(), | ||
| 394 | modules: Vec::new(), | 427 | modules: Vec::new(), |
| 395 | emitted: 0, | 428 | emitted: 0, |
| 396 | features: Vec::new(), | 429 | features: Vec::new(), |
| @@ -489,6 +522,16 @@ impl Lowerer { | |||
| 489 | self.collect(item)?; | 522 | self.collect(item)?; |
| 490 | } | 523 | } |
| 491 | } | 524 | } |
| 525 | + | ||
| 526 | + // Pass 1.5: resolve return-position `impl Trait`. | ||
| 527 | + // | ||
| 528 | + // Rust's opaque return type is a single concrete type -- the one the | ||
| 529 | + // body produces. The caller is not allowed to name it, but it is not | ||
| 530 | + // ambiguous, so reading it off the body is exact rather than a guess. | ||
| 531 | + // Repeated to a fixpoint because one such function may return the | ||
| 532 | + // result of another. | ||
| 533 | + self.resolve_opaque()?; | ||
| 534 | + | ||
| 492 | // Pass 2: type definitions, which every signature may mention. | 535 | // Pass 2: type definitions, which every signature may mention. |
| 493 | for (m, f) in files { | 536 | for (m, f) in files { |
| 494 | self.cur_mod = m.clone(); | 537 | self.cur_mod = m.clone(); |
| @@ -502,6 +545,17 @@ impl Lowerer { | |||
| 502 | // reordering the input would not handle mutual recursion anyway. | 545 | // reordering the input would not handle mutual recursion anyway. |
| 503 | if !self.forwards.is_empty() { | 546 | if !self.forwards.is_empty() { |
| 504 | for f in self.forwards.clone() { | 547 | for f in self.forwards.clone() { |
| 548 | + let f = match f { | ||
| 549 | + Fwd::Text(t) => t, | ||
| 550 | + Fwd::Head(nim, sig, recv, gens, self_ty) => { | ||
| 551 | + let og = std::mem::replace(&mut self.impl_generics, gens); | ||
| 552 | + let os = std::mem::replace(&mut self.self_ty, self_ty); | ||
| 553 | + let r = self.head_of(&nim, &sig, recv.as_ref()); | ||
| 554 | + self.impl_generics = og; | ||
| 555 | + self.self_ty = os; | ||
| 556 | + r? | ||
| 557 | + } | ||
| 558 | + }; | ||
| 505 | self.line(&f); | 559 | self.line(&f); |
| 506 | } | 560 | } |
| 507 | self.blank(); | 561 | self.blank(); |
| @@ -663,9 +717,19 @@ impl Lowerer { | |||
| 663 | gen_names.extend(self.impl_trait_params(&f.sig)); | 717 | gen_names.extend(self.impl_trait_params(&f.sig)); |
| 664 | let name = f.sig.ident.to_string(); | 718 | let name = f.sig.ident.to_string(); |
| 665 | let nim = self.fn_name(&self.cur_mod, &name); | 719 | let nim = self.fn_name(&self.cur_mod, &name); |
| 666 | - self.forwards.push(self.head_of(&nim, &f.sig, None)?); | 720 | + self.forwards.push(Fwd::Head(nim.clone(), Box::new(f.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone())); |
| 667 | self.fns | 721 | self.fns |
| 668 | - .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() }); | 722 | + .insert( |
| 723 | + (self.cur_mod.clone(), name.clone()), | ||
| 724 | + Sig { params, ret: ret.clone(), generics: gen_names.clone() }, | ||
| 725 | + ); | ||
| 726 | + if ret == Nim::Named(OPAQUE.into(), Vec::new()) { | ||
| 727 | + self.opaque_fns.push(( | ||
| 728 | + OpaqueKey::Free(self.cur_mod.clone(), name), | ||
| 729 | + f.sig.clone(), | ||
| 730 | + (*f.block).clone(), | ||
| 731 | + )); | ||
| 732 | + } | ||
| 669 | } | 733 | } |
| 670 | Item::Struct(s) => { | 734 | Item::Struct(s) => { |
| 671 | self.note_const_generics(&s.generics)?; | 735 | self.note_const_generics(&s.generics)?; |
| @@ -856,11 +920,11 @@ impl Lowerer { | |||
| 856 | return Ok(()); | 920 | return Ok(()); |
| 857 | } | 921 | } |
| 858 | if is_fmt_trait(&tr) { | 922 | if is_fmt_trait(&tr) { |
| 859 | - self.forwards.push(format!( | 923 | + self.forwards.push(Fwd::Text(format!( |
| 860 | "proc {}*(self: {}): string", | 924 | "proc {}*(self: {}): string", |
| 861 | fmt_proc(&tr), | 925 | fmt_proc(&tr), |
| 862 | self_ty.render() | 926 | self_ty.render() |
| 863 | - )); | 927 | + ))); |
| 864 | self.fmt_impls.insert((tyname, tr), ()); | 928 | self.fmt_impls.insert((tyname, tr), ()); |
| 865 | return Ok(()); | 929 | return Ok(()); |
| 866 | } | 930 | } |
| @@ -876,7 +940,7 @@ impl Lowerer { | |||
| 876 | // The source type may be a tuple, whose rendering has | 940 | // The source type may be a tuple, whose rendering has |
| 877 | // parens and commas in it; a Nim identifier may not. | 941 | // parens and commas in it; a Nim identifier may not. |
| 878 | let name = format!("rsFrom{}{}", mangle(&tyname), mangle(&type_name(&src))); | 942 | let name = format!("rsFrom{}{}", mangle(&tyname), mangle(&type_name(&src))); |
| 879 | - self.forwards.push(self.head_of(&name, &m.sig, None)?); | 943 | + self.forwards.push(Fwd::Head(name.clone(), Box::new(m.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone())); |
| 880 | self.from_types | 944 | self.from_types |
| 881 | .insert((type_name(&src), tyname.clone()), src.clone()); | 945 | .insert((type_name(&src), tyname.clone()), src.clone()); |
| 882 | self.from_impls | 946 | self.from_impls |
| @@ -917,10 +981,17 @@ impl Lowerer { | |||
| 917 | None | 981 | None |
| 918 | }; | 982 | }; |
| 919 | let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some()); | 983 | let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some()); |
| 920 | - self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); | 984 | + self.forwards.push(Fwd::Head(nim.clone(), Box::new(m.sig.clone()), recv.clone(), self.impl_generics.clone(), self.self_ty.clone())); |
| 921 | self.methods | 985 | self.methods |
| 922 | - .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() }); | 986 | + .insert((tyname.clone(), mname.clone()), Sig { params, ret: ret.clone(), generics: gen_names.clone() }); |
| 923 | - self.statics.insert((tyname.clone(), mname), nim); | 987 | + self.statics.insert((tyname.clone(), mname.clone()), nim); |
| 988 | + if ret == Nim::Named(OPAQUE.into(), Vec::new()) { | ||
| 989 | + self.opaque_fns.push(( | ||
| 990 | + OpaqueKey::Method(tyname.clone(), mname), | ||
| 991 | + m.sig.clone(), | ||
| 992 | + m.block.clone(), | ||
| 993 | + )); | ||
| 994 | + } | ||
| 924 | } | 995 | } |
| 925 | return Ok(()); | 996 | return Ok(()); |
| 926 | } | 997 | } |
| @@ -937,12 +1008,24 @@ impl Lowerer { | |||
| 937 | } | 1008 | } |
| 938 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | 1009 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; |
| 939 | let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); | 1010 | let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); |
| 940 | - let head = self.head_of(&nim, &m.sig, recv.as_ref())?; | 1011 | + self.forwards.push(Fwd::Head( |
| 941 | - self.forwards.push(head); | 1012 | + nim.clone(), |
| 1013 | + Box::new(m.sig.clone()), | ||
| 1014 | + recv.clone(), | ||
| 1015 | + self.impl_generics.clone(), | ||
| 1016 | + self.self_ty.clone(), | ||
| 1017 | + )); | ||
| 942 | self.methods | 1018 | self.methods |
| 943 | - .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() }); | 1019 | + .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret: ret.clone(), generics: gen_names.clone() }); |
| 944 | self.statics | 1020 | self.statics |
| 945 | .insert((tyname.clone(), m.sig.ident.to_string()), nim); | 1021 | .insert((tyname.clone(), m.sig.ident.to_string()), nim); |
| 1022 | + if ret == Nim::Named(OPAQUE.into(), Vec::new()) { | ||
| 1023 | + self.opaque_fns.push(( | ||
| 1024 | + OpaqueKey::Method(tyname.clone(), m.sig.ident.to_string()), | ||
| 1025 | + m.sig.clone(), | ||
| 1026 | + m.block.clone(), | ||
| 1027 | + )); | ||
| 1028 | + } | ||
| 946 | } | 1029 | } |
| 947 | } | 1030 | } |
| 948 | } | 1031 | } |
| @@ -1588,6 +1671,115 @@ impl Lowerer { | |||
| 1588 | Ok(()) | 1671 | Ok(()) |
| 1589 | } | 1672 | } |
| 1590 | 1673 | ||
| 1674 | + | ||
| 1675 | + /// Give every return-position `impl Trait` its concrete type, taken from | ||
| 1676 | + /// the body that produces it. | ||
| 1677 | + /// | ||
| 1678 | + /// The body is lowered into a scratch buffer that is then thrown away -- | ||
| 1679 | + /// only the *type* of the result expression is kept. Doing it with the | ||
| 1680 | + /// real lowering rather than a separate syntactic guess is what makes the | ||
| 1681 | + /// answer the same one the body will actually produce. | ||
| 1682 | + fn resolve_opaque(&mut self) -> Result<(), String> { | ||
| 1683 | + let opaque = Nim::Named(OPAQUE.into(), Vec::new()); | ||
| 1684 | + let pending = std::mem::take(&mut self.opaque_fns); | ||
| 1685 | + let mut left = pending.clone(); | ||
| 1686 | + // One function's opaque type may be another's; each round resolves at | ||
| 1687 | + // least one or nothing more can be learned. | ||
| 1688 | + for _ in 0..pending.len().max(1) { | ||
| 1689 | + let mut progressed = false; | ||
| 1690 | + let mut still = Vec::new(); | ||
| 1691 | + for (key, sig, block) in left { | ||
| 1692 | + match self.opaque_of(&sig, &block) { | ||
| 1693 | + Some(t) if t != opaque => { | ||
| 1694 | + let sk = quote::quote!(#sig).to_string(); | ||
| 1695 | + if let Some(prev) = self.opaque_ret.get(&sk) { | ||
| 1696 | + if prev != &t { | ||
| 1697 | + return Err(format!( | ||
| 1698 | + "two functions share the signature `{}` but \ | ||
| 1699 | + their opaque return types are different \ | ||
| 1700 | + (`{}` and `{}`); rustnim keys the \ | ||
| 1701 | + resolution on the signature text, so it \ | ||
| 1702 | + cannot tell them apart", | ||
| 1703 | + sk, | ||
| 1704 | + prev.render(), | ||
| 1705 | + t.render() | ||
| 1706 | + )); | ||
| 1707 | + } | ||
| 1708 | + } | ||
| 1709 | + self.opaque_ret.insert(sk, t.clone()); | ||
| 1710 | + match &key { | ||
| 1711 | + OpaqueKey::Free(m, n) => { | ||
| 1712 | + if let Some(s) = self.fns.get_mut(&(m.clone(), n.clone())) { | ||
| 1713 | + s.ret = t; | ||
| 1714 | + } | ||
| 1715 | + } | ||
| 1716 | + OpaqueKey::Method(ty, n) => { | ||
| 1717 | + if let Some(s) = self.methods.get_mut(&(ty.clone(), n.clone())) { | ||
| 1718 | + s.ret = t; | ||
| 1719 | + } | ||
| 1720 | + } | ||
| 1721 | + } | ||
| 1722 | + progressed = true; | ||
| 1723 | + } | ||
| 1724 | + _ => still.push((key, sig, block)), | ||
| 1725 | + } | ||
| 1726 | + } | ||
| 1727 | + left = still; | ||
| 1728 | + if left.is_empty() || !progressed { | ||
| 1729 | + break; | ||
| 1730 | + } | ||
| 1731 | + } | ||
| 1732 | + if let Some((key, sig, _)) = left.first() { | ||
| 1733 | + let what = match key { | ||
| 1734 | + OpaqueKey::Free(_, n) => n.clone(), | ||
| 1735 | + OpaqueKey::Method(t, n) => format!("{t}::{n}"), | ||
| 1736 | + }; | ||
| 1737 | + let ret = match &sig.output { | ||
| 1738 | + ReturnType::Type(_, t) => quote::quote!(#t).to_string(), | ||
| 1739 | + ReturnType::Default => String::new(), | ||
| 1740 | + }; | ||
| 1741 | + return Err(format!( | ||
| 1742 | + "`fn {what}() -> {ret}`: the concrete type behind the opaque \ | ||
| 1743 | + return type could not be read off the body. Rust allows only \ | ||
| 1744 | + one such type per function, so it is there -- rustnim could \ | ||
| 1745 | + not work out what it is" | ||
| 1746 | + )); | ||
| 1747 | + } | ||
| 1748 | + Ok(()) | ||
| 1749 | + } | ||
| 1750 | + | ||
| 1751 | + /// The type of what a body returns, lowered for real and then discarded. | ||
| 1752 | + fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { | ||
| 1753 | + let tail = block.stmts.iter().rev().find_map(|st| match st { | ||
| 1754 | + Stmt::Expr(e, None) => Some(e.clone()), | ||
| 1755 | + _ => None, | ||
| 1756 | + })?; | ||
| 1757 | + // Parameters have to be in scope, or every mention of one is an | ||
| 1758 | + // unknown name and the result types as nothing. | ||
| 1759 | + let (ptys, _) = self.signature(sig).ok()?; | ||
| 1760 | + self.push_scope(); | ||
| 1761 | + let mut i = 0usize; | ||
| 1762 | + for a in &sig.inputs { | ||
| 1763 | + if let FnArg::Typed(t) = a { | ||
| 1764 | + if let Pat::Ident(id) = &*t.pat { | ||
| 1765 | + if let Some(ty) = ptys.get(i) { | ||
| 1766 | + self.bind(&id.ident.to_string(), ty.clone().owned()); | ||
| 1767 | + } | ||
| 1768 | + } | ||
| 1769 | + i += 1; | ||
| 1770 | + } | ||
| 1771 | + } | ||
| 1772 | + let saved_out = self.out.clone(); | ||
| 1773 | + let saved_indent = self.indent; | ||
| 1774 | + let saved_hoisted = self.hoisted.clone(); | ||
| 1775 | + let r = self.expr(&tail); | ||
| 1776 | + self.out = saved_out; | ||
| 1777 | + self.indent = saved_indent; | ||
| 1778 | + self.hoisted = saved_hoisted; | ||
| 1779 | + self.pop_scope(); | ||
| 1780 | + r.ok().and_then(|v| v.ty) | ||
| 1781 | + } | ||
| 1782 | + | ||
| 1591 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { | 1783 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| 1592 | // `unsafe fn` marks a contract for callers; it does not change what | 1784 | // `unsafe fn` marks a contract for callers; it does not change what |
| 1593 | // the body means, so it lowers like any other proc. | 1785 | // the body means, so it lowers like any other proc. |
| @@ -1612,6 +1804,18 @@ impl Lowerer { | |||
| 1612 | // which is a pragma rather than a type, so the return type here is | 1804 | // which is a pragma rather than a type, so the return type here is |
| 1613 | // the absence of one -- and the pragma is added at the proc head. | 1805 | // the absence of one -- and the pragma is added at the proc head. |
| 1614 | ReturnType::Type(_, t) if matches!(&**t, syn::Type::Never(_)) => Nim::Unit, | 1806 | ReturnType::Type(_, t) if matches!(&**t, syn::Type::Never(_)) => Nim::Unit, |
| 1807 | + // Return-position `impl Trait` names one concrete type -- the one | ||
| 1808 | + // the body produces. Rust hides which; it does not permit two. So | ||
| 1809 | + // the type is recoverable from the body, and `resolve_opaque` | ||
| 1810 | + // recovers it. Marked here, resolved between passes. | ||
| 1811 | + ReturnType::Type(_, t) | ||
| 1812 | + if matches!(&**t, syn::Type::ImplTrait(_)) && self.map_ty(t).is_err() => | ||
| 1813 | + { | ||
| 1814 | + match self.opaque_ret.get("e::quote!(#sig).to_string()) { | ||
| 1815 | + Some(t) => t.clone(), | ||
| 1816 | + None => Nim::Named(OPAQUE.into(), Vec::new()), | ||
| 1817 | + } | ||
| 1818 | + } | ||
| 1615 | ReturnType::Type(_, t) => { | 1819 | ReturnType::Type(_, t) => { |
| 1616 | let n = self.map_ty(t)?; | 1820 | let n = self.map_ty(t)?; |
| 1617 | if returns_borrow(t) { n } else { n.owned() } | 1821 | if returns_borrow(t) { n } else { n.owned() } |
added
tests/cases/056-return-impl-trait.rs +35 -0 | new file mode 100644 | ||
| @@ -0,0 +1,35 @@ | ||
| 1 | +// Return-position `impl Trait`. Rust hides which concrete type comes back but | |
| 2 | +// does not permit two, so the type is recoverable from the body. | |
| 3 | +use std::fmt::Display; | |
| 4 | + | |
| 5 | +struct Tag { | |
| 6 | + n: u32, | |
| 7 | +} | |
| 8 | + | |
| 9 | +impl Display for Tag { | |
| 10 | + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | |
| 11 | + write!(f, "tag<{}>", self.n) | |
| 12 | + } | |
| 13 | +} | |
| 14 | + | |
| 15 | +fn make(n: u32) -> impl Display { | |
| 16 | + Tag { n: n * 2 } | |
| 17 | +} | |
| 18 | + | |
| 19 | +fn relay(n: u32) -> impl Display { | |
| 20 | + make(n + 1) | |
| 21 | +} | |
| 22 | + | |
| 23 | +struct Wrap; | |
| 24 | + | |
| 25 | +impl Wrap { | |
| 26 | + fn build(&self, n: u32) -> impl Display { | |
| 27 | + Tag { n } | |
| 28 | + } | |
| 29 | +} | |
| 30 | + | |
| 31 | +fn main() { | |
| 32 | + println!("{}", make(3)); | |
| 33 | + println!("{}", relay(3)); | |
| 34 | + println!("{}", Wrap.build(9)); | |
| 35 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | +// Return-position `impl Trait`. Rust hides which concrete type comes back but | ||
| 2 | +// does not permit two, so the type is recoverable from the body. | ||
| 3 | +use std::fmt::Display; | ||
| 4 | + | ||
| 5 | +struct Tag { | ||
| 6 | + n: u32, | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +impl Display for Tag { | ||
| 10 | + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
| 11 | + write!(f, "tag<{}>", self.n) | ||
| 12 | + } | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +fn make(n: u32) -> impl Display { | ||
| 16 | + Tag { n: n * 2 } | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +fn relay(n: u32) -> impl Display { | ||
| 20 | + make(n + 1) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +struct Wrap; | ||
| 24 | + | ||
| 25 | +impl Wrap { | ||
| 26 | + fn build(&self, n: u32) -> impl Display { | ||
| 27 | + Tag { n } | ||
| 28 | + } | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +fn main() { | ||
| 32 | + println!("{}", make(3)); | ||
| 33 | + println!("{}", relay(3)); | ||
| 34 | + println!("{}", Wrap.build(9)); | ||
| 35 | +} | ||