pub struct SourceMap { /* private fields */ }Expand description
A collection of sources laid out end to end in a single global position space.
A SourceMap is the multi-file coordinate layer of a front-end. Each source
added to it gets a stable SourceId and a non-overlapping range in one
shared position space, so a single global BytePos names a point across the
whole project. locate maps such a position back to its
(SourceId, local offset) — the inverse of the layout — which is how a
diagnostic rendered against a global span knows which file to point at.
§Layout
Sources are placed in the order they are added: the first occupies
0..len₀, the next len₀..len₀ + len₁, and so on. Because the bases only
increase, the internal list is always sorted by start offset, so a lookup is a
binary search over it — O(log files) — with no separate index to maintain.
The whole space is 32 bits wide, the same envelope a single
BytePos addresses, so the combined length of every source is capped at
u32::MAX; overrunning it is the SpaceExhausted error, never a silent
wrap into a neighbour’s range.
§Examples
use source_lang::{BytePos, SourceMap};
let mut map = SourceMap::new();
let main = map.add("main.rs", "fn main() {}").expect("fits"); // global 0..12
let util = map.add("util.rs", "fn helper() {}").expect("fits"); // global 12..26
// A global position resolves to the file it lands in and the local offset.
let (id, local) = map.locate(BytePos::new(13)).expect("inside util.rs");
assert_eq!(id, util);
assert_eq!(local, BytePos::new(1)); // 13 - 12
assert_eq!(map.source(id).unwrap().name(), "util.rs");
// Position 0 is the very start of the first file.
assert_eq!(map.locate(BytePos::new(0)).unwrap().0, main);
// The end of the last file is still in it (its end-of-input position)...
assert_eq!(map.locate(BytePos::new(26)), Some((util, BytePos::new(14))));
// ...but anything past it belongs to no file.
assert_eq!(map.locate(BytePos::new(27)), None);Implementations§
Source§impl SourceMap
impl SourceMap
Sourcepub const fn new() -> Self
pub const fn new() -> Self
Creates an empty map whose global position space starts at 0.
The per-source size ceiling starts at u32::MAX; lower it with
set_max_source_len to bound untrusted
input.
§Examples
use source_lang::SourceMap;
let map = SourceMap::new();
assert!(map.is_empty());Sourcepub fn with_capacity(capacity: usize) -> Self
pub fn with_capacity(capacity: usize) -> Self
Creates an empty map with room for capacity sources preallocated.
A hint only: it sizes the internal list so that adding up to capacity
sources does not reallocate, which matters when the source count is known
up front. The global position space still starts empty.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::with_capacity(2);
map.add("a", "x").expect("fits");
map.add("b", "y").expect("fits");
assert_eq!(map.len(), 2);Sourcepub const fn max_source_len(&self) -> u32
pub const fn max_source_len(&self) -> u32
Returns the current per-source size ceiling, in bytes.
A source longer than this is rejected with
SourceMapError::Oversize before it consumes any global space. The
default is u32::MAX.
§Examples
use source_lang::SourceMap;
let map = SourceMap::new();
assert_eq!(map.max_source_len(), u32::MAX);Sourcepub fn set_max_source_len(&mut self, max: u32)
pub fn set_max_source_len(&mut self, max: u32)
Sets the largest a single source may be, in bytes.
Use it to bound how much one untrusted input — a file named on a command
line, a buffer from the network — can pull into memory. The limit applies
to every later add, add_bytes,
and add_file; for a file it is checked against the
path’s metadata before any bytes are read. Sources already in the map are
unaffected.
§Examples
use source_lang::{SourceMap, SourceMapError};
let mut map = SourceMap::new();
map.set_max_source_len(8);
assert!(map.add("ok", "12345678").is_ok()); // exactly 8 bytes
let err = map.add("big", "123456789").unwrap_err(); // 9 bytes
assert!(matches!(err, SourceMapError::Oversize { len: 9, .. }));Sourcepub fn add(
&mut self,
name: impl Into<Box<str>>,
text: impl Into<Box<str>>,
) -> Result<SourceId, SourceMapError>
pub fn add( &mut self, name: impl Into<Box<str>>, text: impl Into<Box<str>>, ) -> Result<SourceId, SourceMapError>
Adds a source under name with the given text, returning its
SourceId.
The source is appended after every existing one: it takes the range
next..next + text.len() where next is the current end of the global
space. Both name and text are taken by value (anything that converts
into a Box<str> — a String or a &str), so the map owns the text and
callers can borrow it back for the life of the map.
Adding an empty text is allowed: it yields a valid id whose source has a
zero-width span and does not advance the global space. It is located only
while it is the last source (its position is then the end of the space,
see locate); otherwise its offset is the next
source’s first byte.
This is also where the source’s line table is built, in one pass, so that
line_col never rescans the text afterwards.
§Errors
Returns SourceMapError::SpaceExhausted if text does not fit in the
bytes left in the 32-bit global space, or if the map already holds the
maximum number of sources. The map is left unchanged, so the failure is
recoverable.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::new();
let id = map.add("config.toml", "name = \"demo\"").expect("fits");
assert_eq!(map.source(id).unwrap().text(), "name = \"demo\"");
// A String works just as well as a &str.
let owned = String::from("generated");
let _ = map.add("out.txt", owned).expect("fits");Sourcepub fn add_bytes(
&mut self,
name: impl Into<Box<str>>,
bytes: &[u8],
) -> Result<SourceId, SourceMapError>
pub fn add_bytes( &mut self, name: impl Into<Box<str>>, bytes: &[u8], ) -> Result<SourceId, SourceMapError>
Validates raw bytes as UTF-8 and adds them as a source under name.
This is the in-memory counterpart to add_file:
both turn untrusted bytes — from a buffer here, from disk there — into a
stored source through the same checks, so a network buffer and a file on
disk fail and succeed the same way.
§Errors
SourceMapError::NotUtf8ifbytesare not valid UTF-8.SourceMapError::Oversizeif they exceedmax_source_len.SourceMapError::SpaceExhaustedif they do not fit in the remaining global space.
On any error the map is left unchanged.
§Examples
use source_lang::{SourceMap, SourceMapError};
let mut map = SourceMap::new();
let id = map.add_bytes("greeting.txt", b"hello").expect("valid UTF-8");
assert_eq!(map.source(id).unwrap().text(), "hello");
// A stray binary byte is rejected, not stored as corrupt text.
let err = map.add_bytes("blob", &[0xff]).unwrap_err();
assert!(matches!(err, SourceMapError::NotUtf8 { .. }));Sourcepub fn add_file(
&mut self,
path: impl AsRef<Path>,
) -> Result<SourceId, SourceMapError>
Available on crate feature std only.
pub fn add_file( &mut self, path: impl AsRef<Path>, ) -> Result<SourceId, SourceMapError>
std only.Reads a file from disk and adds its contents as a source named by path.
The file’s size is checked against max_source_len
from its metadata before a single byte is read, so an oversize file is
rejected without being loaded into memory. The read itself is bounded too:
it stops one byte past the ceiling, so a source whose metadata reports no
length (a pipe, a /proc file) or a file that grows while it is read can
never pull more than max_source_len + 1 bytes into memory. The bytes are
then validated as UTF-8 and stored. The source’s name is the path as given.
§Errors
SourceMapError::Oversizeif the file’s metadata length exceedsmax_source_len, or if the read reaches past it (thenlenis the number of bytes read before stopping,max_source_len + 1, a lower bound on the true size).SourceMapError::Ioif the path cannot be opened or read (missing file, a directory, permission denied).SourceMapError::NotUtf8if the contents are not valid UTF-8.SourceMapError::SpaceExhaustedif they do not fit in the remaining global space.
On any error the map is left unchanged.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::new();
let id = map.add_file("src/main.rs")?;
assert_eq!(map.source(id).unwrap().name(), "src/main.rs");Sourcepub fn locate(&self, pos: BytePos) -> Option<(SourceId, BytePos)>
pub fn locate(&self, pos: BytePos) -> Option<(SourceId, BytePos)>
Resolves a global position to the source it falls in and the local offset within that source.
The returned BytePos is pos minus the source’s base, i.e. the offset
into SourceFile::text. Resolution is a binary search over the sources’
start offsets, so it is O(log files) and borrows the located source
rather than copying it.
Membership is half-open: a source covering start..end contains start
but not end, so the boundary between two adjacent sources resolves to the
second, never to both. One extension (since 1.0.1): the end of the last
source, the current end of the global space, resolves to that source with a
local offset equal to its length. That is where an end-of-input diagnostic
(a zero-width span at the end of the text) points, so end-of-input errors in
the most recently added source (in particular in a single-source map)
resolve to it, and an empty last source is locatable. Up to 1.0.0 that
position returned None.
Returns None past the end of the space, on an empty map, and at the
offset of an empty source that is not the last one (that offset is the next
source’s first byte).
§Known limitation
Sources are laid out contiguously, so the end of a source that is not
the last one is the same position as the next source’s first byte. That
position resolves to the next source, at local offset 0: an end-of-input
diagnostic for an earlier source is attributed to the following source’s
1:1. Telling the two apart needs a layout change (one reserved end
position per source, as rustc does), which moves every later source’s base
and so waits for 2.0.
§Examples
use source_lang::{BytePos, SourceMap};
let mut map = SourceMap::new();
let a = map.add("a", "abc").expect("fits"); // 0..3
let b = map.add("b", "de").expect("fits"); // 3..5
assert_eq!(map.locate(BytePos::new(2)), Some((a, BytePos::new(2))));
// The shared boundary at 3 is the start of `b`, not the end of `a`.
assert_eq!(map.locate(BytePos::new(3)), Some((b, BytePos::new(0))));
// The end of the last source is its end-of-input position (since 1.0.1).
assert_eq!(map.locate(BytePos::new(5)), Some((b, BytePos::new(2))));
assert_eq!(map.locate(BytePos::new(6)), None);Sourcepub fn line_col(&self, pos: BytePos) -> Option<(SourceId, LineCol)>
pub fn line_col(&self, pos: BytePos) -> Option<(SourceId, LineCol)>
Resolves a global position to its source and 1-based line/column.
This is locate composed with span-lang’s line
index: the position is mapped to its source and local offset, then that
offset is turned into a LineCol within the source’s own text. The
column counts Unicode scalar values, so a multi-byte character advances
the column by one, not by its byte width.
The source is chosen exactly as locate chooses it,
so the end of the last source resolves to it, one column past its last
character. Returns None exactly when locate does.
Each call is O(log files + log lines) plus a character count within the
one located line: since 1.0.1 the source’s line table is built once, when
the source is added, never per call.
§Examples
use source_lang::{BytePos, LineCol, SourceMap};
let mut map = SourceMap::new();
map.add("a.rs", "fn a() {}").expect("fits"); // 0..9
let b = map.add("b.rs", "let x = 1;\nlet y = 2;").expect("fits"); // 9..30
// Global 20 is the second line of b.rs ("let y = 2;").
let (id, lc) = map.line_col(BytePos::new(20)).expect("in range");
assert_eq!(id, b);
assert_eq!(lc, LineCol::new(2, 1));Sourcepub fn source(&self, id: SourceId) -> Option<&SourceFile>
pub fn source(&self, id: SourceId) -> Option<&SourceFile>
Borrows the source named by id, or None if the id is not from this map.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::new();
let id = map.add("readme.md", "# title").expect("fits");
assert_eq!(map.source(id).unwrap().name(), "readme.md");Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Returns the number of sources in the map.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::new();
assert_eq!(map.len(), 0);
map.add("a", "x").expect("fits");
assert_eq!(map.len(), 1);Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Returns true if the map holds no sources.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::new();
assert!(map.is_empty());
map.add("a", "x").expect("fits");
assert!(!map.is_empty());Sourcepub fn iter(
&self,
) -> impl ExactSizeIterator<Item = (SourceId, &SourceFile)> + '_
pub fn iter( &self, ) -> impl ExactSizeIterator<Item = (SourceId, &SourceFile)> + '_
Iterates over the sources in insertion order, pairing each with its id.
The order is also id order (0, 1, …) and global-offset order, so the
iterator walks the global position space from start to end. Useful for
listing the loaded files or building a side table keyed by SourceId.
§Examples
use source_lang::SourceMap;
let mut map = SourceMap::new();
map.add("a.txt", "one").expect("fits");
map.add("b.txt", "two").expect("fits");
let names: Vec<_> = map.iter().map(|(_, f)| f.name()).collect();
assert_eq!(names, ["a.txt", "b.txt"]);Trait Implementations§
Source§impl<'de> Deserialize<'de> for SourceMap
Available on crate feature serde only.
impl<'de> Deserialize<'de> for SourceMap
serde only.