Skip to main content

wesl/
sourcemap.rs

1//! [`SourceMap`] trait and implementations.
2
3use std::{cell::RefCell, collections::HashMap, path::PathBuf};
4
5use wgsl_parse::{span::Span, syntax::TypeExpression};
6
7use crate::{ModulePath, error::ResolveError, mangler::Mangler, resolver::Resolver};
8
9/// A SourceMap is a lookup from compiled WGSL to source WESL. It translates a mangled
10/// name into a module path and declaration name.
11///
12/// Using SourceMaps improves the readability of error diagnostics, by providing needed
13/// information to identify the originating code snippet, file name and declaration name.
14/// It is highly recommended to use them, but they can increase the compilation memory
15/// footprint, since they cache all loaded files.
16///
17/// Typically you record to a SourceMap by passing a [`SourceMapper`] as the [`Resolver`]
18/// and [`Mangler`] when compiling code.
19pub trait SourceMap {
20    /// Get the module path and declaration name from a mangled name.
21    fn item(&self, decl: &str) -> Option<&SourceMapEntry>;
22    /// Get a module contents.
23    fn source(&self, path: &ModulePath) -> Option<&str>;
24    /// Get a module display name.
25    fn display_name(&self, path: &ModulePath) -> Option<&str>;
26    /// Get the default module contents.
27    fn default_source(&self) -> Option<&str> {
28        None
29    }
30}
31
32#[derive(Clone, Debug)]
33pub struct SourceMapEntry {
34    pub path: ModulePath,
35    pub name: String,
36    pub span: Option<Span>,
37}
38
39#[derive(Clone, Debug)]
40pub struct SourceMapFile {
41    pub source: String,
42    pub display_name: Option<String>,
43    pub path: Option<PathBuf>,
44}
45
46/// Basic implementation of [`SourceMap`].
47#[derive(Clone, Debug, Default)]
48pub struct BasicSourceMap {
49    mappings: HashMap<String, SourceMapEntry>,
50    sources: HashMap<ModulePath, SourceMapFile>,
51    default_source: Option<String>,
52}
53
54impl BasicSourceMap {
55    pub fn new() -> Self {
56        Default::default()
57    }
58    pub fn add_item(&mut self, decl: String, entry: SourceMapEntry) {
59        self.mappings.insert(decl, entry);
60    }
61    pub fn file(&self, path: &ModulePath) -> Option<&SourceMapFile> {
62        self.sources.get(path)
63    }
64    pub fn add_file(&mut self, path: ModulePath, file: SourceMapFile) {
65        self.sources.insert(path, file);
66    }
67    pub fn set_default_source(&mut self, source: String) {
68        self.default_source = Some(source);
69    }
70}
71
72impl SourceMap for BasicSourceMap {
73    fn item(&self, decl: &str) -> Option<&SourceMapEntry> {
74        self.mappings.get(decl)
75    }
76    fn source(&self, path: &ModulePath) -> Option<&str> {
77        self.sources.get(path).map(|file| file.source.as_str())
78    }
79    fn display_name(&self, path: &ModulePath) -> Option<&str> {
80        self.sources
81            .get(path)
82            .and_then(|file| file.display_name.as_deref())
83    }
84    fn default_source(&self) -> Option<&str> {
85        self.default_source.as_deref()
86    }
87}
88
89impl<T: SourceMap> SourceMap for Option<T> {
90    fn item(&self, decl: &str) -> Option<&SourceMapEntry> {
91        self.as_ref().and_then(|map| map.item(decl))
92    }
93    fn source(&self, path: &ModulePath) -> Option<&str> {
94        self.as_ref().and_then(|map| map.source(path))
95    }
96    fn display_name(&self, path: &ModulePath) -> Option<&str> {
97        self.as_ref().and_then(|map| map.display_name(path))
98    }
99    fn default_source(&self) -> Option<&str> {
100        self.as_ref().and_then(|map| map.default_source())
101    }
102}
103
104/// This [`SourceMap`] implementation simply does nothing and returns `None`.
105///
106/// It can be useful to pass this struct to functions requiring a sourcemap, but
107/// you don't care about sourcemapping.
108pub struct NoSourceMap;
109
110impl SourceMap for NoSourceMap {
111    fn item(&self, _decl: &str) -> Option<&SourceMapEntry> {
112        None
113    }
114    fn source(&self, _path: &ModulePath) -> Option<&str> {
115        None
116    }
117    fn display_name(&self, _path: &ModulePath) -> Option<&str> {
118        None
119    }
120    fn default_source(&self) -> Option<&str> {
121        None
122    }
123}
124
125/// Generate a SourceMap by keeping track of loaded files and mangled identifiers.
126///
127/// `SourceMapper` is a proxy that implements [`Mangler`] and [`Resolver`]. To record a
128/// SourceMap, invoke the compiler with this instance as both the mangler and the
129/// resolver. Call [`SourceMapper::finish`] to get the final SourceMap once finished
130/// recording.
131pub struct SourceMapper<'a> {
132    pub main_path: ModulePath,
133    pub resolver: &'a dyn Resolver,
134    pub mangler: &'a dyn Mangler,
135    pub sourcemap: RefCell<BasicSourceMap>,
136}
137
138impl<'a> SourceMapper<'a> {
139    /// Create a new `SourceMapper` from a mangler and a resolver.
140    pub fn new(
141        main_path: ModulePath,
142        resolver: &'a dyn Resolver,
143        mangler: &'a dyn Mangler,
144    ) -> Self {
145        Self {
146            main_path,
147            resolver,
148            mangler,
149            sourcemap: Default::default(),
150        }
151    }
152    /// Consume this and return a [`BasicSourceMap`].
153    pub fn finish(self) -> BasicSourceMap {
154        let mut sourcemap = self.sourcemap.into_inner();
155        if let Some(file) = sourcemap.file(&self.main_path) {
156            sourcemap.set_default_source(file.source.to_string());
157        }
158        sourcemap
159    }
160}
161
162impl<'a> Resolver for SourceMapper<'a> {
163    fn resolve_source(&self, path: &ModulePath) -> Result<std::borrow::Cow<'a, str>, ResolveError> {
164        let res = self.resolver.resolve_source(path)?;
165        let mut sourcemap = self.sourcemap.borrow_mut();
166        sourcemap.add_file(
167            path.clone(),
168            SourceMapFile {
169                source: res.clone().into(),
170                display_name: self.resolver.display_name(path),
171                path: self.resolver.fs_path(path).ok(),
172            },
173        );
174        Ok(res)
175    }
176    fn display_name(&self, path: &ModulePath) -> Option<String> {
177        self.resolver.display_name(path)
178    }
179    fn fs_path(&self, path: &ModulePath) -> Result<PathBuf, ResolveError> {
180        self.resolver.fs_path(path)
181    }
182    fn canonical_path(&self, path: &ModulePath) -> ModulePath {
183        self.resolver.canonical_path(path)
184    }
185}
186
187impl<'a> Mangler for SourceMapper<'a> {
188    fn mangle(&self, path: &ModulePath, item: &str) -> String {
189        let res = self.mangler.mangle(path, item);
190        let mut sourcemap = self.sourcemap.borrow_mut();
191        let entry = SourceMapEntry {
192            path: path.clone(),
193            name: item.to_string(),
194            span: None,
195        };
196        sourcemap.add_item(res.clone(), entry);
197        res
198    }
199    fn unmangle(&self, mangled: &str) -> Option<(ModulePath, String)> {
200        self.mangler.unmangle(mangled)
201    }
202    fn mangle_types(&self, item: &str, variant: u32, types: &[TypeExpression]) -> String {
203        self.mangler.mangle_types(item, variant, types)
204    }
205}