all_is_cubes/
linking.rs

1//! Storing and accessing definitions of standard blocks in a [`Universe`].
2//!
3//! An enum implementing [`BlockModule`] defines a set of names, and
4//! [`BlockProvider`] assists in ensuring that all of those names are defined
5//! and storing or retrieving their block values in a specific [`Universe`].
6//!
7//! In the future this mechanism may grow to become a dynamic linker/dependency injector
8//! by becoming aware of dependencies between “modules”. For now, it's just enough to
9//! solve bootstrapping needs.
10
11use alloc::boxed::Box;
12use alloc::vec::Vec;
13use core::error::Error;
14use core::fmt;
15use core::hash::Hash;
16use core::ops::Index;
17
18use exhaust::Exhaust;
19use hashbrown::HashMap as HbHashMap;
20
21use crate::block::{self, Block, BlockDef};
22use crate::space::{self, SetCubeError, SpaceTransaction};
23use crate::transaction::ExecuteError;
24use crate::universe::{
25    self, Handle, InsertError, Name, ReadTicket, Universe, UniverseTransaction, VisitHandles,
26};
27use crate::util::YieldProgress;
28
29#[cfg(doc)]
30use crate::block::Primitive;
31
32fn name_in_module<E: BlockModule>(key: &E) -> Name {
33    Name::from(format!("{ns}/{key}", ns = E::namespace()))
34}
35
36/// Allows the use of [`Provider::default`] to construct a [`Provider`]
37/// using this type as its set of keys. [`Self::module_default()`] will be called once for
38/// each value of [`Self`].
39///
40/// See [`BlockModule`] for related expectations.
41pub trait DefaultProvision<T> {
42    /// Returns the default value to use for the given key.
43    fn module_default(self) -> T;
44}
45
46/// Types whose values identify blocks in a set of related blocks, which may be
47/// stored in a [`BlockProvider`] or under specific names in a [`Universe`].
48///
49/// The names of the [`Universe`]'s corresponding [`BlockDef`]s are formed by
50/// combining the [`namespace()`](Self::namespace) and `self.to_string()` (the
51/// [`Display`](fmt::Display) trait implementation).
52///
53/// Implement this trait for an enum, then use the functions of
54/// [`BlockProvider`] to work with the described set of blocks.
55///
56/// TODO: consider replacing Display with a separate method so as not to presume its meaning
57pub trait BlockModule: Exhaust + fmt::Debug + fmt::Display + Eq + Hash + Clone {
58    /// A namespace for the members of this module; currently, this should be a
59    /// `/`-separated path with no trailing slash, but (TODO:) we should have a
60    /// more rigorous namespace scheme for [`Name`]s in future versions.
61    fn namespace() -> &'static str;
62}
63
64/// An instance of a [`BlockModule`]; a container of a `Block` for every possible `E`.
65///
66/// TODO: Deprecate and remove this alias.
67pub type BlockProvider<E> = Provider<E, Block>;
68
69/// Key-value container of a `V` value for every possible `E`.
70#[derive(Clone, Debug)]
71pub struct Provider<E, V> {
72    /// Guaranteed to contain an entry for every variant of `E` if `E`'s
73    /// [`Exhaust`] implementation is accurate.
74    map: HbHashMap<E, V>,
75}
76
77impl<E, V> Default for Provider<E, V>
78where
79    E: DefaultProvision<V> + Exhaust + Eq + Hash + Clone,
80{
81    fn default() -> Self {
82        Self {
83            map: E::exhaust()
84                .map(|key| {
85                    let value = DefaultProvision::module_default(key.clone());
86                    (key, value)
87                })
88                .collect(),
89        }
90    }
91}
92
93impl<E, V> Provider<E, V>
94where
95    E: BlockModule,
96{
97    /// Constructs a `Provider` with values computed by the given function.
98    ///
99    /// This is an async function for the sake of cancellation and optional cooperative
100    /// multitasking. It may be blocked on from a synchronous context (but if that is the
101    /// only use, consider calling [`Provider::new_sync()`] instead).
102    pub async fn new<F>(progress: YieldProgress, mut definer: F) -> Result<Self, GenError>
103    where
104        F: FnMut(E) -> Result<V, InGenError>,
105    {
106        let count = E::exhaust().count();
107        let mut map = HbHashMap::with_capacity(count);
108        #[expect(
109            clippy::shadow_unrelated,
110            reason = "https://github.com/rust-lang/rust-clippy/issues/11827"
111        )]
112        for (key, progress) in E::exhaust().zip(progress.split_evenly(count)) {
113            match definer(key.clone()) {
114                Ok(value) => {
115                    map.insert(key, value);
116                    progress.finish().await;
117                }
118                Err(e) => return Err(GenError::failure(e, name_in_module(&key))),
119            }
120        }
121        Ok(Self { map })
122    }
123}
124
125// TODO: Generalize these to non-blocks however makes sense
126impl<E: BlockModule> Provider<E, Block> {
127    /// Constructs a [`BlockProvider`] with [`BlockDef`]s whose block values are computed by the
128    /// given function.
129    ///
130    /// The module itself is passed to `definer`, which may be used to create
131    /// relationships among the blocks (e.g. one having the behavior of turning into another).
132    /// Attempting to read those handles will necessarily fail.
133    //---
134    // TODO: This has to exist, but is an awkward shape and block-specific.
135    // Figure out what the actually good API looks like.
136    pub async fn new_installed_cyclic<F>(
137        progress: YieldProgress,
138        txn: &mut UniverseTransaction,
139        mut definer: F,
140    ) -> Result<Self, GenError>
141    where
142        F: FnMut(&Provider<E, Block>, &mut UniverseTransaction, E) -> Result<Block, InGenError>,
143    {
144        let module: Self = Self::new_sync(|key| {
145            let block_def_handle: Handle<BlockDef> = txn
146                .insert_without_value(name_in_module(&key))
147                .expect("module failed to produce distinct keys");
148            Block::from(block_def_handle)
149        });
150
151        let count = module.map.len();
152        #[expect(
153            clippy::shadow_unrelated,
154            reason = "https://github.com/rust-lang/rust-clippy/issues/11827"
155        )]
156        for (key, progress) in E::exhaust().zip(progress.split_evenly(count)) {
157            match definer(&module, txn, key.clone()) {
158                Ok(block_value) => {
159                    let block::Primitive::Indirect(block_def_handle) = module[key].primitive()
160                    else {
161                        unreachable!()
162                    };
163                    txn.set_pending_value(
164                        block_def_handle,
165                        BlockDef::new(
166                            txn.read_ticket().expect_may_fail(), // TODO: should caller provide ticket to rest of universe which we merge somehow? or should we stop expecting successful evaluations while building modules/txns, entirely
167                            block_value,
168                        ),
169                    );
170                    progress.finish().await;
171                }
172                Err(e) => return Err(GenError::failure(e, name_in_module(&key))),
173            }
174        }
175        Ok(module)
176    }
177
178    /// Add the block definitions stored in this [`BlockProvider`] into `universe` as
179    /// [`BlockDef`]s, returning a new [`BlockProvider`] whose blocks refer to those
180    /// definitions (via [`Primitive::Indirect`]).
181    ///
182    /// The given `read_ticket` should be sufficient for evaluating the blocks
183    /// in `self`.
184    pub fn install(
185        &self,
186        read_ticket: ReadTicket<'_>,
187        txn: &mut UniverseTransaction,
188    ) -> Result<Self, InsertError> {
189        // The non-generic part of the code.
190        #[inline(never)]
191        fn create_block_def_and_indirect(
192            read_ticket: ReadTicket<'_>,
193            txn: &mut UniverseTransaction,
194            name: Name,
195            block: &Block,
196        ) -> Result<Block, InsertError> {
197            let value = BlockDef::new(read_ticket.with_transaction(txn), block.clone());
198            let block_def_handle = txn.insert_mut(name, value)?;
199            let indirect_block = Block::from(block_def_handle);
200            Ok(indirect_block)
201        }
202
203        let mut map = HbHashMap::with_capacity(self.map.len());
204        for key in E::exhaust() {
205            let indirect_block =
206                create_block_def_and_indirect(read_ticket, txn, name_in_module(&key), &self[&key])?;
207            map.insert(key, indirect_block);
208        }
209        Ok(Self { map })
210    }
211
212    /// Obtain the definitions of `E`'s blocks from `universe`, returning a new
213    /// [`BlockProvider`] whose blocks refer to those definitions (via
214    /// [`Primitive::Indirect`]).
215    ///
216    /// Returns an error if any of the blocks are not defined in that universe.
217    pub fn using(universe: &Universe) -> Result<Self, ProviderError>
218    where
219        E: Eq + Hash + fmt::Display,
220    {
221        let mut found: HbHashMap<E, Handle<BlockDef>> = HbHashMap::new();
222        let mut missing = Vec::new();
223        for key in E::exhaust() {
224            let name = name_in_module(&key);
225            if let Some(handle) = universe.get(&name) {
226                found.insert(key, handle);
227            } else {
228                missing.push(name);
229            }
230        }
231        if !missing.is_empty() {
232            return Err(ProviderError {
233                missing: missing.into(),
234            });
235        }
236        Ok(Provider {
237            map: E::exhaust()
238                .map(|key| {
239                    let block = Block::from(found.remove(&key).unwrap());
240                    (key, block)
241                })
242                .collect(),
243        })
244    }
245}
246
247/// These methods do not require `E` to be a [`BlockModule`].
248impl<E: Exhaust + fmt::Debug + Clone + Eq + Hash, V> Provider<E, V> {
249    /// Alternative to [`Self::new()`] which is neither async nor fallible.
250    pub fn new_sync<F>(mut definer: F) -> Self
251    where
252        F: FnMut(E) -> V,
253    {
254        Provider {
255            map: E::exhaust().map(|key| (key.clone(), definer(key))).collect(),
256        }
257    }
258
259    /// Create another [`Provider`] with different keys that map into a subset of
260    /// this provider's keys.
261    ///
262    /// TODO: add a test
263    #[must_use]
264    pub fn subset<K>(&self, function: impl Fn(K) -> E) -> Provider<K, V>
265    where
266        K: Exhaust + fmt::Debug + Clone + Eq + Hash,
267        V: Clone,
268    {
269        Provider::new_sync(|key: K| self[function(key)].clone())
270    }
271
272    /// Create another [`Provider`] with a modification to each value.
273    #[must_use]
274    pub fn map<V2>(&self, mut function: impl FnMut(&E, &V) -> V2) -> Provider<E, V2> {
275        Provider {
276            map: self
277                .map
278                .iter()
279                .map(|(key, value)| (key.clone(), function(key, value)))
280                .collect(),
281        }
282    }
283
284    /// Iterate over the entire contents of this.
285    pub fn iter(&self) -> ModuleIter<'_, E, V> {
286        ModuleIter {
287            key_iter: E::exhaust(),
288            map: &self.map,
289        }
290    }
291
292    #[cfg(test)]
293    fn consistency_check(&self) {
294        use hashbrown::HashSet;
295        let expected_keys: HashSet<E> = E::exhaust().collect();
296        let actual_keys: HashSet<E> = self.map.keys().cloned().collect();
297        assert_eq!(
298            expected_keys, actual_keys,
299            "Provider keys are not as expected"
300        );
301    }
302}
303
304impl<E: Eq + Hash, V: PartialEq> PartialEq for Provider<E, V> {
305    fn eq(&self, other: &Self) -> bool {
306        let Self { map } = self;
307        *map == other.map
308    }
309}
310impl<E: Eq + Hash, V: PartialEq> Eq for Provider<E, V> {}
311
312impl<E: Eq + Hash, V> Index<E> for Provider<E, V> {
313    type Output = V;
314
315    fn index(&self, index: E) -> &Self::Output {
316        &self.map[&index]
317    }
318}
319impl<E: Eq + Hash, V> Index<&E> for Provider<E, V> {
320    type Output = V;
321
322    fn index(&self, index: &E) -> &Self::Output {
323        &self.map[index]
324    }
325}
326
327impl<'provider, E: Exhaust + fmt::Debug + Clone + Eq + Hash, V> IntoIterator
328    for &'provider Provider<E, V>
329{
330    type Item = (E, &'provider V);
331    type IntoIter = ModuleIter<'provider, E, V>;
332    fn into_iter(self) -> Self::IntoIter {
333        self.iter()
334    }
335}
336
337impl<E: Eq + Hash + VisitHandles, V: VisitHandles> VisitHandles for Provider<E, V> {
338    fn visit_handles(&self, visitor: &mut dyn universe::HandleVisitor) {
339        let Self { map } = self;
340        for (key, value) in map {
341            key.visit_handles(visitor);
342            value.visit_handles(visitor);
343        }
344    }
345}
346
347// -------------------------------------------------------------------------------------------------
348
349/// Iterator returned by [`Provider::iter()`].
350#[expect(missing_debug_implementations)]
351pub struct ModuleIter<'provider, E: Exhaust, V> {
352    /// Using the `Exhaust` iterator instead of the `HashMap` iterator guarantees a deterministic
353    /// iteration order. (We don't currently publicly promise that, though.)
354    key_iter: exhaust::Iter<E>,
355    map: &'provider HbHashMap<E, V>,
356}
357
358impl<'provider, E: Exhaust + Eq + Hash, V> Iterator for ModuleIter<'provider, E, V> {
359    type Item = (E, &'provider V);
360
361    fn next(&mut self) -> Option<Self::Item> {
362        self.key_iter.next().map(|key| {
363            let value: &V = &self.map[&key];
364            (key, value)
365        })
366    }
367
368    fn size_hint(&self) -> (usize, Option<usize>) {
369        self.key_iter.size_hint()
370    }
371}
372
373impl<E, V> ExactSizeIterator for ModuleIter<'_, E, V>
374where
375    E: Exhaust + Eq + Hash,
376    exhaust::Iter<E>: ExactSizeIterator,
377{
378}
379
380/// Error when a [`Provider`] could not be created because the definitions of some
381/// of its members are missing.
382#[derive(Clone, Debug, Eq, displaydoc::Display, PartialEq)]
383#[displaydoc("module definitions missing from universe: {missing:?}")] // TODO: use Name's Display within the list
384pub struct ProviderError {
385    missing: Box<[Name]>,
386}
387
388impl Error for ProviderError {}
389
390/// An error resulting from “world generation”.
391///
392/// May be failure to calculate/create/place objects
393/// (due to bad parameters or unforeseen edge cases),
394/// failure to successfully store them in or retrieve them from a [`Universe`],
395/// et cetera.
396///
397/// A [`GenError`] contains an [`InGenError`] and additionally specifies which universe
398/// member was to be generated but failed.
399#[derive(Debug)]
400pub struct GenError {
401    detail: InGenError,
402    for_object: Option<Name>,
403}
404
405impl Error for GenError {
406    fn source(&self) -> Option<&(dyn Error + 'static)> {
407        Some(&self.detail)
408    }
409}
410
411impl GenError {
412    /// Wrap an error, that occurred while creating an object, as a [`GenError`] which also
413    /// names the object.
414    pub fn failure(error: impl Into<InGenError>, object: Name) -> Self {
415        Self {
416            detail: error.into(),
417            for_object: Some(object),
418        }
419    }
420}
421
422impl fmt::Display for GenError {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        // Don't include `detail` because that's our `Error::source()`.
425        // The assumption is that the cause chain will be walked when printing an error.
426        if let Some(name) = &self.for_object {
427            write!(f, "An error occurred while generating object {name}")?;
428        } else {
429            write!(f, "An error occurred while generating an object")?;
430        }
431        Ok(())
432    }
433}
434
435impl From<InsertError> for GenError {
436    // TODO: Maybe InsertError should just be a variant of GenError?
437    fn from(error: InsertError) -> Self {
438        GenError {
439            for_object: Some(error.name.clone()),
440            detail: error.into(),
441        }
442    }
443}
444
445impl From<ExecuteError<UniverseTransaction>> for GenError {
446    // TODO: Ideally, this works only for `UniverseTransaction` errors, which relate to
447    // specific members, but we don't have a static distinction between different transactions'
448    // errors yet.
449    fn from(error: ExecuteError<UniverseTransaction>) -> Self {
450        GenError {
451            for_object: None,
452            detail: error.into(),
453        }
454    }
455}
456
457/// Aggregation of types of errors that might occur in “world generation”.
458///
459/// This is distinct from [`GenError`] in that this type is returned from functions
460/// _responsible for generation,_ and that type is returned from functions that
461/// _manage_ generation — that invoke the first kind and (usually) store its result
462/// in the [`Universe`]. This separation is intended to encourage more precise
463/// attribution of the source of the error despite implicit conversions, because a
464/// “nested” [`GenError`] will be obligated to be wrapped in `InGenError` rather than
465/// mistakenly taken as the same level.
466///
467/// TODO: Work this into a coherent set of error cases rather than purely
468/// "I saw one of these once, so add it".
469#[derive(Debug)]
470#[non_exhaustive]
471pub enum InGenError {
472    /// Generic error container for unusual situations.
473    Other(Box<dyn Error + Send + Sync>),
474
475    /// Something else needed to be generated and that failed.
476    Gen(Box<GenError>), // boxed due to being a recursive type
477
478    /// Failed to insert the generated items in the [`Universe`].
479    Insert(InsertError),
480
481    /// Failed to find a needed dependency.
482    // TODO: Any special handling? Phrase this as "missing dependency"?
483    Provider(ProviderError),
484
485    /// Failed during [`Space`](crate::space::Space) construction.
486    Space(space::builder::Error),
487
488    /// Failed during [`Space`](crate::space::Space) manipulation.
489    SetCube(SetCubeError),
490
491    /// Failed during a transaction executed as part of generation.
492    UniverseTransaction(ExecuteError<UniverseTransaction>),
493
494    /// Failed during a transaction executed as part of generation.
495    SpaceTransaction(ExecuteError<SpaceTransaction>),
496}
497
498impl InGenError {
499    /// Convert an arbitrary error to `InGenError`.
500    #[cfg_attr(not(feature = "std"), doc(hidden))]
501    pub fn other<E: Error + Send + Sync + 'static>(error: E) -> Self {
502        Self::Other(Box::new(error))
503    }
504}
505
506impl Error for InGenError {
507    fn source(&self) -> Option<&(dyn Error + 'static)> {
508        match self {
509            InGenError::Other(e) => e.source(),
510            InGenError::Gen(e) => e.source(),
511            InGenError::Insert(e) => e.source(),
512            InGenError::Provider(e) => e.source(),
513            InGenError::Space(e) => e.source(),
514            InGenError::SetCube(e) => e.source(),
515            InGenError::UniverseTransaction(e) => e.source(),
516            InGenError::SpaceTransaction(e) => e.source(),
517        }
518    }
519}
520
521impl fmt::Display for InGenError {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        match self {
524            InGenError::Other(e) => e.fmt(f),
525            InGenError::Gen(e) => e.fmt(f),
526            InGenError::Insert(e) => e.fmt(f),
527            InGenError::Provider(e) => e.fmt(f),
528            InGenError::Space(e) => e.fmt(f),
529            InGenError::SetCube(e) => e.fmt(f),
530            InGenError::UniverseTransaction(e) => e.fmt(f),
531            InGenError::SpaceTransaction(e) => e.fmt(f),
532        }
533    }
534}
535
536impl From<GenError> for InGenError {
537    fn from(error: GenError) -> Self {
538        // We need to box this to avoid an unboxed recursive type.
539        InGenError::Gen(Box::new(error))
540    }
541}
542impl From<InsertError> for InGenError {
543    fn from(error: InsertError) -> Self {
544        InGenError::Insert(error)
545    }
546}
547impl From<ProviderError> for InGenError {
548    fn from(error: ProviderError) -> Self {
549        InGenError::Provider(error)
550    }
551}
552impl From<space::builder::Error> for InGenError {
553    fn from(error: space::builder::Error) -> Self {
554        InGenError::Space(error)
555    }
556}
557impl From<SetCubeError> for InGenError {
558    fn from(error: SetCubeError) -> Self {
559        InGenError::SetCube(error)
560    }
561}
562impl From<crate::content::load_image::BlockFromImageError> for InGenError {
563    fn from(error: crate::content::load_image::BlockFromImageError) -> Self {
564        // TODO: give this its own variant?
565        InGenError::Other(Box::new(error))
566    }
567}
568impl From<ExecuteError<UniverseTransaction>> for InGenError {
569    fn from(error: ExecuteError<UniverseTransaction>) -> Self {
570        InGenError::UniverseTransaction(error)
571    }
572}
573impl From<ExecuteError<SpaceTransaction>> for InGenError {
574    fn from(error: ExecuteError<SpaceTransaction>) -> Self {
575        InGenError::SpaceTransaction(error)
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582    use crate::block::{AIR, Quote, Resolution::*};
583    use crate::content::make_some_blocks;
584    use crate::math::GridAab;
585    use crate::transaction::Transactional as _;
586    use crate::util::assert_conditional_send_sync;
587
588    #[derive(Exhaust, Clone, Debug, Eq, Hash, PartialEq)]
589    enum Key {
590        A,
591        B,
592        C,
593    }
594    impl fmt::Display for Key {
595        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596            write!(f, "{self:?}")
597        }
598    }
599    impl BlockModule for Key {
600        fn namespace() -> &'static str {
601            "test-key"
602        }
603    }
604
605    fn test_provider() -> ([Block; 3], BlockProvider<Key>) {
606        let blocks = make_some_blocks();
607        let provider = BlockProvider::new_sync(|k: Key| match k {
608            Key::A => blocks[0].clone(),
609            Key::B => blocks[1].clone(),
610            Key::C => blocks[2].clone(),
611        });
612        provider.consistency_check();
613
614        (blocks, provider)
615    }
616
617    #[test]
618    fn provider_install() {
619        let mut universe = Universe::new();
620        let (_, provider) = test_provider();
621
622        // TODO: double-unwrap in this case is a bad sign (InsertError != UniverseConflict)
623        let installed = universe
624            .transact(|txn, u| Ok(provider.install(u.read_ticket(), txn)))
625            .unwrap()
626            .unwrap();
627
628        assert_eq!(installed, BlockProvider::using(&universe).unwrap());
629    }
630
631    #[test]
632    fn provider_subset() {
633        let (_, p1) = test_provider();
634        let p2 = p1.subset(|x: bool| if x { Key::A } else { Key::B });
635        p2.consistency_check();
636        assert_eq!(p1[Key::A], p2[true]);
637        assert_eq!(p1[Key::B], p2[false]);
638    }
639
640    #[test]
641    fn provider_map() {
642        let (_, p1) = test_provider();
643        let p2 = p1.map(|_, block| block.clone().with_modifier(Quote::default()));
644        p2.consistency_check();
645        assert_eq!(
646            p1[Key::A].clone().with_modifier(Quote::default()),
647            p2[Key::A],
648        );
649    }
650
651    #[test]
652    fn provider_eq() {
653        let (_, p1) = test_provider();
654        let (_, p2) = test_provider();
655        assert_eq!(p1, p2);
656        assert_ne!(
657            p1,
658            p2.map(|key, block| if *key == Key::B { AIR } else { block.clone() })
659        );
660    }
661
662    #[test]
663    fn errors_are_send_sync() {
664        assert_conditional_send_sync::<GenError>();
665        assert_conditional_send_sync::<InGenError>();
666    }
667
668    #[test]
669    fn gen_error_message() {
670        use alloc::string::ToString;
671
672        let set_cube_error = SetCubeError::OutOfBounds {
673            modification: GridAab::for_block(R1),
674            space_bounds: GridAab::for_block(R4),
675        };
676        let e = GenError::failure(set_cube_error.clone(), "x".into());
677        assert_eq!(
678            e.to_string(),
679            "An error occurred while generating object 'x'",
680        );
681        let source = Error::source(&e)
682            .expect("has source")
683            .downcast_ref::<InGenError>()
684            .expect("is InGenError");
685        assert_eq!(source.to_string(), set_cube_error.to_string());
686    }
687
688    #[test]
689    #[expect(clippy::try_err)]
690    fn gen_error_composition() {
691        // TODO: this isn't the greatest example situation
692        fn a() -> Result<(), GenError> {
693            b().map_err(|e| GenError::failure(e, "x".into()))?;
694            Ok(())
695        }
696        fn b() -> Result<(), InGenError> {
697            Err(SetCubeError::OutOfBounds {
698                modification: GridAab::for_block(R1),
699                space_bounds: GridAab::for_block(R1),
700            })?;
701            Ok(())
702        }
703        let r = a();
704        assert!(
705            matches!(
706                r,
707                Err(GenError {
708                    detail: InGenError::SetCube(_),
709                    for_object: Some(Name::Specific(_)),
710                })
711            ),
712            "got error: {r:?}"
713        );
714    }
715}