Skip to main content

turbopack_core/source_map/
mod.rs

1use std::{borrow::Cow, io::Write, ops::Deref, sync::Arc};
2
3use anyhow::Result;
4use bincode::{
5    Decode, Encode,
6    de::Decoder,
7    enc::Encoder,
8    error::{DecodeError, EncodeError},
9};
10use bytes_str::BytesStr;
11use either::Either;
12use once_cell::sync::Lazy;
13use ref_cast::RefCast;
14use regex::Regex;
15use swc_sourcemap::{DecodedMap, SourceMap as RegularMap, SourceMapBuilder, SourceMapIndex};
16use turbo_rcstr::{RcStr, rcstr};
17use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
18use turbo_tasks_fs::{
19    File, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
20    rope::{Rope, RopeBuilder},
21};
22
23use crate::{
24    SOURCE_URL_PROTOCOL, asset::AssetContent, source::Source,
25    source_map::utils::add_default_ignore_list, source_pos::SourcePos,
26    virtual_source::VirtualSource,
27};
28
29pub(crate) mod source_map_asset;
30pub mod utils;
31
32pub use source_map_asset::SourceMapAsset;
33
34/// Represents an empty value in a u32 variable in the sourcemap crate.
35static SOURCEMAP_CRATE_NONE_U32: u32 = !0;
36
37/// Allows callers to generate source maps.
38#[turbo_tasks::value_trait]
39pub trait GenerateSourceMap {
40    /// Generates a usable source map, capable of both tracing and stringifying.
41    #[turbo_tasks::function]
42    fn generate_source_map(self: Vc<Self>) -> Vc<FileContent>;
43
44    /// Returns an individual section of the larger source map, if found.
45    #[turbo_tasks::function]
46    fn by_section(self: Vc<Self>, _section: RcStr) -> Vc<FileContent> {
47        FileContent::NotFound.cell()
48    }
49}
50
51/// Implements the source map specification as either a decoded or sectioned sourcemap.
52///
53/// - A "decoded" map represents a source map as if it was came out of a JSON
54/// decode.
55/// - A "sectioned" source map is a tree of many [SourceMap]
56/// covering regions of an output file.
57///
58/// The distinction between the source map spec's [sourcemap::Index] and our
59/// [SourceMap::Sectioned] is whether the sections are represented with Vcs
60/// pointers.
61#[turbo_tasks::value(shared, cell = "new", eq = "manual")]
62#[derive(Debug)]
63pub struct SourceMap {
64    /// A decoded source map contains no Vcs.
65    #[turbo_tasks(trace_ignore)]
66    map: Arc<CrateMapWrapper>,
67}
68impl Eq for SourceMap {}
69impl PartialEq for SourceMap {
70    fn eq(&self, other: &Self) -> bool {
71        Arc::ptr_eq(&self.map, &other.map)
72    }
73}
74
75#[turbo_tasks::value(transparent)]
76pub struct OptionSourceMap(Option<SourceMap>);
77
78#[turbo_tasks::value_impl]
79impl OptionSourceMap {
80    #[turbo_tasks::function]
81    pub fn none() -> Vc<Self> {
82        Vc::cell(None)
83    }
84}
85
86impl OptionSourceMap {
87    pub fn none_resolved() -> ResolvedVc<Self> {
88        ResolvedVc::cell(None)
89    }
90}
91
92/// A token represents a mapping in a source map.
93///
94/// It may either be Synthetic, meaning it was generated by some build tool and doesn't
95/// represent a location in a user-authored source file, or it is Original, meaning it represents a
96/// real location in source file.
97#[turbo_tasks::value]
98#[derive(Clone, Debug)]
99pub enum Token {
100    Synthetic(SyntheticToken),
101    Original(OriginalToken),
102}
103
104#[turbo_tasks::value]
105#[derive(Clone, Debug)]
106pub struct TokenWithSource {
107    pub token: Token,
108    pub source_content: Option<ResolvedVc<Box<dyn Source>>>,
109}
110
111/// A SyntheticToken represents a region of the generated file that was created
112/// by some build tool.
113#[turbo_tasks::value]
114#[derive(Clone, Debug)]
115pub struct SyntheticToken {
116    pub generated_line: u32,
117    pub generated_column: u32,
118    pub guessed_original_file: Option<RcStr>,
119}
120
121/// An OriginalToken represents a region of the generated file that exists in
122/// user-authored source file.
123#[turbo_tasks::value]
124#[derive(Clone, Debug)]
125pub struct OriginalToken {
126    pub generated_line: u32,
127    pub generated_column: u32,
128    pub original_file: RcStr,
129    pub original_line: u32,
130    pub original_column: u32,
131    pub name: Option<RcStr>,
132    /// Whether this token's source is in the sourcemap's `ignoreList`.
133    pub is_ignored: bool,
134}
135
136impl Token {
137    pub fn generated_line(&self) -> u32 {
138        match self {
139            Self::Original(t) => t.generated_line,
140            Self::Synthetic(t) => t.generated_line,
141        }
142    }
143
144    pub fn generated_column(&self) -> u32 {
145        match self {
146            Self::Original(t) => t.generated_column,
147            Self::Synthetic(t) => t.generated_column,
148        }
149    }
150
151    pub fn with_offset(&self, line_offset: u32, column_offset: u32) -> Self {
152        match self {
153            Self::Original(t) => Self::Original(OriginalToken {
154                generated_line: t.generated_line + line_offset,
155                generated_column: if t.generated_line == 0 {
156                    t.generated_column + column_offset
157                } else {
158                    t.generated_column
159                },
160                original_file: t.original_file.clone(),
161                original_line: t.original_line,
162                original_column: t.original_column,
163                name: t.name.clone(),
164                is_ignored: t.is_ignored,
165            }),
166            Self::Synthetic(t) => Self::Synthetic(SyntheticToken {
167                generated_line: t.generated_line + line_offset,
168                generated_column: if t.generated_line == 0 {
169                    t.generated_column + column_offset
170                } else {
171                    t.generated_column
172                },
173                guessed_original_file: t.guessed_original_file.clone(),
174            }),
175        }
176    }
177}
178
179impl From<swc_sourcemap::Token<'_>> for Token {
180    fn from(t: swc_sourcemap::Token) -> Self {
181        if t.has_source() {
182            Token::Original(OriginalToken {
183                generated_line: t.get_dst_line(),
184                generated_column: t.get_dst_col(),
185                original_file: RcStr::from(
186                    t.get_source()
187                        .expect("already checked token has source")
188                        .clone(),
189                ),
190                original_line: t.get_src_line(),
191                original_column: t.get_src_col(),
192                name: t.get_name().cloned().map(RcStr::from),
193                // Set to false initially; will be updated by
194                // lookup_token_and_source_internal which has access to the full
195                // source map's ignoreList.
196                is_ignored: false,
197            })
198        } else {
199            Token::Synthetic(SyntheticToken {
200                generated_line: t.get_dst_line(),
201                generated_column: t.get_dst_col(),
202                guessed_original_file: None,
203            })
204        }
205    }
206}
207
208impl TryInto<swc_sourcemap::RawToken> for Token {
209    type Error = std::num::ParseIntError;
210
211    fn try_into(self) -> Result<swc_sourcemap::RawToken, Self::Error> {
212        Ok(match self {
213            Self::Original(t) => swc_sourcemap::RawToken {
214                dst_col: t.generated_column,
215                dst_line: t.generated_line,
216                name_id: match t.name {
217                    None => SOURCEMAP_CRATE_NONE_U32,
218                    Some(name) => name.parse()?,
219                },
220                src_col: t.original_column,
221                src_line: t.original_line,
222                src_id: t.original_file.parse()?,
223                is_range: false,
224            },
225            Self::Synthetic(t) => swc_sourcemap::RawToken {
226                dst_col: t.generated_column,
227                dst_line: t.generated_line,
228                name_id: SOURCEMAP_CRATE_NONE_U32,
229                src_col: SOURCEMAP_CRATE_NONE_U32,
230                src_line: SOURCEMAP_CRATE_NONE_U32,
231                src_id: SOURCEMAP_CRATE_NONE_U32,
232                is_range: false,
233            },
234        })
235    }
236}
237
238impl SourceMap {
239    /// Creates a new SourceMap::Decoded Vc out of a [RegularMap] instance.
240    fn new_regular(map: RegularMap) -> Self {
241        Self::new_decoded(DecodedMap::Regular(map))
242    }
243
244    /// Creates a new SourceMap::Decoded Vc out of a [DecodedMap] instance.
245    fn new_decoded(map: DecodedMap) -> Self {
246        SourceMap {
247            map: Arc::new(CrateMapWrapper(map)),
248        }
249    }
250
251    pub fn new_from_rope(content: &Rope) -> Result<Option<Self>> {
252        let Ok(map) = DecodedMap::from_reader(content.read()) else {
253            return Ok(None);
254        };
255        Ok(Some(SourceMap::new_decoded(map)))
256    }
257}
258
259#[turbo_tasks::value_impl]
260impl SourceMap {
261    /// This function should be used sparingly to reduce memory usage, only in cold code paths
262    /// (issue resolving, etc).
263    #[turbo_tasks::function]
264    pub async fn new_from_rope_cached(content: Vc<FileContent>) -> Result<Vc<OptionSourceMap>> {
265        let content = content.await?;
266        let Some(content) = content.as_content() else {
267            return Ok(OptionSourceMap::none());
268        };
269        Ok(Vc::cell(SourceMap::new_from_rope(content.content())?))
270    }
271}
272
273impl SourceMap {
274    pub fn to_source_map(&self) -> Arc<CrateMapWrapper> {
275        self.map.clone()
276    }
277}
278
279static EMPTY_SOURCE_MAP_ROPE: Lazy<Rope> =
280    Lazy::new(|| Rope::from(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#));
281
282impl SourceMap {
283    /// A source map that contains no actual source location information (no
284    /// `sources`, no mappings that point into a source). This is used to tell
285    /// Chrome that the generated code starting at a particular offset is no
286    /// longer part of the previous section's mappings.
287    pub fn empty() -> Self {
288        let mut builder = SourceMapBuilder::new(None);
289        builder.add(0, 0, 0, 0, None, None, false);
290        SourceMap::new_regular(builder.into_sourcemap())
291    }
292
293    /// A source map that contains no actual source location information (no
294    /// `sources`, no mappings that point into a source). This is used to tell
295    /// Chrome that the generated code starting at a particular offset is no
296    /// longer part of the previous section's mappings.
297    pub fn empty_rope() -> Rope {
298        EMPTY_SOURCE_MAP_ROPE.clone()
299    }
300
301    pub fn sections_to_rope(
302        sections: impl IntoIterator<Item = (SourcePos, Rope)>,
303        debug_id: Option<RcStr>,
304    ) -> Rope {
305        let mut sections = sections.into_iter().peekable();
306
307        let mut first = sections.next();
308        if let Some((offset, map)) = &mut first
309            && sections.peek().is_none()
310            && *offset == (0, 0)
311            && debug_id.is_none()
312        {
313            // There is just a single sourcemap that starts at the beginning of the file.
314            return std::mem::take(map);
315        }
316
317        // My kingdom for a decent dedent macro with interpolation!
318        // NOTE: The empty `sources` array is technically incorrect, but there is a bug
319        // in Node.js that requires sectioned source maps to have a `sources` array.
320        let mut rope = RopeBuilder::from(
321            r#"{
322  "version": 3,
323  "sources": [],
324"#,
325        );
326        if let Some(debug_id) = debug_id {
327            writeln!(rope, r#"  "debugId": "{debug_id}","#).unwrap();
328        }
329        rope += "  \"sections\": [";
330
331        let mut first_section = true;
332        for (offset, section_map) in first.into_iter().chain(sections) {
333            if !first_section {
334                rope += ",";
335            }
336            first_section = false;
337
338            write!(
339                rope,
340                r#"
341    {{"offset": {{"line": {}, "column": {}}}, "map": "#,
342                offset.line, offset.column,
343            )
344            .unwrap();
345
346            rope += &section_map;
347
348            rope += "}";
349        }
350
351        rope += "]";
352
353        rope += "\n}";
354
355        rope.build()
356    }
357
358    /// Stringifies the source map into JSON bytes.
359    pub fn to_rope(&self) -> Result<Rope> {
360        let mut bytes = vec![];
361        self.map.0.to_writer(&mut bytes)?;
362        Ok(Rope::from(bytes))
363    }
364
365    /// Traces a generated line/column into an mapping token representing either
366    /// synthetic code or user-authored original code.
367    pub fn lookup_token(&self, line: u32, column: u32) -> Token {
368        let (token, _) = self.lookup_token_and_source_internal(line, column, true);
369        token
370    }
371
372    /// Traces a generated line/column into an mapping token representing either
373    /// synthetic code or user-authored original code.
374    pub async fn lookup_token_and_source(&self, line: u32, column: u32) -> Result<TokenWithSource> {
375        let (token, content) = self.lookup_token_and_source_internal(line, column, true);
376        Ok(TokenWithSource {
377            token,
378            source_content: match content {
379                Some(v) => Some(v.to_resolved().await?),
380                None => None,
381            },
382        })
383    }
384
385    pub async fn with_resolved_sources(&self, origin: FileSystemPath) -> Result<Self> {
386        async fn resolve_source(
387            source_request: BytesStr,
388            source_content: Option<BytesStr>,
389            origin: FileSystemPath,
390        ) -> Result<(BytesStr, BytesStr)> {
391            Ok(
392                if let Some(path) = origin.parent().try_join(&source_request) {
393                    let path_str = path.value_to_string().await?;
394                    let source = format!("{SOURCE_URL_PROTOCOL}///{path_str}");
395                    let source_content = if let Some(source_content) = source_content {
396                        source_content
397                    } else if let FileContent::Content(file) = &*path.read().await? {
398                        let text = file.content().to_str()?;
399                        text.to_string().into()
400                    } else {
401                        format!("unable to read source {path_str}").into()
402                    };
403                    (source.into(), source_content)
404                } else {
405                    let origin_str = origin.value_to_string().await?;
406                    static INVALID_REGEX: Lazy<Regex> =
407                        Lazy::new(|| Regex::new(r#"(?:^|/)(?:\.\.?(?:/|$))+"#).unwrap());
408                    let source = INVALID_REGEX
409                        .replace_all(&source_request, |s: &regex::Captures<'_>| {
410                            s[0].replace('.', "_")
411                        });
412                    let source = format!("{SOURCE_URL_PROTOCOL}///{origin_str}/{source}");
413                    let source_content = source_content.unwrap_or_else(|| {
414                        format!(
415                            "unable to access {source_request} in {origin_str} (it's leaving the \
416                             filesystem root)"
417                        )
418                        .into()
419                    });
420                    (source.into(), source_content)
421                },
422            )
423        }
424        async fn regular_map_with_resolved_sources(
425            map: &RegularMapWrapper,
426            origin: FileSystemPath,
427        ) -> Result<RegularMap> {
428            let map = &map.0;
429            let file = map.get_file().cloned();
430            let tokens = map.tokens().map(|t| t.get_raw_token()).collect();
431            let names = map.names().cloned().collect();
432            let count = map.get_source_count() as usize;
433            let sources = map.sources().cloned().collect::<Vec<_>>();
434            let source_contents = map
435                .source_contents()
436                .map(|s| s.cloned())
437                .collect::<Vec<_>>();
438            let mut new_sources = Vec::with_capacity(count);
439            let mut new_source_contents = Vec::with_capacity(count);
440            for (source, source_content) in sources.into_iter().zip(source_contents.into_iter()) {
441                let (source, source_content) =
442                    resolve_source(source, source_content, origin.clone()).await?;
443                new_sources.push(source);
444                new_source_contents.push(Some(source_content));
445            }
446            let mut map =
447                RegularMap::new(file, tokens, names, new_sources, Some(new_source_contents));
448
449            add_default_ignore_list(&mut map);
450
451            Ok(map)
452        }
453        async fn decoded_map_with_resolved_sources(
454            map: &CrateMapWrapper,
455            origin: FileSystemPath,
456        ) -> Result<CrateMapWrapper> {
457            Ok(CrateMapWrapper(match &map.0 {
458                DecodedMap::Regular(map) => {
459                    let map = RegularMapWrapper::ref_cast(map);
460                    DecodedMap::Regular(regular_map_with_resolved_sources(map, origin).await?)
461                }
462                DecodedMap::Index(map) => {
463                    let count = map.get_section_count() as usize;
464                    let file = map.get_file().cloned();
465                    let sections = map
466                        .sections()
467                        .filter_map(|section| {
468                            section
469                                .get_sourcemap()
470                                .map(|s| (section.get_offset(), CrateMapWrapper::ref_cast(s)))
471                        })
472                        .collect::<Vec<_>>();
473                    let sections = sections
474                        .into_iter()
475                        .map(|(offset, map)| {
476                            let origin = origin.clone();
477                            async move {
478                                Ok((
479                                    offset,
480                                    Box::pin(decoded_map_with_resolved_sources(
481                                        map,
482                                        origin.clone(),
483                                    ))
484                                    .await?,
485                                ))
486                            }
487                        })
488                        .try_join()
489                        .await?;
490                    let mut new_sections = Vec::with_capacity(count);
491                    for (offset, map) in sections {
492                        new_sections.push(swc_sourcemap::SourceMapSection::new(
493                            offset,
494                            // Urls are deprecated and we don't accept them
495                            None,
496                            Some(map.0),
497                        ));
498                    }
499                    DecodedMap::Index(SourceMapIndex::new(file, new_sections))
500                }
501                DecodedMap::Hermes(_) => {
502                    todo!("hermes source maps are not implemented");
503                }
504            }))
505        }
506
507        let map = Box::pin(decoded_map_with_resolved_sources(&self.map, origin)).await?;
508        Ok(Self::new_decoded(map.0))
509    }
510}
511
512#[turbo_tasks::function]
513fn sourcemap_content_fs_root() -> Vc<FileSystemPath> {
514    VirtualFileSystem::new_with_name(rcstr!("sourcemap-content")).root()
515}
516
517#[turbo_tasks::function]
518async fn sourcemap_content_source(path: RcStr, content: RcStr) -> Result<Vc<Box<dyn Source>>> {
519    let path = sourcemap_content_fs_root().await?.join(&path)?;
520    let content = AssetContent::file(FileContent::new(File::from(content)).cell());
521    Ok(Vc::upcast(VirtualSource::new(path, content)))
522}
523
524impl SourceMap {
525    fn lookup_token_and_source_internal(
526        &self,
527        line: u32,
528        column: u32,
529        need_source_content: bool,
530    ) -> (Token, Option<Vc<Box<dyn Source>>>) {
531        let mut content: Option<Vc<Box<dyn Source>>> = None;
532
533        let token: Token = {
534            let map = &self.map;
535
536            let tok = map.lookup_token(line, column);
537            let mut token = tok.map(Token::from).unwrap_or_else(|| {
538                Token::Synthetic(SyntheticToken {
539                    generated_line: line,
540                    generated_column: column,
541                    guessed_original_file: None,
542                })
543            });
544
545            if let Token::Synthetic(SyntheticToken {
546                guessed_original_file,
547                ..
548            }) = &mut token
549                && let DecodedMap::Regular(map) = &map.0
550                && map.get_source_count() == 1
551            {
552                let source = map.sources().next().unwrap().clone();
553                *guessed_original_file = Some(RcStr::from(source));
554            }
555
556            // Check ignoreList and resolve source content using the regular
557            // (possibly flattened) source map. For Index maps,
558            // SourceMapIndex::flatten() correctly transfers ignoreList entries
559            // from each section into the flattened map.
560            if let Some(flat_map) = map.as_regular_source_map() {
561                if let Token::Original(ref mut orig) = token
562                    && let Some(source_name) = tok.and_then(|t| t.get_source().cloned())
563                    && let Some(idx) = flat_map.sources().position(|s| *s == *source_name)
564                {
565                    orig.is_ignored = flat_map.ignore_list().any(|id| *id == idx as u32);
566                }
567
568                if need_source_content && content.is_none() {
569                    content = tok.and_then(|tok| {
570                        let src_id = tok.get_src_id();
571
572                        let name = flat_map.get_source(src_id);
573                        let content = flat_map.get_source_contents(src_id);
574
575                        let (name, content) = name.zip(content)?;
576                        Some(sourcemap_content_source(
577                            name.clone().into(),
578                            content.clone().into(),
579                        ))
580                    });
581                }
582            }
583
584            token
585        };
586
587        (token, content)
588    }
589}
590
591impl SourceMap {
592    pub fn tokens(&self) -> impl Iterator<Item = Token> + '_ {
593        let map = &self.map;
594
595        fn regular_map_to_tokens(
596            map: &RegularMap,
597            offset_line: u32,
598            offset_column: u32,
599        ) -> impl Iterator<Item = Token> + '_ {
600            map.tokens()
601                .map(move |t| Token::from(t).with_offset(offset_line, offset_column))
602        }
603
604        fn index_map_to_tokens(
605            map: &SourceMapIndex,
606            offset_line: u32,
607            offset_column: u32,
608        ) -> impl Iterator<Item = Token> + '_ {
609            map.sections().flat_map(move |section| {
610                let (line, col) = section.get_offset();
611                let offset_line = offset_line + line;
612                let offset_column = if line == 0 { offset_column + col } else { col };
613                if let Some(source_map) = section.get_sourcemap() {
614                    Either::Left(Box::new(decoded_map_to_tokens(
615                        source_map,
616                        offset_line,
617                        offset_column,
618                    )) as Box<dyn Iterator<Item = Token>>)
619                } else {
620                    Either::Right(std::iter::empty())
621                }
622            })
623        }
624
625        fn decoded_map_to_tokens(
626            map: &DecodedMap,
627            offset_line: u32,
628            offset_column: u32,
629        ) -> impl Iterator<Item = Token> + '_ {
630            match map {
631                DecodedMap::Regular(map) => {
632                    Either::Left(regular_map_to_tokens(map, offset_line, offset_column))
633                }
634                DecodedMap::Index(map) => {
635                    Either::Right(index_map_to_tokens(map, offset_line, offset_column))
636                }
637                DecodedMap::Hermes(_) => {
638                    todo!("hermes source maps are not implemented");
639                }
640            }
641        }
642
643        decoded_map_to_tokens(&map.0, 0, 0)
644    }
645}
646
647#[turbo_tasks::value_impl]
648impl GenerateSourceMap for SourceMap {
649    #[turbo_tasks::function]
650    fn generate_source_map(&self) -> Result<Vc<FileContent>> {
651        Ok(FileContent::Content(File::from(self.to_rope()?)).cell())
652    }
653}
654
655/// Wraps the DecodedMap struct so that it is Sync and Send.
656///
657/// # Safety
658///
659/// Must not use per line access to the SourceMap, as it is not thread safe.
660#[derive(Debug, RefCast)]
661#[repr(transparent)]
662pub struct CrateMapWrapper(DecodedMap);
663
664// Safety: DecodedMap contains a raw pointer, which isn't Send, which is
665// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
666unsafe impl Send for CrateMapWrapper {}
667unsafe impl Sync for CrateMapWrapper {}
668
669/// Wraps the RegularMap struct so that it is Sync and Send.
670///
671/// # Safety
672///
673/// Must not use per line access to the SourceMap, as it is not thread safe.
674#[derive(Debug, RefCast)]
675#[repr(transparent)]
676pub struct RegularMapWrapper(RegularMap);
677
678// Safety: RegularMap contains a raw pointer, which isn't Send, which is
679// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
680unsafe impl Send for RegularMapWrapper {}
681unsafe impl Sync for RegularMapWrapper {}
682
683impl CrateMapWrapper {
684    pub fn as_regular_source_map(&self) -> Option<Cow<'_, RegularMap>> {
685        match &self.0 {
686            DecodedMap::Regular(m) => Some(Cow::Borrowed(m)),
687            DecodedMap::Index(m) => m.flatten().map(Cow::Owned).ok(),
688            _ => None,
689        }
690    }
691}
692
693impl Deref for CrateMapWrapper {
694    type Target = DecodedMap;
695
696    fn deref(&self) -> &Self::Target {
697        &self.0
698    }
699}
700
701impl Encode for CrateMapWrapper {
702    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
703        let mut bytes = Vec::new();
704        self.0
705            .to_writer(&mut bytes)
706            .map_err(|e| EncodeError::OtherString(e.to_string()))?;
707        bytes.encode(encoder)
708    }
709}
710
711impl<Context> Decode<Context> for CrateMapWrapper {
712    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
713        let bytes = Vec::<u8>::decode(decoder)?;
714        let map = DecodedMap::from_reader(&*bytes)
715            .map_err(|e| DecodeError::OtherString(e.to_string()))?;
716        Ok(CrateMapWrapper(map))
717    }
718}
719
720bincode::impl_borrow_decode!(CrateMapWrapper);