nandi/rustnimpublic Fork
⑂ 283afd5
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Report opaque-type failures by cause, not just by name

The report listed the first twenty functions, and I read that truncated list
as the whole picture more than once -- twenty of thirty-two, and twenty of
seventy-one, both taken as complete. It now leads with a histogram over every
one of them, and labels the sample as a sample.

Two cases that reached the list without a reason now carry one: a body with no
trailing expression to read a type from, and a signature that does not map.
And a body that lowers without complaint and still yields no type says exactly
that, which is a different thing from a refusal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T20:03:17-07:00 Browse files
283afd5 parent: 7010b5d
modified src/lower.rs +42 -13
@@ -2057,9 +2057,16 @@ impl Lowerer {
20572057 fn result_ty(&mut self, e: &Expr) -> Option<Nim> {
20582058 let r = self.result_ty_inner(e);
20592059 if r.is_none() && self.opaque_why.is_none() {
2060- if let Err(msg) = self.expr(e) {
2061- self.opaque_why = Some(msg);
2062- }
2060+ self.opaque_why = Some(match self.expr(e) {
2061+ Err(msg) => msg,
2062+ // Lowered without complaint and still untyped. A different
2063+ // thing from a refusal, and worth saying so: nothing is
2064+ // missing from the lowering, it just has no type to report.
2065+ Ok(_) => format!(
2066+ "the body lowers but yields no type: `{}`",
2067+ snippet(&quote::quote!(#e).to_string())
2068+ ),
2069+ });
20632070 }
20642071 r
20652072 }
@@ -2188,19 +2195,31 @@ impl Lowerer {
21882195 .collect();
21892196 names.sort();
21902197 names.dedup();
2198+ // A histogram first: with seventy of these, which *causes* there
2199+ // are matters more than which functions, and a truncated list of
2200+ // names invites reading the first twenty as the whole picture.
2201+ let mut counts: HashMap<String, usize> = HashMap::new();
2202+ for w in why.values() {
2203+ let head = w.split(" -- `").next().unwrap_or(w).to_string();
2204+ *counts.entry(head).or_default() += 1;
2205+ }
2206+ let mut hist: Vec<(String, usize)> = counts.into_iter().collect();
2207+ hist.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
2208+ let causes: Vec<String> = hist
2209+ .iter()
2210+ .map(|(w, n)| format!(" {n:>4} {w}"))
2211+ .collect();
21912212 let shown: Vec<String> = names.iter().take(20).cloned().collect();
21922213 return Err(format!(
21932214 "the concrete type behind {} opaque return type(s) could not \
21942215 be read off the body. Rust allows only one such type per \
21952216 function, so it is there -- rustnim could not work out what \
2196- it is:\n{}{}",
2217+ it is.\n\nby cause:\n{}\n\nfirst {} of {}:\n{}",
2218+ names.len(),
2219+ causes.join("\n"),
2220+ shown.len(),
21972221 names.len(),
21982222 shown.join("\n"),
2199- if names.len() > shown.len() {
2200- format!("\n ... and {} more", names.len() - shown.len())
2201- } else {
2202- String::new()
2203- }
22042223 ));
22052224 }
22062225 Ok(())
@@ -2213,16 +2232,27 @@ impl Lowerer {
22132232 block: &syn::Block,
22142233 self_ty: Option<&Nim>,
22152234 ) -> Option<Nim> {
2216- let tail = block.stmts.iter().rev().find_map(|st| match st {
2235+ self.opaque_why = None;
2236+ let Some(tail) = block.stmts.iter().rev().find_map(|st| match st {
22172237 Stmt::Expr(e, None) => Some(e.clone()),
22182238 // `return e;` answers the same question.
22192239 Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(),
22202240 _ => None,
2221- })?;
2241+ }) else {
2242+ self.opaque_why =
2243+ Some("the body has no trailing expression to read a type from".into());
2244+ return None;
2245+ };
22222246
22232247 // Parameters have to be in scope, or every mention of one is an
22242248 // unknown name and the result types as nothing.
2225- let (ptys, _) = self.signature(sig).ok()?;
2249+ let ptys = match self.signature(sig) {
2250+ Ok((p, _)) => p,
2251+ Err(e) => {
2252+ self.opaque_why = Some(format!("the signature does not map: {e}"));
2253+ return None;
2254+ }
2255+ };
22262256 self.push_scope();
22272257 // The receiver too: `match self { .. }` is the whole body of several
22282258 // of these, and `self` is not a `FnArg::Typed`.
@@ -2259,7 +2289,6 @@ impl Lowerer {
22592289 let saved_out = self.out.len();
22602290 let saved_indent = self.indent;
22612291 let saved_hoisted = self.hoisted.len();
2262- self.opaque_why = None;
22632292 let found = self.result_ty(&tail);
22642293 self.out.truncate(saved_out);
22652294 self.indent = saved_indent;
@@ -2057,9 +2057,16 @@ impl Lowerer {
2057 fn result_ty(&mut self, e: &Expr) -> Option<Nim> {2057 fn result_ty(&mut self, e: &Expr) -> Option<Nim> {
2058 let r = self.result_ty_inner(e);2058 let r = self.result_ty_inner(e);
2059 if r.is_none() && self.opaque_why.is_none() {2059 if r.is_none() && self.opaque_why.is_none() {
2060- if let Err(msg) = self.expr(e) {2060+ self.opaque_why = Some(match self.expr(e) {
2061- self.opaque_why = Some(msg);2061+ Err(msg) => msg,
2062- }2062+ // Lowered without complaint and still untyped. A different
2063+ // thing from a refusal, and worth saying so: nothing is
2064+ // missing from the lowering, it just has no type to report.
2065+ Ok(_) => format!(
2066+ "the body lowers but yields no type: `{}`",
2067+ snippet(&quote::quote!(#e).to_string())
2068+ ),
2069+ });
2063 }2070 }
2064 r2071 r
2065 }2072 }
@@ -2188,19 +2195,31 @@ impl Lowerer {
2188 .collect();2195 .collect();
2189 names.sort();2196 names.sort();
2190 names.dedup();2197 names.dedup();
2198+ // A histogram first: with seventy of these, which *causes* there
2199+ // are matters more than which functions, and a truncated list of
2200+ // names invites reading the first twenty as the whole picture.
2201+ let mut counts: HashMap<String, usize> = HashMap::new();
2202+ for w in why.values() {
2203+ let head = w.split(" -- `").next().unwrap_or(w).to_string();
2204+ *counts.entry(head).or_default() += 1;
2205+ }
2206+ let mut hist: Vec<(String, usize)> = counts.into_iter().collect();
2207+ hist.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
2208+ let causes: Vec<String> = hist
2209+ .iter()
2210+ .map(|(w, n)| format!(" {n:>4} {w}"))
2211+ .collect();
2191 let shown: Vec<String> = names.iter().take(20).cloned().collect();2212 let shown: Vec<String> = names.iter().take(20).cloned().collect();
2192 return Err(format!(2213 return Err(format!(
2193 "the concrete type behind {} opaque return type(s) could not \2214 "the concrete type behind {} opaque return type(s) could not \
2194 be read off the body. Rust allows only one such type per \2215 be read off the body. Rust allows only one such type per \
2195 function, so it is there -- rustnim could not work out what \2216 function, so it is there -- rustnim could not work out what \
2196- it is:\n{}{}",2217+ it is.\n\nby cause:\n{}\n\nfirst {} of {}:\n{}",
2218+ names.len(),
2219+ causes.join("\n"),
2220+ shown.len(),
2197 names.len(),2221 names.len(),
2198 shown.join("\n"),2222 shown.join("\n"),
2199- if names.len() > shown.len() {
2200- format!("\n ... and {} more", names.len() - shown.len())
2201- } else {
2202- String::new()
2203- }
2204 ));2223 ));
2205 }2224 }
2206 Ok(())2225 Ok(())
@@ -2213,16 +2232,27 @@ impl Lowerer {
2213 block: &syn::Block,2232 block: &syn::Block,
2214 self_ty: Option<&Nim>,2233 self_ty: Option<&Nim>,
2215 ) -> Option<Nim> {2234 ) -> Option<Nim> {
2216- let tail = block.stmts.iter().rev().find_map(|st| match st {2235+ self.opaque_why = None;
2236+ let Some(tail) = block.stmts.iter().rev().find_map(|st| match st {
2217 Stmt::Expr(e, None) => Some(e.clone()),2237 Stmt::Expr(e, None) => Some(e.clone()),
2218 // `return e;` answers the same question.2238 // `return e;` answers the same question.
2219 Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(),2239 Stmt::Expr(Expr::Return(r), _) => r.expr.as_deref().cloned(),
2220 _ => None,2240 _ => None,
2221- })?;2241+ }) else {
2242+ self.opaque_why =
2243+ Some("the body has no trailing expression to read a type from".into());
2244+ return None;
2245+ };
2222 2246
2223 // Parameters have to be in scope, or every mention of one is an2247 // Parameters have to be in scope, or every mention of one is an
2224 // unknown name and the result types as nothing.2248 // unknown name and the result types as nothing.
2225- let (ptys, _) = self.signature(sig).ok()?;2249+ let ptys = match self.signature(sig) {
2250+ Ok((p, _)) => p,
2251+ Err(e) => {
2252+ self.opaque_why = Some(format!("the signature does not map: {e}"));
2253+ return None;
2254+ }
2255+ };
2226 self.push_scope();2256 self.push_scope();
2227 // The receiver too: `match self { .. }` is the whole body of several2257 // The receiver too: `match self { .. }` is the whole body of several
2228 // of these, and `self` is not a `FnArg::Typed`.2258 // of these, and `self` is not a `FnArg::Typed`.
@@ -2259,7 +2289,6 @@ impl Lowerer {
2259 let saved_out = self.out.len();2289 let saved_out = self.out.len();
2260 let saved_indent = self.indent;2290 let saved_indent = self.indent;
2261 let saved_hoisted = self.hoisted.len();2291 let saved_hoisted = self.hoisted.len();
2262- self.opaque_why = None;
2263 let found = self.result_ty(&tail);2292 let found = self.result_ty(&tail);
2264 self.out.truncate(saved_out);2293 self.out.truncate(saved_out);
2265 self.indent = saved_indent;2294 self.indent = saved_indent;