Read an opaque return type through `match`, `if` and `return`
`anstyle`'s `fn render_fg(self) -> impl Display + Copy` is a tail `match`, and `match` in value position is not lowered, so the concrete type could not be read off the body. It does not need to be lowered to be read. Rust gives every arm of a `match` the same type, so any single arm that rustnim can type answers for all of them -- that is a consequence of Rust's own rule, not a sampling heuristic. Same for `if`/`else`, blocks, and an explicit `return`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6c57571 parent: 8594af4 modified
src/lower.rs +51 -2 | @@ -1782,8 +1782,15 @@ impl Lowerer { | ||
| 1782 | 1782 | fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { |
| 1783 | 1783 | let tail = block.stmts.iter().rev().find_map(|st| match st { |
| 1784 | 1784 | Stmt::Expr(e, None) => Some(e.clone()), |
| 1785 | + // `return e;` answers the same question. | |
| 1786 | + Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(), | |
| 1785 | 1787 | _ => None, |
| 1786 | 1788 | })?; |
| 1789 | + // A tail `match` or `if` produces one type across all its arms -- Rust | |
| 1790 | + // requires that -- so any arm that types is the answer. This is what | |
| 1791 | + // makes a body like `match self { A(c) => c.buf(), .. }` usable here | |
| 1792 | + // even though `match` in value position is not lowered. | |
| 1793 | + let cands = result_exprs(&tail); | |
| 1787 | 1794 | // Parameters have to be in scope, or every mention of one is an |
| 1788 | 1795 | // unknown name and the result types as nothing. |
| 1789 | 1796 | let (ptys, _) = self.signature(sig).ok()?; |
| @@ -1802,12 +1809,18 @@ impl Lowerer { | ||
| 1802 | 1809 | let saved_out = self.out.clone(); |
| 1803 | 1810 | let saved_indent = self.indent; |
| 1804 | 1811 | let saved_hoisted = self.hoisted.clone(); |
| 1805 | - let r = self.expr(&tail); | |
| 1812 | + let mut found = None; | |
| 1813 | + for c in &cands { | |
| 1814 | + if let Some(t) = self.expr(c).ok().and_then(|v| v.ty) { | |
| 1815 | + found = Some(t); | |
| 1816 | + break; | |
| 1817 | + } | |
| 1818 | + } | |
| 1806 | 1819 | self.out = saved_out; |
| 1807 | 1820 | self.indent = saved_indent; |
| 1808 | 1821 | self.hoisted = saved_hoisted; |
| 1809 | 1822 | self.pop_scope(); |
| 1810 | - r.ok().and_then(|v| v.ty) | |
| 1823 | + found | |
| 1811 | 1824 | } |
| 1812 | 1825 | |
| 1813 | 1826 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| @@ -7606,3 +7619,39 @@ fn opaque_key(sig: &syn::Signature, block: &syn::Block) -> String { | ||
| 7606 | 7619 | quote::quote!(#block) |
| 7607 | 7620 | ) |
| 7608 | 7621 | } |
| 7622 | + | |
| 7623 | +/// The expressions a body can produce its result from, flattened through | |
| 7624 | +/// `match`, `if` and blocks. Rust gives every branch the same type, so any one | |
| 7625 | +/// of them that rustnim can type answers for all of them. | |
| 7626 | +fn result_exprs(e: &Expr) -> Vec<Expr> { | |
| 7627 | + match e { | |
| 7628 | + Expr::Match(m) => m.arms.iter().flat_map(|a| result_exprs(&a.body)).collect(), | |
| 7629 | + Expr::If(i) => { | |
| 7630 | + let mut v: Vec<Expr> = i | |
| 7631 | + .then_branch | |
| 7632 | + .stmts | |
| 7633 | + .last() | |
| 7634 | + .and_then(|s| match s { | |
| 7635 | + Stmt::Expr(e, None) => Some(result_exprs(e)), | |
| 7636 | + _ => None, | |
| 7637 | + }) | |
| 7638 | + .unwrap_or_default(); | |
| 7639 | + if let Some((_, els)) = &i.else_branch { | |
| 7640 | + v.extend(result_exprs(els)); | |
| 7641 | + } | |
| 7642 | + v | |
| 7643 | + } | |
| 7644 | + Expr::Block(b) => b | |
| 7645 | + .block | |
| 7646 | + .stmts | |
| 7647 | + .last() | |
| 7648 | + .and_then(|s| match s { | |
| 7649 | + Stmt::Expr(e, None) => Some(result_exprs(e)), | |
| 7650 | + _ => None, | |
| 7651 | + }) | |
| 7652 | + .unwrap_or_default(), | |
| 7653 | + Expr::Paren(p) => result_exprs(&p.expr), | |
| 7654 | + Expr::Group(g) => result_exprs(&g.expr), | |
| 7655 | + other => vec![other.clone()], | |
| 7656 | + } | |
| 7657 | +} | |
| @@ -1782,8 +1782,15 @@ impl Lowerer { | |||
| 1782 | fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { | 1782 | fn opaque_of(&mut self, sig: &syn::Signature, block: &syn::Block) -> Option<Nim> { |
| 1783 | let tail = block.stmts.iter().rev().find_map(|st| match st { | 1783 | let tail = block.stmts.iter().rev().find_map(|st| match st { |
| 1784 | Stmt::Expr(e, None) => Some(e.clone()), | 1784 | Stmt::Expr(e, None) => Some(e.clone()), |
| 1785 | + // `return e;` answers the same question. | ||
| 1786 | + Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(), | ||
| 1785 | _ => None, | 1787 | _ => None, |
| 1786 | })?; | 1788 | })?; |
| 1789 | + // A tail `match` or `if` produces one type across all its arms -- Rust | ||
| 1790 | + // requires that -- so any arm that types is the answer. This is what | ||
| 1791 | + // makes a body like `match self { A(c) => c.buf(), .. }` usable here | ||
| 1792 | + // even though `match` in value position is not lowered. | ||
| 1793 | + let cands = result_exprs(&tail); | ||
| 1787 | // Parameters have to be in scope, or every mention of one is an | 1794 | // Parameters have to be in scope, or every mention of one is an |
| 1788 | // unknown name and the result types as nothing. | 1795 | // unknown name and the result types as nothing. |
| 1789 | let (ptys, _) = self.signature(sig).ok()?; | 1796 | let (ptys, _) = self.signature(sig).ok()?; |
| @@ -1802,12 +1809,18 @@ impl Lowerer { | |||
| 1802 | let saved_out = self.out.clone(); | 1809 | let saved_out = self.out.clone(); |
| 1803 | let saved_indent = self.indent; | 1810 | let saved_indent = self.indent; |
| 1804 | let saved_hoisted = self.hoisted.clone(); | 1811 | let saved_hoisted = self.hoisted.clone(); |
| 1805 | - let r = self.expr(&tail); | 1812 | + let mut found = None; |
| 1813 | + for c in &cands { | ||
| 1814 | + if let Some(t) = self.expr(c).ok().and_then(|v| v.ty) { | ||
| 1815 | + found = Some(t); | ||
| 1816 | + break; | ||
| 1817 | + } | ||
| 1818 | + } | ||
| 1806 | self.out = saved_out; | 1819 | self.out = saved_out; |
| 1807 | self.indent = saved_indent; | 1820 | self.indent = saved_indent; |
| 1808 | self.hoisted = saved_hoisted; | 1821 | self.hoisted = saved_hoisted; |
| 1809 | self.pop_scope(); | 1822 | self.pop_scope(); |
| 1810 | - r.ok().and_then(|v| v.ty) | 1823 | + found |
| 1811 | } | 1824 | } |
| 1812 | 1825 | ||
| 1813 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { | 1826 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| @@ -7606,3 +7619,39 @@ fn opaque_key(sig: &syn::Signature, block: &syn::Block) -> String { | |||
| 7606 | quote::quote!(#block) | 7619 | quote::quote!(#block) |
| 7607 | ) | 7620 | ) |
| 7608 | } | 7621 | } |
| 7622 | + | ||
| 7623 | +/// The expressions a body can produce its result from, flattened through | ||
| 7624 | +/// `match`, `if` and blocks. Rust gives every branch the same type, so any one | ||
| 7625 | +/// of them that rustnim can type answers for all of them. | ||
| 7626 | +fn result_exprs(e: &Expr) -> Vec<Expr> { | ||
| 7627 | + match e { | ||
| 7628 | + Expr::Match(m) => m.arms.iter().flat_map(|a| result_exprs(&a.body)).collect(), | ||
| 7629 | + Expr::If(i) => { | ||
| 7630 | + let mut v: Vec<Expr> = i | ||
| 7631 | + .then_branch | ||
| 7632 | + .stmts | ||
| 7633 | + .last() | ||
| 7634 | + .and_then(|s| match s { | ||
| 7635 | + Stmt::Expr(e, None) => Some(result_exprs(e)), | ||
| 7636 | + _ => None, | ||
| 7637 | + }) | ||
| 7638 | + .unwrap_or_default(); | ||
| 7639 | + if let Some((_, els)) = &i.else_branch { | ||
| 7640 | + v.extend(result_exprs(els)); | ||
| 7641 | + } | ||
| 7642 | + v | ||
| 7643 | + } | ||
| 7644 | + Expr::Block(b) => b | ||
| 7645 | + .block | ||
| 7646 | + .stmts | ||
| 7647 | + .last() | ||
| 7648 | + .and_then(|s| match s { | ||
| 7649 | + Stmt::Expr(e, None) => Some(result_exprs(e)), | ||
| 7650 | + _ => None, | ||
| 7651 | + }) | ||
| 7652 | + .unwrap_or_default(), | ||
| 7653 | + Expr::Paren(p) => result_exprs(&p.expr), | ||
| 7654 | + Expr::Group(g) => result_exprs(&g.expr), | ||
| 7655 | + other => vec![other.clone()], | ||
| 7656 | + } | ||
| 7657 | +} | ||
modified
tests/cases/056-return-impl-trait.rs +11 -0 | @@ -28,8 +28,19 @@ impl Wrap { | ||
| 28 | 28 | } |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | +// The body's result reached through a `match`: Rust gives every arm the same | |
| 32 | +// type, so any arm answers for all of them. | |
| 33 | +fn pick(n: u32) -> impl Display { | |
| 34 | + match n { | |
| 35 | + 0 => Tag { n: 100 }, | |
| 36 | + 1 => Tag { n: 200 }, | |
| 37 | + _ => Tag { n: 300 }, | |
| 38 | + } | |
| 39 | +} | |
| 40 | + | |
| 31 | 41 | fn main() { |
| 32 | 42 | println!("{}", make(3)); |
| 33 | 43 | println!("{}", relay(3)); |
| 34 | 44 | println!("{}", Wrap.build(9)); |
| 45 | + println!("{} {}", pick(0), pick(7)); | |
| 35 | 46 | } |
| @@ -28,8 +28,19 @@ impl Wrap { | |||
| 28 | } | 28 | } |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | +// The body's result reached through a `match`: Rust gives every arm the same | ||
| 32 | +// type, so any arm answers for all of them. | ||
| 33 | +fn pick(n: u32) -> impl Display { | ||
| 34 | + match n { | ||
| 35 | + 0 => Tag { n: 100 }, | ||
| 36 | + 1 => Tag { n: 200 }, | ||
| 37 | + _ => Tag { n: 300 }, | ||
| 38 | + } | ||
| 39 | +} | ||
| 40 | + | ||
| 31 | fn main() { | 41 | fn main() { |
| 32 | println!("{}", make(3)); | 42 | println!("{}", make(3)); |
| 33 | println!("{}", relay(3)); | 43 | println!("{}", relay(3)); |
| 34 | println!("{}", Wrap.build(9)); | 44 | println!("{}", Wrap.build(9)); |
| 45 | + println!("{} {}", pick(0), pick(7)); | ||
| 35 | } | 46 | } |