summaryrefslogtreecommitdiff
path: root/crates/typst-syntax/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-08-29 17:35:35 +0200
committerLaurenz <laurmaedje@gmail.com>2023-08-29 17:35:35 +0200
commita71a2057f286677b5bf2e064fea05024aeca0dd2 (patch)
tree716d85481aca232abdb6c2e01a0a545c003f4c6b /crates/typst-syntax/src
parent7bdf1f57b09ea605045254013a8200373451baf0 (diff)
More type safety for spans
Diffstat (limited to 'crates/typst-syntax/src')
-rw-r--r--crates/typst-syntax/src/file.rs44
-rw-r--r--crates/typst-syntax/src/node.rs6
-rw-r--r--crates/typst-syntax/src/reparser.rs4
-rw-r--r--crates/typst-syntax/src/source.rs24
-rw-r--r--crates/typst-syntax/src/span.rs69
5 files changed, 55 insertions, 92 deletions
diff --git a/crates/typst-syntax/src/file.rs b/crates/typst-syntax/src/file.rs
index 6b3117cf..f5fa493b 100644
--- a/crates/typst-syntax/src/file.rs
+++ b/crates/typst-syntax/src/file.rs
@@ -16,9 +16,6 @@ use super::is_ident;
static INTERNER: Lazy<RwLock<Interner>> =
Lazy::new(|| RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() }));
-/// The path that we use for detached file ids.
-static DETACHED_PATH: Lazy<VirtualPath> = Lazy::new(|| VirtualPath::new("/unknown"));
-
/// A package-path interner.
struct Interner {
to_id: HashMap<Pair, FileId>,
@@ -48,66 +45,41 @@ impl FileId {
}
let mut interner = INTERNER.write().unwrap();
- let len = interner.from_id.len();
- if len >= usize::from(u16::MAX) {
- panic!("too many file specifications");
- }
+ let num = interner.from_id.len().try_into().expect("out of file ids");
// Create a new entry forever by leaking the pair. We can't leak more
// than 2^16 pair (and typically will leak a lot less), so its not a
// big deal.
- let id = FileId(len as u16);
+ let id = FileId(num);
let leaked = Box::leak(Box::new(pair));
interner.to_id.insert(leaked, id);
interner.from_id.push(leaked);
id
}
- /// Get an id that does not identify any real file.
- pub const fn detached() -> Self {
- Self(u16::MAX)
- }
-
- /// Whether the id is the detached.
- pub const fn is_detached(self) -> bool {
- self.0 == Self::detached().0
- }
-
/// The package the file resides in, if any.
pub fn package(&self) -> Option<&'static PackageSpec> {
- if self.is_detached() {
- None
- } else {
- self.pair().0.as_ref()
- }
+ self.pair().0.as_ref()
}
/// The absolute and normalized path to the file _within_ the project or
/// package.
pub fn vpath(&self) -> &'static VirtualPath {
- if self.is_detached() {
- &DETACHED_PATH
- } else {
- &self.pair().1
- }
+ &self.pair().1
}
/// Resolve a file location relative to this file.
- pub fn join(self, path: &str) -> Result<Self, EcoString> {
- if self.is_detached() {
- Err("cannot access file system from here")?;
- }
-
- Ok(Self::new(self.package().cloned(), self.vpath().join(path)))
+ pub fn join(self, path: &str) -> Self {
+ Self::new(self.package().cloned(), self.vpath().join(path))
}
/// Construct from a raw number.
- pub(crate) const fn from_u16(v: u16) -> Self {
+ pub(crate) const fn from_raw(v: u16) -> Self {
Self(v)
}
/// Extract the raw underlying number.
- pub(crate) const fn as_u16(self) -> u16 {
+ pub(crate) const fn into_raw(self) -> u16 {
self.0
}
diff --git a/crates/typst-syntax/src/node.rs b/crates/typst-syntax/src/node.rs
index 8e4e056e..77c73f58 100644
--- a/crates/typst-syntax/src/node.rs
+++ b/crates/typst-syntax/src/node.rs
@@ -202,7 +202,7 @@ impl SyntaxNode {
return Err(Unnumberable);
}
- let mid = Span::new(id, (within.start + within.end) / 2);
+ let mid = Span::new(id, (within.start + within.end) / 2).unwrap();
match &mut self.0 {
Repr::Leaf(leaf) => leaf.span = mid,
Repr::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within)?,
@@ -424,7 +424,7 @@ impl InnerNode {
let mut start = within.start;
if range.is_none() {
let end = start + stride;
- self.span = Span::new(id, (start + end) / 2);
+ self.span = Span::new(id, (start + end) / 2).unwrap();
self.upper = within.end;
start = end;
}
@@ -448,6 +448,7 @@ impl InnerNode {
mut range: Range<usize>,
replacement: Vec<SyntaxNode>,
) -> NumberingResult {
+ let Some(id) = self.span.id() else { return Err(Unnumberable) };
let superseded = &self.children[range.clone()];
// Compute the new byte length.
@@ -505,7 +506,6 @@ impl InnerNode {
// Try to renumber.
let within = start_number..end_number;
- let id = self.span.id();
if self.numberize(id, Some(renumber), within).is_ok() {
return Ok(());
}
diff --git a/crates/typst-syntax/src/reparser.rs b/crates/typst-syntax/src/reparser.rs
index a4186fa7..e03e1619 100644
--- a/crates/typst-syntax/src/reparser.rs
+++ b/crates/typst-syntax/src/reparser.rs
@@ -21,7 +21,9 @@ pub fn reparse(
try_reparse(text, replaced, replacement_len, None, root, 0).unwrap_or_else(|| {
let id = root.span().id();
*root = parse(text);
- root.numberize(id, Span::FULL).unwrap();
+ if let Some(id) = id {
+ root.numberize(id, Span::FULL).unwrap();
+ }
0..text.len()
})
}
diff --git a/crates/typst-syntax/src/source.rs b/crates/typst-syntax/src/source.rs
index 036499ab..56b27195 100644
--- a/crates/typst-syntax/src/source.rs
+++ b/crates/typst-syntax/src/source.rs
@@ -9,6 +9,7 @@ use comemo::Prehashed;
use super::reparser::reparse;
use super::{is_newline, parse, FileId, LinkedNode, Span, SyntaxNode};
+use crate::VirtualPath;
/// A source file.
///
@@ -44,19 +45,7 @@ impl Source {
/// Create a source file without a real id and path, usually for testing.
pub fn detached(text: impl Into<String>) -> Self {
- Self::new(FileId::detached(), text.into())
- }
-
- /// Create a source file with the same synthetic span for all nodes.
- pub fn synthesized(text: String, span: Span) -> Self {
- let mut root = parse(&text);
- root.synthesize(span);
- Self(Arc::new(Repr {
- id: FileId::detached(),
- lines: lines(&text),
- text: Prehashed::new(text),
- root: Prehashed::new(root),
- }))
+ Self::new(FileId::new(None, VirtualPath::new("main.typ")), text.into())
}
/// The root node of the file's untyped syntax tree.
@@ -151,12 +140,9 @@ impl Source {
/// Get the byte range for the given span in this file.
///
- /// Panics if the span does not point into this source file.
- #[track_caller]
- pub fn range(&self, span: Span) -> Range<usize> {
- self.find(span)
- .expect("span does not point into this source file")
- .range()
+ /// Returns `None` if the span does not point into this source file.
+ pub fn range(&self, span: Span) -> Option<Range<usize>> {
+ Some(self.find(span)?.range())
}
/// Return the index of the UTF-16 code unit at the byte index.
diff --git a/crates/typst-syntax/src/span.rs b/crates/typst-syntax/src/span.rs
index 8715e476..d715af1c 100644
--- a/crates/typst-syntax/src/span.rs
+++ b/crates/typst-syntax/src/span.rs
@@ -28,55 +28,58 @@ pub struct Span(NonZeroU64);
impl Span {
/// The full range of numbers available for span numbering.
- pub const FULL: Range<u64> = 2..(1 << Self::BITS);
+ pub(super) const FULL: Range<u64> = 2..(1 << Self::BITS);
+
+ /// The value reserved for the detached span.
const DETACHED: u64 = 1;
- // Data layout:
- // | 16 bits source id | 48 bits number |
+ /// Data layout:
+ /// | 16 bits source id | 48 bits number |
const BITS: usize = 48;
/// Create a new span from a source id and a unique number.
///
- /// Panics if the `number` is not contained in `FULL`.
- #[track_caller]
- pub const fn new(id: FileId, number: u64) -> Self {
- assert!(
- Self::FULL.start <= number && number < Self::FULL.end,
- "span number outside valid range"
- );
-
- Self::pack(id, number)
- }
+ /// Returns `None` if `number` is not contained in `FULL`.
+ pub(super) const fn new(id: FileId, number: u64) -> Option<Self> {
+ if number < Self::FULL.start || number >= Self::FULL.end {
+ return None;
+ }
- /// A span that does not point into any source file.
- pub const fn detached() -> Self {
- Self::pack(FileId::detached(), Self::DETACHED)
+ let bits = ((id.into_raw() as u64) << Self::BITS) | number;
+ match NonZeroU64::new(bits) {
+ Some(v) => Some(Self(v)),
+ None => unreachable!(),
+ }
}
- /// Pack the components into a span.
- #[track_caller]
- const fn pack(id: FileId, number: u64) -> Span {
- let bits = ((id.as_u16() as u64) << Self::BITS) | number;
- match NonZeroU64::new(bits) {
+ /// Create a span that does not point into any source file.
+ pub const fn detached() -> Self {
+ match NonZeroU64::new(Self::DETACHED) {
Some(v) => Self(v),
- None => panic!("span encoding is zero"),
+ None => unreachable!(),
}
}
+ /// Whether the span is detached.
+ pub const fn is_detached(self) -> bool {
+ self.0.get() == Self::DETACHED
+ }
+
/// The id of the source file the span points into.
- pub const fn id(self) -> FileId {
- FileId::from_u16((self.0.get() >> Self::BITS) as u16)
+ ///
+ /// Returns `None` if the span is detached.
+ pub const fn id(self) -> Option<FileId> {
+ if self.is_detached() {
+ return None;
+ }
+ let bits = (self.0.get() >> Self::BITS) as u16;
+ Some(FileId::from_raw(bits))
}
- /// The unique number of the span within its source file.
+ /// The unique number of the span within its [`Source`](super::Source).
pub const fn number(self) -> u64 {
self.0.get() & ((1 << Self::BITS) - 1)
}
-
- /// Whether the span is detached.
- pub const fn is_detached(self) -> bool {
- self.id().is_detached()
- }
}
/// A value with a span locating it in the source code.
@@ -120,9 +123,9 @@ mod tests {
#[test]
fn test_span_encoding() {
- let id = FileId::from_u16(5);
- let span = Span::new(id, 10);
- assert_eq!(span.id(), id);
+ let id = FileId::from_raw(5);
+ let span = Span::new(id, 10).unwrap();
+ assert_eq!(span.id(), Some(id));
assert_eq!(span.number(), 10);
}
}