nandi/rustnimpublic Fork
⑂ 8594af4
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

Lower enum discriminants in their own pass, after every signature is known

`atspi-common` and `enumflags2` both contain `next_bit(..)` enum
discriminants, and `atspi-common` sorts first. Discriminants were lowered
during collection, so whether the call resolved depended on the order the
input files happened to be given in -- alphabetical order was enough to break
it. Moved to a pass that runs once all files are collected.

Also replaces the signature-text key for opaque return types with one made
from the signature *and* the body. `fn render(&self) -> impl Display + Copy`
occurs twice in libcosmic's graph with two different concrete types behind it,
so a signature alone genuinely cannot tell them apart -- the previous commit's
loud error on that was right about the problem and wrong to stop there. The
resolved type now reaches `signature` as a hint from whoever knows which body
is in hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T10:16:32-07:00 Browse files
8594af4 parent: 24296c3
modified src/lower.rs +56 -27
@@ -44,7 +44,7 @@ enum Fwd {
4444 Text(String),
4545 /// Also carries the impl's generic parameters and `Self` type: rendering
4646 /// happens after collection, when that context is no longer current.
47- Head(String, Box<syn::Signature>, Option<Nim>, Vec<String>, Option<Nim>),
47+ Head(String, Box<syn::Signature>, Option<Nim>, Vec<String>, Option<Nim>, Box<syn::Block>),
4848 }
4949
5050 #[derive(Clone, Debug)]
@@ -200,6 +200,10 @@ struct Variant {
200200 /// `Error = 1` — Nim enums take explicit ordinals too, so the value is
201201 /// preserved rather than the variant being renumbered.
202202 discriminant: Option<String>,
203+ /// The discriminant as written. Lowered in its own pass, because it may
204+ /// call a function defined in a file that has not been collected yet --
205+ /// which made the result depend on the order the inputs were given in.
206+ disc_expr: Option<syn::Expr>,
203207 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
204208 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
205209 /// Nim requires the branches of a variant object to have distinct fields.
@@ -348,6 +352,11 @@ pub struct Lowerer {
348352 /// `signature` is called again on the emission pass and has only the
349353 /// signature to go on, so that is what the key is made of.
350354 opaque_ret: HashMap<String, Nim>,
355+ /// Set around a call to `signature` when the caller knows which body the
356+ /// signature belongs to. `signature` has only the signature to go on, and
357+ /// two functions can share one -- `fn render(self) -> impl Display` twice
358+ /// with different concrete types is real, and happens.
359+ opaque_hint: Option<Nim>,
351360 /// Module names supplied as separate input files. A `mod x;` naming one
352361 /// of these is satisfied by that file having been passed in.
353362 pub modules: Vec<String>,
@@ -424,6 +433,7 @@ impl Lowerer {
424433 const_gens: HashMap::new(),
425434 opaque_fns: Vec::new(),
426435 opaque_ret: HashMap::new(),
436+ opaque_hint: None,
427437 modules: Vec::new(),
428438 emitted: 0,
429439 features: Vec::new(),
@@ -530,6 +540,11 @@ impl Lowerer {
530540 // ambiguous, so reading it off the body is exact rather than a guess.
531541 // Repeated to a fixpoint because one such function may return the
532542 // result of another.
543+ // Discriminants, now that every file's signatures are known. They can
544+ // call ordinary functions -- `enumflags2` expands to exactly that --
545+ // and lowering them during collection made the answer depend on which
546+ // order the input files happened to be given in.
547+ self.resolve_discriminants()?;
533548 self.resolve_opaque()?;
534549
535550 // Pass 2: type definitions, which every signature may mention.
@@ -547,10 +562,12 @@ impl Lowerer {
547562 for f in self.forwards.clone() {
548563 let f = match f {
549564 Fwd::Text(t) => t,
550- Fwd::Head(nim, sig, recv, gens, self_ty) => {
565+ Fwd::Head(nim, sig, recv, gens, self_ty, block) => {
551566 let og = std::mem::replace(&mut self.impl_generics, gens);
552567 let os = std::mem::replace(&mut self.self_ty, self_ty);
568+ self.opaque_hint = self.opaque_ret.get(&opaque_key(&sig, &block)).cloned();
553569 let r = self.head_of(&nim, &sig, recv.as_ref());
570+ self.opaque_hint = None;
554571 self.impl_generics = og;
555572 self.self_ty = os;
556573 r?
@@ -717,7 +734,7 @@ impl Lowerer {
717734 gen_names.extend(self.impl_trait_params(&f.sig));
718735 let name = f.sig.ident.to_string();
719736 let nim = self.fn_name(&self.cur_mod, &name);
720- self.forwards.push(Fwd::Head(nim.clone(), Box::new(f.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone()));
737+ self.forwards.push(Fwd::Head(nim.clone(), Box::new(f.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone(), Box::new((*f.block).clone())));
721738 self.fns
722739 .insert(
723740 (self.cur_mod.clone(), name.clone()),
@@ -835,6 +852,7 @@ impl Lowerer {
835852 .map(|v| Variant {
836853 name: v.ident.to_string(),
837854 discriminant: None,
855+ disc_expr: None,
838856 fields: Vec::new(),
839857 })
840858 .collect(),
@@ -844,10 +862,7 @@ impl Lowerer {
844862 let mut variants = Vec::new();
845863 for v in &e.variants {
846864 let vname = v.ident.to_string();
847- let discriminant = match &v.discriminant {
848- Some((_, e)) => Some(self.expr(e)?.code),
849- None => None,
850- };
865+ let disc_expr = v.discriminant.as_ref().map(|(_, e)| e.clone());
851866 let mut fields = Vec::new();
852867 for (i, f) in v.fields.iter().enumerate() {
853868 // Nim requires the branches of a variant object to have
@@ -860,7 +875,7 @@ impl Lowerer {
860875 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
861876 fields.push((fname, t));
862877 }
863- variants.push(Variant { name: vname, discriminant, fields });
878+ variants.push(Variant { name: vname, discriminant: None, disc_expr, fields });
864879 }
865880 self.enums.insert(
866881 name.clone(),
@@ -940,7 +955,7 @@ impl Lowerer {
940955 // The source type may be a tuple, whose rendering has
941956 // parens and commas in it; a Nim identifier may not.
942957 let name = format!("rsFrom{}{}", mangle(&tyname), mangle(&type_name(&src)));
943- self.forwards.push(Fwd::Head(name.clone(), Box::new(m.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone()));
958+ self.forwards.push(Fwd::Head(name.clone(), Box::new(m.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone(), Box::new(m.block.clone())));
944959 self.from_types
945960 .insert((type_name(&src), tyname.clone()), src.clone());
946961 self.from_impls
@@ -981,7 +996,7 @@ impl Lowerer {
981996 None
982997 };
983998 let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some());
984- self.forwards.push(Fwd::Head(nim.clone(), Box::new(m.sig.clone()), recv.clone(), self.impl_generics.clone(), self.self_ty.clone()));
999+ self.forwards.push(Fwd::Head(nim.clone(), Box::new(m.sig.clone()), recv.clone(), self.impl_generics.clone(), self.self_ty.clone(), Box::new(m.block.clone())));
9851000 self.methods
9861001 .insert((tyname.clone(), mname.clone()), Sig { params, ret: ret.clone(), generics: gen_names.clone() });
9871002 self.statics.insert((tyname.clone(), mname.clone()), nim);
@@ -1014,6 +1029,7 @@ impl Lowerer {
10141029 recv.clone(),
10151030 self.impl_generics.clone(),
10161031 self.self_ty.clone(),
1032+ Box::new(m.block.clone()),
10171033 ));
10181034 self.methods
10191035 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret: ret.clone(), generics: gen_names.clone() });
@@ -1686,6 +1702,21 @@ impl Lowerer {
16861702 }
16871703
16881704
1705+ /// Lower every enum discriminant, after all signatures are collected.
1706+ fn resolve_discriminants(&mut self) -> Result<(), String> {
1707+ let names: Vec<String> = self.enums.keys().cloned().collect();
1708+ for n in names {
1709+ let Some(def) = self.enums.get(&n).cloned() else { continue };
1710+ let mut out = def.clone();
1711+ for (i, v) in def.variants.iter().enumerate() {
1712+ let Some(e) = &v.disc_expr else { continue };
1713+ out.variants[i].discriminant = Some(self.expr(e)?.code);
1714+ }
1715+ self.enums.insert(n, out);
1716+ }
1717+ Ok(())
1718+ }
1719+
16891720 /// Give every return-position `impl Trait` its concrete type, taken from
16901721 /// the body that produces it.
16911722 ///
@@ -1705,22 +1736,7 @@ impl Lowerer {
17051736 for (key, sig, block) in left {
17061737 match self.opaque_of(&sig, &block) {
17071738 Some(t) if t != opaque => {
1708- let sk = quote::quote!(#sig).to_string();
1709- if let Some(prev) = self.opaque_ret.get(&sk) {
1710- if prev != &t {
1711- return Err(format!(
1712- "two functions share the signature `{}` but \
1713- their opaque return types are different \
1714- (`{}` and `{}`); rustnim keys the \
1715- resolution on the signature text, so it \
1716- cannot tell them apart",
1717- sk,
1718- prev.render(),
1719- t.render()
1720- ));
1721- }
1722- }
1723- self.opaque_ret.insert(sk, t.clone());
1739+ self.opaque_ret.insert(opaque_key(&sig, &block), t.clone());
17241740 match &key {
17251741 OpaqueKey::Free(m, n) => {
17261742 if let Some(s) = self.fns.get_mut(&(m.clone(), n.clone())) {
@@ -1825,7 +1841,7 @@ impl Lowerer {
18251841 ReturnType::Type(_, t)
18261842 if matches!(&**t, syn::Type::ImplTrait(_)) && self.map_ty(t).is_err() =>
18271843 {
1828- match self.opaque_ret.get(&quote::quote!(#sig).to_string()) {
1844+ match &self.opaque_hint {
18291845 Some(t) => t.clone(),
18301846 None => Nim::Named(OPAQUE.into(), Vec::new()),
18311847 }
@@ -2725,7 +2741,9 @@ impl Lowerer {
27252741 body: &syn::Block,
27262742 recv: Option<Nim>,
27272743 ) -> Result<(), String> {
2744+ self.opaque_hint = self.opaque_ret.get(&opaque_key(sig, body)).cloned();
27282745 let (ptys, ret) = self.signature(sig)?;
2746+ self.opaque_hint = None;
27292747
27302748 self.push_scope();
27312749 let mut rendered: Vec<String> = Vec::new();
@@ -7577,3 +7595,14 @@ fn rename_ident(ts: proc_macro2::TokenStream, from: &str, to: &str) -> proc_macr
75777595 })
75787596 .collect()
75797597 }
7598+
7599+/// Identify a function by its signature *and* its body. A signature alone is
7600+/// not enough: `fn render(&self) -> impl Display` appears twice in one
7601+/// dependency graph with two different concrete types behind it.
7602+fn opaque_key(sig: &syn::Signature, block: &syn::Block) -> String {
7603+ format!(
7604+ "{}\u{1}{}",
7605+ quote::quote!(#sig),
7606+ quote::quote!(#block)
7607+ )
7608+}
@@ -44,7 +44,7 @@ enum Fwd {
44 Text(String),44 Text(String),
45 /// Also carries the impl's generic parameters and `Self` type: rendering45 /// Also carries the impl's generic parameters and `Self` type: rendering
46 /// happens after collection, when that context is no longer current.46 /// happens after collection, when that context is no longer current.
47- Head(String, Box<syn::Signature>, Option<Nim>, Vec<String>, Option<Nim>),47+ Head(String, Box<syn::Signature>, Option<Nim>, Vec<String>, Option<Nim>, Box<syn::Block>),
48 }48 }
49 49
50 #[derive(Clone, Debug)]50 #[derive(Clone, Debug)]
@@ -200,6 +200,10 @@ struct Variant {
200 /// `Error = 1` — Nim enums take explicit ordinals too, so the value is200 /// `Error = 1` — Nim enums take explicit ordinals too, so the value is
201 /// preserved rather than the variant being renumbered.201 /// preserved rather than the variant being renumbered.
202 discriminant: Option<String>,202 discriminant: Option<String>,
203+ /// The discriminant as written. Lowered in its own pass, because it may
204+ /// call a function defined in a file that has not been collected yet --
205+ /// which made the result depend on the order the inputs were given in.
206+ disc_expr: Option<syn::Expr>,
203 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get207 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
204 /// `f0`, `f1`, ...; every field is prefixed with the variant name because208 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
205 /// Nim requires the branches of a variant object to have distinct fields.209 /// Nim requires the branches of a variant object to have distinct fields.
@@ -348,6 +352,11 @@ pub struct Lowerer {
348 /// `signature` is called again on the emission pass and has only the352 /// `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.353 /// signature to go on, so that is what the key is made of.
350 opaque_ret: HashMap<String, Nim>,354 opaque_ret: HashMap<String, Nim>,
355+ /// Set around a call to `signature` when the caller knows which body the
356+ /// signature belongs to. `signature` has only the signature to go on, and
357+ /// two functions can share one -- `fn render(self) -> impl Display` twice
358+ /// with different concrete types is real, and happens.
359+ opaque_hint: Option<Nim>,
351 /// Module names supplied as separate input files. A `mod x;` naming one360 /// Module names supplied as separate input files. A `mod x;` naming one
352 /// of these is satisfied by that file having been passed in.361 /// of these is satisfied by that file having been passed in.
353 pub modules: Vec<String>,362 pub modules: Vec<String>,
@@ -424,6 +433,7 @@ impl Lowerer {
424 const_gens: HashMap::new(),433 const_gens: HashMap::new(),
425 opaque_fns: Vec::new(),434 opaque_fns: Vec::new(),
426 opaque_ret: HashMap::new(),435 opaque_ret: HashMap::new(),
436+ opaque_hint: None,
427 modules: Vec::new(),437 modules: Vec::new(),
428 emitted: 0,438 emitted: 0,
429 features: Vec::new(),439 features: Vec::new(),
@@ -530,6 +540,11 @@ impl Lowerer {
530 // ambiguous, so reading it off the body is exact rather than a guess.540 // ambiguous, so reading it off the body is exact rather than a guess.
531 // Repeated to a fixpoint because one such function may return the541 // Repeated to a fixpoint because one such function may return the
532 // result of another.542 // result of another.
543+ // Discriminants, now that every file's signatures are known. They can
544+ // call ordinary functions -- `enumflags2` expands to exactly that --
545+ // and lowering them during collection made the answer depend on which
546+ // order the input files happened to be given in.
547+ self.resolve_discriminants()?;
533 self.resolve_opaque()?;548 self.resolve_opaque()?;
534 549
535 // Pass 2: type definitions, which every signature may mention.550 // Pass 2: type definitions, which every signature may mention.
@@ -547,10 +562,12 @@ impl Lowerer {
547 for f in self.forwards.clone() {562 for f in self.forwards.clone() {
548 let f = match f {563 let f = match f {
549 Fwd::Text(t) => t,564 Fwd::Text(t) => t,
550- Fwd::Head(nim, sig, recv, gens, self_ty) => {565+ Fwd::Head(nim, sig, recv, gens, self_ty, block) => {
551 let og = std::mem::replace(&mut self.impl_generics, gens);566 let og = std::mem::replace(&mut self.impl_generics, gens);
552 let os = std::mem::replace(&mut self.self_ty, self_ty);567 let os = std::mem::replace(&mut self.self_ty, self_ty);
568+ self.opaque_hint = self.opaque_ret.get(&opaque_key(&sig, &block)).cloned();
553 let r = self.head_of(&nim, &sig, recv.as_ref());569 let r = self.head_of(&nim, &sig, recv.as_ref());
570+ self.opaque_hint = None;
554 self.impl_generics = og;571 self.impl_generics = og;
555 self.self_ty = os;572 self.self_ty = os;
556 r?573 r?
@@ -717,7 +734,7 @@ impl Lowerer {
717 gen_names.extend(self.impl_trait_params(&f.sig));734 gen_names.extend(self.impl_trait_params(&f.sig));
718 let name = f.sig.ident.to_string();735 let name = f.sig.ident.to_string();
719 let nim = self.fn_name(&self.cur_mod, &name);736 let nim = self.fn_name(&self.cur_mod, &name);
720- self.forwards.push(Fwd::Head(nim.clone(), Box::new(f.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone()));737+ self.forwards.push(Fwd::Head(nim.clone(), Box::new(f.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone(), Box::new((*f.block).clone())));
721 self.fns738 self.fns
722 .insert(739 .insert(
723 (self.cur_mod.clone(), name.clone()),740 (self.cur_mod.clone(), name.clone()),
@@ -835,6 +852,7 @@ impl Lowerer {
835 .map(|v| Variant {852 .map(|v| Variant {
836 name: v.ident.to_string(),853 name: v.ident.to_string(),
837 discriminant: None,854 discriminant: None,
855+ disc_expr: None,
838 fields: Vec::new(),856 fields: Vec::new(),
839 })857 })
840 .collect(),858 .collect(),
@@ -844,10 +862,7 @@ impl Lowerer {
844 let mut variants = Vec::new();862 let mut variants = Vec::new();
845 for v in &e.variants {863 for v in &e.variants {
846 let vname = v.ident.to_string();864 let vname = v.ident.to_string();
847- let discriminant = match &v.discriminant {865+ let disc_expr = v.discriminant.as_ref().map(|(_, e)| e.clone());
848- Some((_, e)) => Some(self.expr(e)?.code),
849- None => None,
850- };
851 let mut fields = Vec::new();866 let mut fields = Vec::new();
852 for (i, f) in v.fields.iter().enumerate() {867 for (i, f) in v.fields.iter().enumerate() {
853 // Nim requires the branches of a variant object to have868 // Nim requires the branches of a variant object to have
@@ -860,7 +875,7 @@ impl Lowerer {
860 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };875 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
861 fields.push((fname, t));876 fields.push((fname, t));
862 }877 }
863- variants.push(Variant { name: vname, discriminant, fields });878+ variants.push(Variant { name: vname, discriminant: None, disc_expr, fields });
864 }879 }
865 self.enums.insert(880 self.enums.insert(
866 name.clone(),881 name.clone(),
@@ -940,7 +955,7 @@ impl Lowerer {
940 // The source type may be a tuple, whose rendering has955 // The source type may be a tuple, whose rendering has
941 // parens and commas in it; a Nim identifier may not.956 // parens and commas in it; a Nim identifier may not.
942 let name = format!("rsFrom{}{}", mangle(&tyname), mangle(&type_name(&src)));957 let name = format!("rsFrom{}{}", mangle(&tyname), mangle(&type_name(&src)));
943- self.forwards.push(Fwd::Head(name.clone(), Box::new(m.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone()));958+ self.forwards.push(Fwd::Head(name.clone(), Box::new(m.sig.clone()), None, self.impl_generics.clone(), self.self_ty.clone(), Box::new(m.block.clone())));
944 self.from_types959 self.from_types
945 .insert((type_name(&src), tyname.clone()), src.clone());960 .insert((type_name(&src), tyname.clone()), src.clone());
946 self.from_impls961 self.from_impls
@@ -981,7 +996,7 @@ impl Lowerer {
981 None996 None
982 };997 };
983 let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some());998 let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some());
984- self.forwards.push(Fwd::Head(nim.clone(), Box::new(m.sig.clone()), recv.clone(), self.impl_generics.clone(), self.self_ty.clone()));999+ self.forwards.push(Fwd::Head(nim.clone(), Box::new(m.sig.clone()), recv.clone(), self.impl_generics.clone(), self.self_ty.clone(), Box::new(m.block.clone())));
985 self.methods1000 self.methods
986 .insert((tyname.clone(), mname.clone()), Sig { params, ret: ret.clone(), generics: gen_names.clone() });1001 .insert((tyname.clone(), mname.clone()), Sig { params, ret: ret.clone(), generics: gen_names.clone() });
987 self.statics.insert((tyname.clone(), mname.clone()), nim);1002 self.statics.insert((tyname.clone(), mname.clone()), nim);
@@ -1014,6 +1029,7 @@ impl Lowerer {
1014 recv.clone(),1029 recv.clone(),
1015 self.impl_generics.clone(),1030 self.impl_generics.clone(),
1016 self.self_ty.clone(),1031 self.self_ty.clone(),
1032+ Box::new(m.block.clone()),
1017 ));1033 ));
1018 self.methods1034 self.methods
1019 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret: ret.clone(), generics: gen_names.clone() });1035 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret: ret.clone(), generics: gen_names.clone() });
@@ -1686,6 +1702,21 @@ impl Lowerer {
1686 }1702 }
1687 1703
1688 1704
1705+ /// Lower every enum discriminant, after all signatures are collected.
1706+ fn resolve_discriminants(&mut self) -> Result<(), String> {
1707+ let names: Vec<String> = self.enums.keys().cloned().collect();
1708+ for n in names {
1709+ let Some(def) = self.enums.get(&n).cloned() else { continue };
1710+ let mut out = def.clone();
1711+ for (i, v) in def.variants.iter().enumerate() {
1712+ let Some(e) = &v.disc_expr else { continue };
1713+ out.variants[i].discriminant = Some(self.expr(e)?.code);
1714+ }
1715+ self.enums.insert(n, out);
1716+ }
1717+ Ok(())
1718+ }
1719+
1689 /// Give every return-position `impl Trait` its concrete type, taken from1720 /// Give every return-position `impl Trait` its concrete type, taken from
1690 /// the body that produces it.1721 /// the body that produces it.
1691 ///1722 ///
@@ -1705,22 +1736,7 @@ impl Lowerer {
1705 for (key, sig, block) in left {1736 for (key, sig, block) in left {
1706 match self.opaque_of(&sig, &block) {1737 match self.opaque_of(&sig, &block) {
1707 Some(t) if t != opaque => {1738 Some(t) if t != opaque => {
1708- let sk = quote::quote!(#sig).to_string();1739+ self.opaque_ret.insert(opaque_key(&sig, &block), t.clone());
1709- if let Some(prev) = self.opaque_ret.get(&sk) {
1710- if prev != &t {
1711- return Err(format!(
1712- "two functions share the signature `{}` but \
1713- their opaque return types are different \
1714- (`{}` and `{}`); rustnim keys the \
1715- resolution on the signature text, so it \
1716- cannot tell them apart",
1717- sk,
1718- prev.render(),
1719- t.render()
1720- ));
1721- }
1722- }
1723- self.opaque_ret.insert(sk, t.clone());
1724 match &key {1740 match &key {
1725 OpaqueKey::Free(m, n) => {1741 OpaqueKey::Free(m, n) => {
1726 if let Some(s) = self.fns.get_mut(&(m.clone(), n.clone())) {1742 if let Some(s) = self.fns.get_mut(&(m.clone(), n.clone())) {
@@ -1825,7 +1841,7 @@ impl Lowerer {
1825 ReturnType::Type(_, t)1841 ReturnType::Type(_, t)
1826 if matches!(&**t, syn::Type::ImplTrait(_)) && self.map_ty(t).is_err() =>1842 if matches!(&**t, syn::Type::ImplTrait(_)) && self.map_ty(t).is_err() =>
1827 {1843 {
1828- match self.opaque_ret.get(&quote::quote!(#sig).to_string()) {1844+ match &self.opaque_hint {
1829 Some(t) => t.clone(),1845 Some(t) => t.clone(),
1830 None => Nim::Named(OPAQUE.into(), Vec::new()),1846 None => Nim::Named(OPAQUE.into(), Vec::new()),
1831 }1847 }
@@ -2725,7 +2741,9 @@ impl Lowerer {
2725 body: &syn::Block,2741 body: &syn::Block,
2726 recv: Option<Nim>,2742 recv: Option<Nim>,
2727 ) -> Result<(), String> {2743 ) -> Result<(), String> {
2744+ self.opaque_hint = self.opaque_ret.get(&opaque_key(sig, body)).cloned();
2728 let (ptys, ret) = self.signature(sig)?;2745 let (ptys, ret) = self.signature(sig)?;
2746+ self.opaque_hint = None;
2729 2747
2730 self.push_scope();2748 self.push_scope();
2731 let mut rendered: Vec<String> = Vec::new();2749 let mut rendered: Vec<String> = Vec::new();
@@ -7577,3 +7595,14 @@ fn rename_ident(ts: proc_macro2::TokenStream, from: &str, to: &str) -> proc_macr
7577 })7595 })
7578 .collect()7596 .collect()
7579 }7597 }
7598+
7599+/// Identify a function by its signature *and* its body. A signature alone is
7600+/// not enough: `fn render(&self) -> impl Display` appears twice in one
7601+/// dependency graph with two different concrete types behind it.
7602+fn opaque_key(sig: &syn::Signature, block: &syn::Block) -> String {
7603+ format!(
7604+ "{}\u{1}{}",
7605+ quote::quote!(#sig),
7606+ quote::quote!(#block)
7607+ )
7608+}