Make opaque-type resolution finish, and read arms correctly
The 357-crate run stopped failing and started never finishing. Three costs,
all introduced with the opaque-return-type work and all invisible on one
crate:
* `refill_const_names` rebuilt a map over every enum and every associated
const on *every* `map_ty` call. This was almost all of it -- thirty crates
went from over two minutes to twenty-three seconds once it rebuilds only
when those tables have actually grown.
* `opaque_of` cloned the whole output buffer -- megabytes of translated
program -- to save and restore it around each candidate expression. It
truncates back instead.
* `resolve_opaque` ran up to `pending.len()` rounds, each re-lowering every
unresolved body. The bound is a chain depth, not a count: `a` returning
`b`'s result returning `c`'s is three rounds. Capped at eight, with
anything deeper reported rather than waited for.
And a correctness bug the speed work exposed. Arm bodies were being typed
without binding their patterns, so `Self::Ansi(c) => c.as_fg_buffer()` could
not resolve -- `c` was not in scope. It looked like it worked because some
other arm happened to type. Arms are now entered through `pat_bind`, the same
machinery the real lowering uses, and the receiver is bound too: `match self`
is the whole body of several of these, and `self` is not a `FnArg::Typed`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>abf2c13 parent: e534fb9 modified
src/lower.rs +104 -61 | @@ -344,10 +344,12 @@ pub struct Lowerer { | ||
| 344 | 344 | /// Const generic parameters, by name, with their Nim type. Nim's `static` |
| 345 | 345 | /// parameter is the same thing: a generic parameter that is a value. |
| 346 | 346 | const_gens: HashMap<String, String>, |
| 347 | + /// Combined table size when `CONST_NAMES` was last rebuilt. | |
| 348 | + const_names_at: std::cell::Cell<Option<usize>>, | |
| 347 | 349 | /// Bodies of functions whose return type is a return-position `impl |
| 348 | 350 | /// Trait`, keyed the same way as `fns`/`methods`, so `resolve_opaque` can |
| 349 | 351 | /// read the concrete type back off the body. |
| 350 | - opaque_fns: Vec<(OpaqueKey, syn::Signature, syn::Block)>, | |
| 352 | + opaque_fns: Vec<(OpaqueKey, syn::Signature, syn::Block, Option<Nim>)>, | |
| 351 | 353 | /// Resolved opaque return types, keyed by the signature's source text. |
| 352 | 354 | /// `signature` is called again on the emission pass and has only the |
| 353 | 355 | /// signature to go on, so that is what the key is made of. |
| @@ -431,6 +433,7 @@ impl Lowerer { | ||
| 431 | 433 | local_items: HashMap::new(), |
| 432 | 434 | emitted_local: std::collections::HashSet::new(), |
| 433 | 435 | const_gens: HashMap::new(), |
| 436 | + const_names_at: std::cell::Cell::new(None), | |
| 434 | 437 | opaque_fns: Vec::new(), |
| 435 | 438 | opaque_ret: HashMap::new(), |
| 436 | 439 | opaque_hint: None, |
| @@ -817,6 +820,7 @@ impl Lowerer { | ||
| 817 | 820 | OpaqueKey::Free(self.cur_mod.clone(), name), |
| 818 | 821 | f.sig.clone(), |
| 819 | 822 | (*f.block).clone(), |
| 823 | + None, | |
| 820 | 824 | )); |
| 821 | 825 | } |
| 822 | 826 | } |
| @@ -1091,6 +1095,7 @@ impl Lowerer { | ||
| 1091 | 1095 | OpaqueKey::Method(tyname.clone(), mname), |
| 1092 | 1096 | m.sig.clone(), |
| 1093 | 1097 | m.block.clone(), |
| 1098 | + Some(self_ty.clone()), | |
| 1094 | 1099 | )); |
| 1095 | 1100 | } |
| 1096 | 1101 | } |
| @@ -1126,6 +1131,7 @@ impl Lowerer { | ||
| 1126 | 1131 | OpaqueKey::Method(tyname.clone(), m.sig.ident.to_string()), |
| 1127 | 1132 | m.sig.clone(), |
| 1128 | 1133 | m.block.clone(), |
| 1134 | + Some(self_ty.clone()), | |
| 1129 | 1135 | )); |
| 1130 | 1136 | } |
| 1131 | 1137 | } |
| @@ -1274,6 +1280,16 @@ impl Lowerer { | ||
| 1274 | 1280 | /// Publish the `Qualifier::NAME` constants an array length may refer to. |
| 1275 | 1281 | /// See `ty::CONST_NAMES` for why this is a side channel. |
| 1276 | 1282 | fn refill_const_names(&self) { |
| 1283 | + // `map_ty` is called constantly, and this walks every enum and every | |
| 1284 | + // associated const in the program. Both tables only ever grow, so | |
| 1285 | + // their combined size is enough to tell whether anything has been | |
| 1286 | + // added since the last refill -- without it, a 357-crate run spends | |
| 1287 | + // essentially all of its time rebuilding this map. | |
| 1288 | + let size = self.enums.len() + self.assoc_consts.len(); | |
| 1289 | + if self.const_names_at.get() == Some(size) { | |
| 1290 | + return; | |
| 1291 | + } | |
| 1292 | + self.const_names_at.set(Some(size)); | |
| 1277 | 1293 | ty::CONST_NAMES.with(|m| { |
| 1278 | 1294 | let mut m = m.borrow_mut(); |
| 1279 | 1295 | m.clear(); |
| @@ -1824,6 +1840,54 @@ impl Lowerer { | ||
| 1824 | 1840 | Ok(()) |
| 1825 | 1841 | } |
| 1826 | 1842 | |
| 1843 | + /// The type a result expression produces, looking through `match`, `if` | |
| 1844 | + /// and blocks. | |
| 1845 | + /// | |
| 1846 | + /// Rust gives every branch of these the same type, so the first branch | |
| 1847 | + /// that types answers for all of them. A `match` arm has to be entered | |
| 1848 | + /// properly rather than typed on its own: `Self::Ansi(c) => c.buf()` | |
| 1849 | + /// means nothing until `c` is bound, which is what `pat_bind` does for | |
| 1850 | + /// the real lowering too. | |
| 1851 | + fn result_ty(&mut self, e: &Expr) -> Option<Nim> { | |
| 1852 | + match e { | |
| 1853 | + Expr::Match(m) => { | |
| 1854 | + let scrut = self.expr(&m.expr).ok()?; | |
| 1855 | + let st = scrut.ty.clone()?; | |
| 1856 | + for arm in &m.arms { | |
| 1857 | + self.push_scope(); | |
| 1858 | + let bound = self.pat_bind(&arm.pat, &scrut.code, &st).is_ok(); | |
| 1859 | + let t = if bound { self.result_ty(&arm.body) } else { None }; | |
| 1860 | + self.pop_scope(); | |
| 1861 | + if t.is_some() { | |
| 1862 | + return t; | |
| 1863 | + } | |
| 1864 | + } | |
| 1865 | + None | |
| 1866 | + } | |
| 1867 | + Expr::If(i) => { | |
| 1868 | + let then = i.then_branch.stmts.last().and_then(|st| match st { | |
| 1869 | + Stmt::Expr(e, None) => Some(e.clone()), | |
| 1870 | + _ => None, | |
| 1871 | + }); | |
| 1872 | + if let Some(t) = then.and_then(|e| self.result_ty(&e)) { | |
| 1873 | + return Some(t); | |
| 1874 | + } | |
| 1875 | + let (_, els) = i.else_branch.as_ref()?; | |
| 1876 | + self.result_ty(els) | |
| 1877 | + } | |
| 1878 | + Expr::Block(b) => { | |
| 1879 | + let tail = b.block.stmts.last().and_then(|st| match st { | |
| 1880 | + Stmt::Expr(e, None) => Some(e.clone()), | |
| 1881 | + _ => None, | |
| 1882 | + })?; | |
| 1883 | + self.result_ty(&tail) | |
| 1884 | + } | |
| 1885 | + Expr::Paren(p) => self.result_ty(&p.expr), | |
| 1886 | + Expr::Group(g) => self.result_ty(&g.expr), | |
| 1887 | + other => self.expr(other).ok().and_then(|v| v.ty), | |
| 1888 | + } | |
| 1889 | + } | |
| 1890 | + | |
| 1827 | 1891 | /// Give every return-position `impl Trait` its concrete type, taken from |
| 1828 | 1892 | /// the body that produces it. |
| 1829 | 1893 | /// |
| @@ -1835,13 +1899,21 @@ impl Lowerer { | ||
| 1835 | 1899 | let opaque = Nim::Named(OPAQUE.into(), Vec::new()); |
| 1836 | 1900 | let pending = std::mem::take(&mut self.opaque_fns); |
| 1837 | 1901 | let mut left = pending.clone(); |
| 1838 | - // One function's opaque type may be another's; each round resolves at | |
| 1839 | - // least one or nothing more can be learned. | |
| 1840 | - for _ in 0..pending.len().max(1) { | |
| 1902 | + // One function's opaque type may be another's, so this iterates -- but | |
| 1903 | + // it is bounded rather than run to exhaustion. Each round lowers every | |
| 1904 | + // still-unresolved body, so a bound of `pending.len()` is quadratic, | |
| 1905 | + // and with several hundred opaque functions across a dependency graph | |
| 1906 | + // that is the difference between seconds and never finishing. | |
| 1907 | + // | |
| 1908 | + // The bound is a chain depth, not a count: `a` returning `b`'s result | |
| 1909 | + // returning `c`'s is three rounds. Chains that long are already | |
| 1910 | + // unusual, and anything deeper is reported rather than waited for. | |
| 1911 | + const MAX_ROUNDS: usize = 8; | |
| 1912 | + for _ in 0..MAX_ROUNDS.min(pending.len().max(1)) { | |
| 1841 | 1913 | let mut progressed = false; |
| 1842 | 1914 | let mut still = Vec::new(); |
| 1843 | - for (key, sig, block) in left { | |
| 1844 | - match self.opaque_of(&sig, &block) { | |
| 1915 | + for (key, sig, block, self_ty) in left { | |
| 1916 | + match self.opaque_of(&sig, &block, self_ty.as_ref()) { | |
| 1845 | 1917 | Some(t) if t != opaque => { |
| 1846 | 1918 | self.opaque_ret.insert(opaque_key(&sig, &block), t.clone()); |
| 1847 | 1919 | match &key { |
| @@ -1858,7 +1930,7 @@ impl Lowerer { | ||
| 1858 | 1930 | } |
| 1859 | 1931 | progressed = true; |
| 1860 | 1932 | } |
| 1861 | - _ => still.push((key, sig, block)), | |
| 1933 | + _ => still.push((key, sig, block, self_ty)), | |
| 1862 | 1934 | } |
| 1863 | 1935 | } |
| 1864 | 1936 | left = still; |
| @@ -1866,7 +1938,7 @@ impl Lowerer { | ||
| 1866 | 1938 | break; |
| 1867 | 1939 | } |
| 1868 | 1940 | } |
| 1869 | - if let Some((key, sig, _)) = left.first() { | |
| 1941 | + if let Some((key, sig, _, _)) = left.first() { | |
| 1870 | 1942 | let what = match key { |
| 1871 | 1943 | OpaqueKey::Free(_, n) => n.clone(), |
| 1872 | 1944 | OpaqueKey::Method(t, n) => format!("{t}::{n}"), |
| @@ -1886,22 +1958,30 @@ impl Lowerer { | ||
| 1886 | 1958 | } |
| 1887 | 1959 | |
| 1888 | 1960 | /// The type of what a body returns, lowered for real and then discarded. |
| 1889 | - fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { | |
| 1961 | + fn opaque_of( | |
| 1962 | + &mut self, | |
| 1963 | + sig: &syn::Signature, | |
| 1964 | + block: &syn::Block, | |
| 1965 | + self_ty: Option<&Nim>, | |
| 1966 | + ) -> Option<Nim> { | |
| 1890 | 1967 | let tail = block.stmts.iter().rev().find_map(|st| match st { |
| 1891 | 1968 | Stmt::Expr(e, None) => Some(e.clone()), |
| 1892 | 1969 | // `return e;` answers the same question. |
| 1893 | 1970 | Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(), |
| 1894 | 1971 | _ => None, |
| 1895 | 1972 | })?; |
| 1896 | - // A tail `match` or `if` produces one type across all its arms -- Rust | |
| 1897 | - // requires that -- so any arm that types is the answer. This is what | |
| 1898 | - // makes a body like `match self { A(c) => c.buf(), .. }` usable here | |
| 1899 | - // even though `match` in value position is not lowered. | |
| 1900 | - let cands = result_exprs(&tail); | |
| 1973 | + | |
| 1901 | 1974 | // Parameters have to be in scope, or every mention of one is an |
| 1902 | 1975 | // unknown name and the result types as nothing. |
| 1903 | 1976 | let (ptys, _) = self.signature(sig).ok()?; |
| 1904 | 1977 | self.push_scope(); |
| 1978 | + // The receiver too: `match self { .. }` is the whole body of several | |
| 1979 | + // of these, and `self` is not a `FnArg::Typed`. | |
| 1980 | + if let Some(t) = self_ty { | |
| 1981 | + self.bind("self", t.clone()); | |
| 1982 | + } | |
| 1983 | + let outer_self = self.self_ty.clone(); | |
| 1984 | + self.self_ty = self_ty.cloned().or(outer_self.clone()); | |
| 1905 | 1985 | let mut i = 0usize; |
| 1906 | 1986 | for a in &sig.inputs { |
| 1907 | 1987 | if let FnArg::Typed(t) = a { |
| @@ -1913,20 +1993,19 @@ impl Lowerer { | ||
| 1913 | 1993 | i += 1; |
| 1914 | 1994 | } |
| 1915 | 1995 | } |
| 1916 | - let saved_out = self.out.clone(); | |
| 1996 | + // Truncated back rather than cloned and restored: `out` is the whole | |
| 1997 | + // translated program, megabytes of it by the time this runs, and | |
| 1998 | + // copying it once per candidate expression per round was the cost | |
| 1999 | + // that made a 357-crate run never finish. | |
| 2000 | + let saved_out = self.out.len(); | |
| 1917 | 2001 | let saved_indent = self.indent; |
| 1918 | - let saved_hoisted = self.hoisted.clone(); | |
| 1919 | - let mut found = None; | |
| 1920 | - for c in &cands { | |
| 1921 | - if let Some(t) = self.expr(c).ok().and_then(|v| v.ty) { | |
| 1922 | - found = Some(t); | |
| 1923 | - break; | |
| 1924 | - } | |
| 1925 | - } | |
| 1926 | - self.out = saved_out; | |
| 2002 | + let saved_hoisted = self.hoisted.len(); | |
| 2003 | + let found = self.result_ty(&tail); | |
| 2004 | + self.out.truncate(saved_out); | |
| 1927 | 2005 | self.indent = saved_indent; |
| 1928 | - self.hoisted = saved_hoisted; | |
| 2006 | + self.hoisted.truncate(saved_hoisted); | |
| 1929 | 2007 | self.pop_scope(); |
| 2008 | + self.self_ty = outer_self; | |
| 1930 | 2009 | found |
| 1931 | 2010 | } |
| 1932 | 2011 | |
| @@ -7728,42 +7807,6 @@ fn opaque_key(sig: &syn::Signature, block: &syn::Block) -> String { | ||
| 7728 | 7807 | ) |
| 7729 | 7808 | } |
| 7730 | 7809 | |
| 7731 | -/// The expressions a body can produce its result from, flattened through | |
| 7732 | -/// `match`, `if` and blocks. Rust gives every branch the same type, so any one | |
| 7733 | -/// of them that rustnim can type answers for all of them. | |
| 7734 | -fn result_exprs(e: &Expr) -> Vec<Expr> { | |
| 7735 | - match e { | |
| 7736 | - Expr::Match(m) => m.arms.iter().flat_map(|a| result_exprs(&a.body)).collect(), | |
| 7737 | - Expr::If(i) => { | |
| 7738 | - let mut v: Vec<Expr> = i | |
| 7739 | - .then_branch | |
| 7740 | - .stmts | |
| 7741 | - .last() | |
| 7742 | - .and_then(|s| match s { | |
| 7743 | - Stmt::Expr(e, None) => Some(result_exprs(e)), | |
| 7744 | - _ => None, | |
| 7745 | - }) | |
| 7746 | - .unwrap_or_default(); | |
| 7747 | - if let Some((_, els)) = &i.else_branch { | |
| 7748 | - v.extend(result_exprs(els)); | |
| 7749 | - } | |
| 7750 | - v | |
| 7751 | - } | |
| 7752 | - Expr::Block(b) => b | |
| 7753 | - .block | |
| 7754 | - .stmts | |
| 7755 | - .last() | |
| 7756 | - .and_then(|s| match s { | |
| 7757 | - Stmt::Expr(e, None) => Some(result_exprs(e)), | |
| 7758 | - _ => None, | |
| 7759 | - }) | |
| 7760 | - .unwrap_or_default(), | |
| 7761 | - Expr::Paren(p) => result_exprs(&p.expr), | |
| 7762 | - Expr::Group(g) => result_exprs(&g.expr), | |
| 7763 | - other => vec![other.clone()], | |
| 7764 | - } | |
| 7765 | -} | |
| 7766 | - | |
| 7767 | 7810 | /// Whether a type contains an `impl Trait` anywhere inside it. |
| 7768 | 7811 | fn contains_impl_trait(t: &syn::Type) -> bool { |
| 7769 | 7812 | use syn::Type; |
| @@ -344,10 +344,12 @@ pub struct Lowerer { | |||
| 344 | /// Const generic parameters, by name, with their Nim type. Nim's `static` | 344 | /// Const generic parameters, by name, with their Nim type. Nim's `static` |
| 345 | /// parameter is the same thing: a generic parameter that is a value. | 345 | /// parameter is the same thing: a generic parameter that is a value. |
| 346 | const_gens: HashMap<String, String>, | 346 | const_gens: HashMap<String, String>, |
| 347 | + /// Combined table size when `CONST_NAMES` was last rebuilt. | ||
| 348 | + const_names_at: std::cell::Cell<Option<usize>>, | ||
| 347 | /// Bodies of functions whose return type is a return-position `impl | 349 | /// Bodies of functions whose return type is a return-position `impl |
| 348 | /// Trait`, keyed the same way as `fns`/`methods`, so `resolve_opaque` can | 350 | /// Trait`, keyed the same way as `fns`/`methods`, so `resolve_opaque` can |
| 349 | /// read the concrete type back off the body. | 351 | /// read the concrete type back off the body. |
| 350 | - opaque_fns: Vec<(OpaqueKey, syn::Signature, syn::Block)>, | 352 | + opaque_fns: Vec<(OpaqueKey, syn::Signature, syn::Block, Option<Nim>)>, |
| 351 | /// Resolved opaque return types, keyed by the signature's source text. | 353 | /// Resolved opaque return types, keyed by the signature's source text. |
| 352 | /// `signature` is called again on the emission pass and has only the | 354 | /// `signature` is called again on the emission pass and has only the |
| 353 | /// signature to go on, so that is what the key is made of. | 355 | /// signature to go on, so that is what the key is made of. |
| @@ -431,6 +433,7 @@ impl Lowerer { | |||
| 431 | local_items: HashMap::new(), | 433 | local_items: HashMap::new(), |
| 432 | emitted_local: std::collections::HashSet::new(), | 434 | emitted_local: std::collections::HashSet::new(), |
| 433 | const_gens: HashMap::new(), | 435 | const_gens: HashMap::new(), |
| 436 | + const_names_at: std::cell::Cell::new(None), | ||
| 434 | opaque_fns: Vec::new(), | 437 | opaque_fns: Vec::new(), |
| 435 | opaque_ret: HashMap::new(), | 438 | opaque_ret: HashMap::new(), |
| 436 | opaque_hint: None, | 439 | opaque_hint: None, |
| @@ -817,6 +820,7 @@ impl Lowerer { | |||
| 817 | OpaqueKey::Free(self.cur_mod.clone(), name), | 820 | OpaqueKey::Free(self.cur_mod.clone(), name), |
| 818 | f.sig.clone(), | 821 | f.sig.clone(), |
| 819 | (*f.block).clone(), | 822 | (*f.block).clone(), |
| 823 | + None, | ||
| 820 | )); | 824 | )); |
| 821 | } | 825 | } |
| 822 | } | 826 | } |
| @@ -1091,6 +1095,7 @@ impl Lowerer { | |||
| 1091 | OpaqueKey::Method(tyname.clone(), mname), | 1095 | OpaqueKey::Method(tyname.clone(), mname), |
| 1092 | m.sig.clone(), | 1096 | m.sig.clone(), |
| 1093 | m.block.clone(), | 1097 | m.block.clone(), |
| 1098 | + Some(self_ty.clone()), | ||
| 1094 | )); | 1099 | )); |
| 1095 | } | 1100 | } |
| 1096 | } | 1101 | } |
| @@ -1126,6 +1131,7 @@ impl Lowerer { | |||
| 1126 | OpaqueKey::Method(tyname.clone(), m.sig.ident.to_string()), | 1131 | OpaqueKey::Method(tyname.clone(), m.sig.ident.to_string()), |
| 1127 | m.sig.clone(), | 1132 | m.sig.clone(), |
| 1128 | m.block.clone(), | 1133 | m.block.clone(), |
| 1134 | + Some(self_ty.clone()), | ||
| 1129 | )); | 1135 | )); |
| 1130 | } | 1136 | } |
| 1131 | } | 1137 | } |
| @@ -1274,6 +1280,16 @@ impl Lowerer { | |||
| 1274 | /// Publish the `Qualifier::NAME` constants an array length may refer to. | 1280 | /// Publish the `Qualifier::NAME` constants an array length may refer to. |
| 1275 | /// See `ty::CONST_NAMES` for why this is a side channel. | 1281 | /// See `ty::CONST_NAMES` for why this is a side channel. |
| 1276 | fn refill_const_names(&self) { | 1282 | fn refill_const_names(&self) { |
| 1283 | + // `map_ty` is called constantly, and this walks every enum and every | ||
| 1284 | + // associated const in the program. Both tables only ever grow, so | ||
| 1285 | + // their combined size is enough to tell whether anything has been | ||
| 1286 | + // added since the last refill -- without it, a 357-crate run spends | ||
| 1287 | + // essentially all of its time rebuilding this map. | ||
| 1288 | + let size = self.enums.len() + self.assoc_consts.len(); | ||
| 1289 | + if self.const_names_at.get() == Some(size) { | ||
| 1290 | + return; | ||
| 1291 | + } | ||
| 1292 | + self.const_names_at.set(Some(size)); | ||
| 1277 | ty::CONST_NAMES.with(|m| { | 1293 | ty::CONST_NAMES.with(|m| { |
| 1278 | let mut m = m.borrow_mut(); | 1294 | let mut m = m.borrow_mut(); |
| 1279 | m.clear(); | 1295 | m.clear(); |
| @@ -1824,6 +1840,54 @@ impl Lowerer { | |||
| 1824 | Ok(()) | 1840 | Ok(()) |
| 1825 | } | 1841 | } |
| 1826 | 1842 | ||
| 1843 | + /// The type a result expression produces, looking through `match`, `if` | ||
| 1844 | + /// and blocks. | ||
| 1845 | + /// | ||
| 1846 | + /// Rust gives every branch of these the same type, so the first branch | ||
| 1847 | + /// that types answers for all of them. A `match` arm has to be entered | ||
| 1848 | + /// properly rather than typed on its own: `Self::Ansi(c) => c.buf()` | ||
| 1849 | + /// means nothing until `c` is bound, which is what `pat_bind` does for | ||
| 1850 | + /// the real lowering too. | ||
| 1851 | + fn result_ty(&mut self, e: &Expr) -> Option<Nim> { | ||
| 1852 | + match e { | ||
| 1853 | + Expr::Match(m) => { | ||
| 1854 | + let scrut = self.expr(&m.expr).ok()?; | ||
| 1855 | + let st = scrut.ty.clone()?; | ||
| 1856 | + for arm in &m.arms { | ||
| 1857 | + self.push_scope(); | ||
| 1858 | + let bound = self.pat_bind(&arm.pat, &scrut.code, &st).is_ok(); | ||
| 1859 | + let t = if bound { self.result_ty(&arm.body) } else { None }; | ||
| 1860 | + self.pop_scope(); | ||
| 1861 | + if t.is_some() { | ||
| 1862 | + return t; | ||
| 1863 | + } | ||
| 1864 | + } | ||
| 1865 | + None | ||
| 1866 | + } | ||
| 1867 | + Expr::If(i) => { | ||
| 1868 | + let then = i.then_branch.stmts.last().and_then(|st| match st { | ||
| 1869 | + Stmt::Expr(e, None) => Some(e.clone()), | ||
| 1870 | + _ => None, | ||
| 1871 | + }); | ||
| 1872 | + if let Some(t) = then.and_then(|e| self.result_ty(&e)) { | ||
| 1873 | + return Some(t); | ||
| 1874 | + } | ||
| 1875 | + let (_, els) = i.else_branch.as_ref()?; | ||
| 1876 | + self.result_ty(els) | ||
| 1877 | + } | ||
| 1878 | + Expr::Block(b) => { | ||
| 1879 | + let tail = b.block.stmts.last().and_then(|st| match st { | ||
| 1880 | + Stmt::Expr(e, None) => Some(e.clone()), | ||
| 1881 | + _ => None, | ||
| 1882 | + })?; | ||
| 1883 | + self.result_ty(&tail) | ||
| 1884 | + } | ||
| 1885 | + Expr::Paren(p) => self.result_ty(&p.expr), | ||
| 1886 | + Expr::Group(g) => self.result_ty(&g.expr), | ||
| 1887 | + other => self.expr(other).ok().and_then(|v| v.ty), | ||
| 1888 | + } | ||
| 1889 | + } | ||
| 1890 | + | ||
| 1827 | /// Give every return-position `impl Trait` its concrete type, taken from | 1891 | /// Give every return-position `impl Trait` its concrete type, taken from |
| 1828 | /// the body that produces it. | 1892 | /// the body that produces it. |
| 1829 | /// | 1893 | /// |
| @@ -1835,13 +1899,21 @@ impl Lowerer { | |||
| 1835 | let opaque = Nim::Named(OPAQUE.into(), Vec::new()); | 1899 | let opaque = Nim::Named(OPAQUE.into(), Vec::new()); |
| 1836 | let pending = std::mem::take(&mut self.opaque_fns); | 1900 | let pending = std::mem::take(&mut self.opaque_fns); |
| 1837 | let mut left = pending.clone(); | 1901 | let mut left = pending.clone(); |
| 1838 | - // One function's opaque type may be another's; each round resolves at | 1902 | + // One function's opaque type may be another's, so this iterates -- but |
| 1839 | - // least one or nothing more can be learned. | 1903 | + // it is bounded rather than run to exhaustion. Each round lowers every |
| 1840 | - for _ in 0..pending.len().max(1) { | 1904 | + // still-unresolved body, so a bound of `pending.len()` is quadratic, |
| 1905 | + // and with several hundred opaque functions across a dependency graph | ||
| 1906 | + // that is the difference between seconds and never finishing. | ||
| 1907 | + // | ||
| 1908 | + // The bound is a chain depth, not a count: `a` returning `b`'s result | ||
| 1909 | + // returning `c`'s is three rounds. Chains that long are already | ||
| 1910 | + // unusual, and anything deeper is reported rather than waited for. | ||
| 1911 | + const MAX_ROUNDS: usize = 8; | ||
| 1912 | + for _ in 0..MAX_ROUNDS.min(pending.len().max(1)) { | ||
| 1841 | let mut progressed = false; | 1913 | let mut progressed = false; |
| 1842 | let mut still = Vec::new(); | 1914 | let mut still = Vec::new(); |
| 1843 | - for (key, sig, block) in left { | 1915 | + for (key, sig, block, self_ty) in left { |
| 1844 | - match self.opaque_of(&sig, &block) { | 1916 | + match self.opaque_of(&sig, &block, self_ty.as_ref()) { |
| 1845 | Some(t) if t != opaque => { | 1917 | Some(t) if t != opaque => { |
| 1846 | self.opaque_ret.insert(opaque_key(&sig, &block), t.clone()); | 1918 | self.opaque_ret.insert(opaque_key(&sig, &block), t.clone()); |
| 1847 | match &key { | 1919 | match &key { |
| @@ -1858,7 +1930,7 @@ impl Lowerer { | |||
| 1858 | } | 1930 | } |
| 1859 | progressed = true; | 1931 | progressed = true; |
| 1860 | } | 1932 | } |
| 1861 | - _ => still.push((key, sig, block)), | 1933 | + _ => still.push((key, sig, block, self_ty)), |
| 1862 | } | 1934 | } |
| 1863 | } | 1935 | } |
| 1864 | left = still; | 1936 | left = still; |
| @@ -1866,7 +1938,7 @@ impl Lowerer { | |||
| 1866 | break; | 1938 | break; |
| 1867 | } | 1939 | } |
| 1868 | } | 1940 | } |
| 1869 | - if let Some((key, sig, _)) = left.first() { | 1941 | + if let Some((key, sig, _, _)) = left.first() { |
| 1870 | let what = match key { | 1942 | let what = match key { |
| 1871 | OpaqueKey::Free(_, n) => n.clone(), | 1943 | OpaqueKey::Free(_, n) => n.clone(), |
| 1872 | OpaqueKey::Method(t, n) => format!("{t}::{n}"), | 1944 | OpaqueKey::Method(t, n) => format!("{t}::{n}"), |
| @@ -1886,22 +1958,30 @@ impl Lowerer { | |||
| 1886 | } | 1958 | } |
| 1887 | 1959 | ||
| 1888 | /// The type of what a body returns, lowered for real and then discarded. | 1960 | /// The type of what a body returns, lowered for real and then discarded. |
| 1889 | - fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { | 1961 | + fn opaque_of( |
| 1962 | + &mut self, | ||
| 1963 | + sig: &syn::Signature, | ||
| 1964 | + block: &syn::Block, | ||
| 1965 | + self_ty: Option<&Nim>, | ||
| 1966 | + ) -> Option<Nim> { | ||
| 1890 | let tail = block.stmts.iter().rev().find_map(|st| match st { | 1967 | let tail = block.stmts.iter().rev().find_map(|st| match st { |
| 1891 | Stmt::Expr(e, None) => Some(e.clone()), | 1968 | Stmt::Expr(e, None) => Some(e.clone()), |
| 1892 | // `return e;` answers the same question. | 1969 | // `return e;` answers the same question. |
| 1893 | Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(), | 1970 | Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(), |
| 1894 | _ => None, | 1971 | _ => None, |
| 1895 | })?; | 1972 | })?; |
| 1896 | - // A tail `match` or `if` produces one type across all its arms -- Rust | 1973 | + |
| 1897 | - // requires that -- so any arm that types is the answer. This is what | ||
| 1898 | - // makes a body like `match self { A(c) => c.buf(), .. }` usable here | ||
| 1899 | - // even though `match` in value position is not lowered. | ||
| 1900 | - let cands = result_exprs(&tail); | ||
| 1901 | // Parameters have to be in scope, or every mention of one is an | 1974 | // Parameters have to be in scope, or every mention of one is an |
| 1902 | // unknown name and the result types as nothing. | 1975 | // unknown name and the result types as nothing. |
| 1903 | let (ptys, _) = self.signature(sig).ok()?; | 1976 | let (ptys, _) = self.signature(sig).ok()?; |
| 1904 | self.push_scope(); | 1977 | self.push_scope(); |
| 1978 | + // The receiver too: `match self { .. }` is the whole body of several | ||
| 1979 | + // of these, and `self` is not a `FnArg::Typed`. | ||
| 1980 | + if let Some(t) = self_ty { | ||
| 1981 | + self.bind("self", t.clone()); | ||
| 1982 | + } | ||
| 1983 | + let outer_self = self.self_ty.clone(); | ||
| 1984 | + self.self_ty = self_ty.cloned().or(outer_self.clone()); | ||
| 1905 | let mut i = 0usize; | 1985 | let mut i = 0usize; |
| 1906 | for a in &sig.inputs { | 1986 | for a in &sig.inputs { |
| 1907 | if let FnArg::Typed(t) = a { | 1987 | if let FnArg::Typed(t) = a { |
| @@ -1913,20 +1993,19 @@ impl Lowerer { | |||
| 1913 | i += 1; | 1993 | i += 1; |
| 1914 | } | 1994 | } |
| 1915 | } | 1995 | } |
| 1916 | - let saved_out = self.out.clone(); | 1996 | + // Truncated back rather than cloned and restored: `out` is the whole |
| 1997 | + // translated program, megabytes of it by the time this runs, and | ||
| 1998 | + // copying it once per candidate expression per round was the cost | ||
| 1999 | + // that made a 357-crate run never finish. | ||
| 2000 | + let saved_out = self.out.len(); | ||
| 1917 | let saved_indent = self.indent; | 2001 | let saved_indent = self.indent; |
| 1918 | - let saved_hoisted = self.hoisted.clone(); | 2002 | + let saved_hoisted = self.hoisted.len(); |
| 1919 | - let mut found = None; | 2003 | + let found = self.result_ty(&tail); |
| 1920 | - for c in &cands { | 2004 | + self.out.truncate(saved_out); |
| 1921 | - if let Some(t) = self.expr(c).ok().and_then(|v| v.ty) { | ||
| 1922 | - found = Some(t); | ||
| 1923 | - break; | ||
| 1924 | - } | ||
| 1925 | - } | ||
| 1926 | - self.out = saved_out; | ||
| 1927 | self.indent = saved_indent; | 2005 | self.indent = saved_indent; |
| 1928 | - self.hoisted = saved_hoisted; | 2006 | + self.hoisted.truncate(saved_hoisted); |
| 1929 | self.pop_scope(); | 2007 | self.pop_scope(); |
| 2008 | + self.self_ty = outer_self; | ||
| 1930 | found | 2009 | found |
| 1931 | } | 2010 | } |
| 1932 | 2011 | ||
| @@ -7728,42 +7807,6 @@ fn opaque_key(sig: &syn::Signature, block: &syn::Block) -> String { | |||
| 7728 | ) | 7807 | ) |
| 7729 | } | 7808 | } |
| 7730 | 7809 | ||
| 7731 | -/// The expressions a body can produce its result from, flattened through | ||
| 7732 | -/// `match`, `if` and blocks. Rust gives every branch the same type, so any one | ||
| 7733 | -/// of them that rustnim can type answers for all of them. | ||
| 7734 | -fn result_exprs(e: &Expr) -> Vec<Expr> { | ||
| 7735 | - match e { | ||
| 7736 | - Expr::Match(m) => m.arms.iter().flat_map(|a| result_exprs(&a.body)).collect(), | ||
| 7737 | - Expr::If(i) => { | ||
| 7738 | - let mut v: Vec<Expr> = i | ||
| 7739 | - .then_branch | ||
| 7740 | - .stmts | ||
| 7741 | - .last() | ||
| 7742 | - .and_then(|s| match s { | ||
| 7743 | - Stmt::Expr(e, None) => Some(result_exprs(e)), | ||
| 7744 | - _ => None, | ||
| 7745 | - }) | ||
| 7746 | - .unwrap_or_default(); | ||
| 7747 | - if let Some((_, els)) = &i.else_branch { | ||
| 7748 | - v.extend(result_exprs(els)); | ||
| 7749 | - } | ||
| 7750 | - v | ||
| 7751 | - } | ||
| 7752 | - Expr::Block(b) => b | ||
| 7753 | - .block | ||
| 7754 | - .stmts | ||
| 7755 | - .last() | ||
| 7756 | - .and_then(|s| match s { | ||
| 7757 | - Stmt::Expr(e, None) => Some(result_exprs(e)), | ||
| 7758 | - _ => None, | ||
| 7759 | - }) | ||
| 7760 | - .unwrap_or_default(), | ||
| 7761 | - Expr::Paren(p) => result_exprs(&p.expr), | ||
| 7762 | - Expr::Group(g) => result_exprs(&g.expr), | ||
| 7763 | - other => vec![other.clone()], | ||
| 7764 | - } | ||
| 7765 | -} | ||
| 7766 | - | ||
| 7767 | /// Whether a type contains an `impl Trait` anywhere inside it. | 7810 | /// Whether a type contains an `impl Trait` anywhere inside it. |
| 7768 | fn contains_impl_trait(t: &syn::Type) -> bool { | 7811 | fn contains_impl_trait(t: &syn::Type) -> bool { |
| 7769 | use syn::Type; | 7812 | use syn::Type; |