diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index d29e9f9b3f65ae24d68c698c254b273a452dd99b..a8151ccfff3c9a840dd48a1b442139ca22fb9d16 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1663,11 +1663,7 @@ fn lower_opaque_inner( ); debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params); - let lifetime_mapping = if in_trait { - Some(&*self.arena.alloc_slice(&synthesized_lifetime_args)) - } else { - None - }; + let lifetime_mapping = self.arena.alloc_slice(&synthesized_lifetime_args); let opaque_ty_item = hir::OpaqueTy { generics: this.arena.alloc(hir::Generics { diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index c6f8d1e211d59ea332a2e07d992997971402909e..61353c7b1ec510a4a3d05d18633920370669040c 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2675,7 +2675,7 @@ pub struct OpaqueTy<'hir> { /// /// This mapping associated a captured lifetime (first parameter) with the new /// early-bound lifetime that was generated for the opaque. - pub lifetime_mapping: Option<&'hir [(&'hir Lifetime, LocalDefId)]>, + pub lifetime_mapping: &'hir [(&'hir Lifetime, LocalDefId)], /// Whether the opaque is a return-position impl trait (or async future) /// originating from a trait method. This makes it so that the opaque is /// lowered as an associated type. diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 49307d96cc2347eef0f8e116f47b293880f0781c..8a5099804ed26efb9ce4d669d6c8e44f2db05fd4 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -407,7 +407,17 @@ fn check_opaque_meets_bounds<'tcx>( .build(); let ocx = ObligationCtxt::new(&infcx); - let args = GenericArgs::identity_for_item(tcx, def_id.to_def_id()); + let args = match *origin { + hir::OpaqueTyOrigin::FnReturn(parent) | hir::OpaqueTyOrigin::AsyncFn(parent) => { + GenericArgs::identity_for_item(tcx, parent).extend_to( + tcx, + def_id.to_def_id(), + |param, _| tcx.map_rpit_lifetime_to_fn_lifetime(param.def_id.expect_local()).into(), + ) + } + hir::OpaqueTyOrigin::TyAlias { .. } => GenericArgs::identity_for_item(tcx, def_id), + }; + let opaque_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args); // `ReErased` regions appear in the "parent_args" of closures/generators. @@ -468,9 +478,10 @@ fn check_opaque_meets_bounds<'tcx>( } } // Check that any hidden types found during wf checking match the hidden types that `type_of` sees. - for (key, mut ty) in infcx.take_opaque_types() { + for (mut key, mut ty) in infcx.take_opaque_types() { ty.hidden_type.ty = infcx.resolve_vars_if_possible(ty.hidden_type.ty); - sanity_check_found_hidden_type(tcx, key, ty.hidden_type, defining_use_anchor, origin)?; + key = infcx.resolve_vars_if_possible(key); + sanity_check_found_hidden_type(tcx, key, ty.hidden_type)?; } Ok(()) } @@ -479,8 +490,6 @@ fn sanity_check_found_hidden_type<'tcx>( tcx: TyCtxt<'tcx>, key: ty::OpaqueTypeKey<'tcx>, mut ty: ty::OpaqueHiddenType<'tcx>, - defining_use_anchor: LocalDefId, - origin: &hir::OpaqueTyOrigin, ) -> Result<(), ErrorGuaranteed> { if ty.ty.is_ty_var() { // Nothing was actually constrained. @@ -493,29 +502,23 @@ fn sanity_check_found_hidden_type<'tcx>( return Ok(()); } } + let strip_vars = |ty: Ty<'tcx>| { + ty.fold_with(&mut BottomUpFolder { + tcx, + ty_op: |t| t, + ct_op: |c| c, + lt_op: |l| match l.kind() { + RegionKind::ReVar(_) => tcx.lifetimes.re_erased, + _ => l, + }, + }) + }; // Closures frequently end up containing erased lifetimes in their final representation. // These correspond to lifetime variables that never got resolved, so we patch this up here. - ty.ty = ty.ty.fold_with(&mut BottomUpFolder { - tcx, - ty_op: |t| t, - ct_op: |c| c, - lt_op: |l| match l.kind() { - RegionKind::ReVar(_) => tcx.lifetimes.re_erased, - _ => l, - }, - }); + ty.ty = strip_vars(ty.ty); // Get the hidden type. - let mut hidden_ty = tcx.type_of(key.def_id).instantiate(tcx, key.args); - if let hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) = origin { - if hidden_ty != ty.ty { - hidden_ty = find_and_apply_rpit_args( - tcx, - hidden_ty, - defining_use_anchor.to_def_id(), - key.def_id.to_def_id(), - )?; - } - } + let hidden_ty = tcx.type_of(key.def_id).instantiate(tcx, key.args); + let hidden_ty = strip_vars(hidden_ty); // If the hidden types differ, emit a type mismatch diagnostic. if hidden_ty == ty.ty { @@ -527,105 +530,6 @@ fn sanity_check_found_hidden_type<'tcx>( } } -/// In case it is in a nested opaque type, find that opaque type's -/// usage in the function signature and use the generic arguments from the usage site. -/// We need to do because RPITs ignore the lifetimes of the function, -/// as they have their own copies of all the lifetimes they capture. -/// So the only way to get the lifetimes represented in terms of the function, -/// is to look how they are used in the function signature (or do some other fancy -/// recording of this mapping at ast -> hir lowering time). -/// -/// As an example: -/// ```text -/// trait Id { -/// type Assoc; -/// } -/// impl<'a> Id for &'a () { -/// type Assoc = &'a (); -/// } -/// fn func<'a>(x: &'a ()) -> impl Id { x } -/// // desugared to -/// fn func<'a>(x: &'a () -> Outer<'a> where as Id>::Assoc = Inner<'a> { -/// // Note that in contrast to other nested items, RPIT type aliases can -/// // access their parents' generics. -/// -/// // hidden type is `&'aDupOuter ()` -/// // During wfcheck the hidden type of `Inner<'aDupOuter>` is `&'a ()`, but -/// // `typeof(Inner<'aDupOuter>) = &'aDupOuter ()`. -/// // So we walk the signature of `func` to find the use of `Inner<'a>` -/// // and then use that to replace the lifetimes in the hidden type, obtaining -/// // `&'a ()`. -/// type Outer<'aDupOuter> = impl Id>; -/// -/// // hidden type is `&'aDupInner ()` -/// type Inner<'aDupInner> = impl Sized + 'aDupInner; -/// -/// x -/// } -/// ``` -fn find_and_apply_rpit_args<'tcx>( - tcx: TyCtxt<'tcx>, - mut hidden_ty: Ty<'tcx>, - function: DefId, - opaque: DefId, -) -> Result, ErrorGuaranteed> { - // Find use of the RPIT in the function signature and thus find the right args to - // convert it into the parameter space of the function signature. This is needed, - // because that's what `type_of` returns, against which we compare later. - let ret = tcx.fn_sig(function).instantiate_identity().output(); - struct Visitor<'tcx> { - tcx: TyCtxt<'tcx>, - opaque: DefId, - seen: FxHashSet, - } - impl<'tcx> ty::TypeVisitor> for Visitor<'tcx> { - type BreakTy = GenericArgsRef<'tcx>; - - #[instrument(level = "trace", skip(self), ret)] - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { - trace!("{:#?}", t.kind()); - match t.kind() { - ty::Alias(ty::Opaque, alias) => { - trace!(?alias.def_id); - if alias.def_id == self.opaque { - return ControlFlow::Break(alias.args); - } else if self.seen.insert(alias.def_id) { - for clause in self - .tcx - .explicit_item_bounds(alias.def_id) - .iter_instantiated_copied(self.tcx, alias.args) - { - trace!(?clause); - clause.visit_with(self)?; - } - } - } - ty::Alias(ty::Weak, alias) => { - self.tcx - .type_of(alias.def_id) - .instantiate(self.tcx, alias.args) - .visit_with(self)?; - } - _ => (), - } - - t.super_visit_with(self) - } - } - if let ControlFlow::Break(args) = - ret.visit_with(&mut Visitor { tcx, opaque, seen: Default::default() }) - { - trace!(?args); - trace!("expected: {hidden_ty:#?}"); - hidden_ty = ty::EarlyBinder::bind(hidden_ty).instantiate(tcx, args); - trace!("expected: {hidden_ty:#?}"); - } else { - tcx.sess - .delay_span_bug(tcx.def_span(function), format!("{ret:?} does not contain {opaque:?}")); - } - Ok(hidden_ty) -} - fn is_enum_of_nonnullable_ptr<'tcx>( tcx: TyCtxt<'tcx>, adt_def: AdtDef<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index 83220be68838ddb90b0271cc71af145743a66d9c..495e663666cb9faf61d8b34df86e555b1c23cf66 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -2,16 +2,16 @@ use crate::bounds::Bounds; use crate::collect::ItemCtxt; use crate::constrained_generic_params as cgp; -use hir::{HirId, Lifetime, Node}; +use hir::{HirId, Node}; use rustc_data_structures::fx::FxIndexSet; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{self, Visitor}; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_middle::ty::{GenericPredicates, Generics, ImplTraitInTraitData, ToPredicate}; +use rustc_middle::ty::{GenericPredicates, ImplTraitInTraitData, ToPredicate}; use rustc_span::symbol::Ident; -use rustc_span::{Span, Symbol, DUMMY_SP}; +use rustc_span::{Span, DUMMY_SP}; /// Returns a list of all type predicates (explicit and implicit) for the definition with /// ID `def_id`. This includes all predicates returned by `predicates_defined_on`, plus @@ -55,17 +55,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen use rustc_hir::*; match tcx.opt_rpitit_info(def_id.to_def_id()) { - Some(ImplTraitInTraitData::Trait { opaque_def_id, fn_def_id }) => { - let opaque_ty_id = tcx.hir().local_def_id_to_hir_id(opaque_def_id.expect_local()); - let opaque_ty_node = tcx.hir().get(opaque_ty_id); - let Node::Item(&Item { - kind: ItemKind::OpaqueTy(OpaqueTy { lifetime_mapping: Some(lifetime_mapping), .. }), - .. - }) = opaque_ty_node - else { - bug!("unexpected {opaque_ty_node:?}") - }; - + Some(ImplTraitInTraitData::Trait { fn_def_id, .. }) => { let mut predicates = Vec::new(); // RPITITs should inherit the predicates of their parent. This is @@ -78,13 +68,12 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen // We also install bidirectional outlives predicates for the RPITIT // to keep the duplicates lifetimes from opaque lowering in sync. + // We only need to compute bidirectional outlives for the duplicated + // opaque lifetimes, which explains the slicing below. compute_bidirectional_outlives_predicates( tcx, - def_id, - lifetime_mapping.iter().map(|(lifetime, def_id)| { - (**lifetime, (*def_id, lifetime.ident.name, lifetime.ident.span)) - }), - tcx.generics_of(def_id.to_def_id()), + &tcx.generics_of(def_id.to_def_id()).params + [tcx.generics_of(fn_def_id).params.len()..], &mut predicates, ); @@ -351,21 +340,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen }; debug!(?lifetimes); - let lifetime_mapping = std::iter::zip(lifetimes, ast_generics.params) - .map(|(arg, dup)| { - let hir::GenericArg::Lifetime(arg) = arg else { bug!() }; - (**arg, dup) - }) - .filter(|(_, dup)| matches!(dup.kind, hir::GenericParamKind::Lifetime { .. })) - .map(|(lifetime, dup)| (lifetime, (dup.def_id, dup.name.ident().name, dup.span))); - - compute_bidirectional_outlives_predicates( - tcx, - def_id, - lifetime_mapping, - generics, - &mut predicates, - ); + compute_bidirectional_outlives_predicates(tcx, &generics.params, &mut predicates); debug!(?predicates); } @@ -379,41 +354,28 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen /// enforce that these lifetimes stay in sync. fn compute_bidirectional_outlives_predicates<'tcx>( tcx: TyCtxt<'tcx>, - item_def_id: LocalDefId, - lifetime_mapping: impl Iterator, - generics: &Generics, + opaque_own_params: &[ty::GenericParamDef], predicates: &mut Vec<(ty::Clause<'tcx>, Span)>, ) { - let icx = ItemCtxt::new(tcx, item_def_id); - - for (arg, (dup_def, name, span)) in lifetime_mapping { - let orig_region = icx.astconv().ast_region_to_region(&arg, None); - if !matches!(orig_region.kind(), ty::ReEarlyBound(..)) { - // There is no late-bound lifetime to actually match up here, since the lifetime doesn't - // show up in the opaque's parent's args. - continue; + for param in opaque_own_params { + let orig_lifetime = tcx.map_rpit_lifetime_to_fn_lifetime(param.def_id.expect_local()); + if let ty::ReEarlyBound(..) = *orig_lifetime { + let dup_lifetime = ty::Region::new_early_bound( + tcx, + ty::EarlyBoundRegion { def_id: param.def_id, index: param.index, name: param.name }, + ); + let span = tcx.def_span(param.def_id); + predicates.push(( + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(orig_lifetime, dup_lifetime)) + .to_predicate(tcx), + span, + )); + predicates.push(( + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(dup_lifetime, orig_lifetime)) + .to_predicate(tcx), + span, + )); } - - let Some(dup_index) = generics.param_def_id_to_index(icx.tcx, dup_def.to_def_id()) else { - bug!() - }; - - let dup_region = ty::Region::new_early_bound( - tcx, - ty::EarlyBoundRegion { def_id: dup_def.to_def_id(), index: dup_index, name }, - ); - - predicates.push(( - ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(orig_region, dup_region)) - .to_predicate(tcx), - span, - )); - - predicates.push(( - ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(dup_region, orig_region)) - .to_predicate(tcx), - span, - )); } } diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 0b2d95506bf427b5639b1599546f15bdf464ee3d..6446f48bd1a5af264cc7c13946649b1b753a2394 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1931,6 +1931,84 @@ pub fn late_bound_vars(self, id: HirId) -> &'tcx List { ) } + /// Given the def-id of an early-bound lifetime on an RPIT corresponding to + /// a duplicated captured lifetime, map it back to the early- or late-bound + /// lifetime of the function from which it originally as captured. If it is + /// a late-bound lifetime, this will represent the liberated (`ReFree`) lifetime + /// of the signature. + // FIXME(RPITIT): if we ever synthesize new lifetimes for RPITITs and not just + // re-use the generics of the opaque, this function will need to be tweaked slightly. + pub fn map_rpit_lifetime_to_fn_lifetime( + self, + mut rpit_lifetime_param_def_id: LocalDefId, + ) -> ty::Region<'tcx> { + debug_assert!( + matches!(self.def_kind(rpit_lifetime_param_def_id), DefKind::LifetimeParam), + "{rpit_lifetime_param_def_id:?} is a {}", + self.def_descr(rpit_lifetime_param_def_id.to_def_id()) + ); + + loop { + let parent = self.local_parent(rpit_lifetime_param_def_id); + let hir::OpaqueTy { lifetime_mapping, .. } = + self.hir().get_by_def_id(parent).expect_item().expect_opaque_ty(); + + let Some((lifetime, _)) = lifetime_mapping + .iter() + .find(|(_, duplicated_param)| *duplicated_param == rpit_lifetime_param_def_id) + else { + bug!("duplicated lifetime param should be present"); + }; + + match self.named_bound_var(lifetime.hir_id) { + Some(resolve_bound_vars::ResolvedArg::EarlyBound(ebv)) => { + let new_parent = self.parent(ebv); + + // If we map to another opaque, then it should be a parent + // of the opaque we mapped from. Continue mapping. + if matches!(self.def_kind(new_parent), DefKind::OpaqueTy) { + debug_assert_eq!(self.parent(parent.to_def_id()), new_parent); + rpit_lifetime_param_def_id = ebv.expect_local(); + continue; + } + + let generics = self.generics_of(new_parent); + return ty::Region::new_early_bound( + self, + ty::EarlyBoundRegion { + def_id: ebv, + index: generics + .param_def_id_to_index(self, ebv) + .expect("early-bound var should be present in fn generics"), + name: self.hir().name(self.local_def_id_to_hir_id(ebv.expect_local())), + }, + ); + } + Some(resolve_bound_vars::ResolvedArg::LateBound(_, _, lbv)) => { + let new_parent = self.parent(lbv); + return ty::Region::new_free( + self, + new_parent, + ty::BoundRegionKind::BrNamed( + lbv, + self.hir().name(self.local_def_id_to_hir_id(lbv.expect_local())), + ), + ); + } + Some(resolve_bound_vars::ResolvedArg::Error(guar)) => { + return ty::Region::new_error(self, guar); + } + _ => { + return ty::Region::new_error_with_message( + self, + lifetime.ident.span, + "cannot resolve lifetime", + ); + } + } + } + } + /// Whether the `def_id` counts as const fn in the current crate, considering all active /// feature gates pub fn is_const_fn(self, def_id: DefId) -> bool { diff --git a/compiler/rustc_ty_utils/src/implied_bounds.rs b/compiler/rustc_ty_utils/src/implied_bounds.rs index aa49a5561d15733fa8100725f2c08ca2f4b36176..436f10a4f7b91dc5bc3961dc1de0f06a04884dd5 100644 --- a/compiler/rustc_ty_utils/src/implied_bounds.rs +++ b/compiler/rustc_ty_utils/src/implied_bounds.rs @@ -2,7 +2,6 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; -use rustc_middle::middle::resolve_bound_vars as rbv; use rustc_middle::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::Span; @@ -52,9 +51,7 @@ fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<' tcx.arena.alloc_from_iter(tys.into_iter().map(|ty| (ty, impl_spans.next().unwrap()))) } DefKind::AssocTy if let Some(data) = tcx.opt_rpitit_info(def_id.to_def_id()) => match data { - ty::ImplTraitInTraitData::Trait { fn_def_id, opaque_def_id } => { - let hir::OpaqueTy { lifetime_mapping, .. } = - *tcx.hir().expect_item(opaque_def_id.expect_local()).expect_opaque_ty(); + ty::ImplTraitInTraitData::Trait { fn_def_id, .. } => { // We need to remap all of the late-bound lifetimes in theassumed wf types // of the fn (which are represented as ReFree) to the early-bound lifetimes // of the RPITIT (which are represented by ReEarlyBound owned by the opaque). @@ -66,28 +63,22 @@ fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<' // predicates we insert in the `explicit_predicates_of` query for RPITITs. let mut mapping = FxHashMap::default(); let generics = tcx.generics_of(def_id); - for &(lifetime, new_early_bound_def_id) in - lifetime_mapping.expect("expected lifetime mapping for RPITIT") - { - if let Some(rbv::ResolvedArg::LateBound(_, _, def_id)) = - tcx.named_bound_var(lifetime.hir_id) - { - let name = tcx.hir().name(lifetime.hir_id); - let index = generics - .param_def_id_to_index(tcx, new_early_bound_def_id.to_def_id()) - .unwrap(); + + // For each captured opaque lifetime, if it's late-bound (`ReFree` in this case, + // since it has been liberated), map it back to the early-bound lifetime of + // the GAT. Since RPITITs also have all of the fn's generics, we slice only + // the end of the list corresponding to the opaque's generics. + for param in &generics.params[tcx.generics_of(fn_def_id).params.len()..] { + let orig_lt = tcx.map_rpit_lifetime_to_fn_lifetime(param.def_id.expect_local()); + if matches!(*orig_lt, ty::ReFree(..)) { mapping.insert( - ty::Region::new_free( - tcx, - fn_def_id, - ty::BoundRegionKind::BrNamed(def_id, name), - ), + orig_lt, ty::Region::new_early_bound( tcx, ty::EarlyBoundRegion { - def_id: new_early_bound_def_id.to_def_id(), - index, - name, + def_id: param.def_id, + index: param.index, + name: param.name, }, ), ); diff --git a/tests/ui/impl-trait/in-trait/outlives-in-nested-rpit.rs b/tests/ui/impl-trait/in-trait/outlives-in-nested-rpit.rs new file mode 100644 index 0000000000000000000000000000000000000000..6330242ceebc24c73d06b1aabb3d8be7f33f3bad --- /dev/null +++ b/tests/ui/impl-trait/in-trait/outlives-in-nested-rpit.rs @@ -0,0 +1,11 @@ +// check-pass + +#![feature(return_position_impl_trait_in_trait)] + +trait Foo { + fn early<'a, T: 'a>(x: &'a T) -> impl Iterator>; + + fn late<'a, T>(x: &'a T) -> impl Iterator>; +} + +fn main() {} diff --git a/tests/ui/impl-trait/mapping-duplicated-lifetimes-issue-114597.rs b/tests/ui/impl-trait/mapping-duplicated-lifetimes-issue-114597.rs new file mode 100644 index 0000000000000000000000000000000000000000..a2dd0a9308d3ed851117bb5a50a20f8242bdd13d --- /dev/null +++ b/tests/ui/impl-trait/mapping-duplicated-lifetimes-issue-114597.rs @@ -0,0 +1,15 @@ +// check-pass +// issue: 114597 +// edition: 2021 + +struct A<'a> { + dat: &'a (), +} + +impl<'a> A<'a> { + async fn a(&self) -> impl Iterator> { + std::iter::repeat(()).map(|()| std::iter::repeat(())) + } +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/wf-check-rpit-lifetimes.rs b/tests/ui/type-alias-impl-trait/wf-check-rpit-lifetimes.rs new file mode 100644 index 0000000000000000000000000000000000000000..b92e15aad56185a717a4e50d3a2d8b89b3bd25a4 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/wf-check-rpit-lifetimes.rs @@ -0,0 +1,19 @@ +//check-pass + +pub struct Key; +#[derive(Clone)] +pub struct Value; + +use std::collections::HashMap; + +pub struct DiagnosticBuilder<'db> { + inner: HashMap<&'db Key, Vec<&'db Value>>, +} + +impl<'db> DiagnosticBuilder<'db> { + pub fn iter<'a>(&'a self) -> impl Iterator)> { + self.inner.iter().map(|(key, values)| (*key, values.iter().map(|v| *v))) + } +} + +fn main() {}