#![deny(warnings)]
use std::cell::{Cell, RefCell};
use std::cmp;
use std::ops::{Add, Sub};
use std::rc::Rc;
use std::fmt;
use serde::de::{self, Deserializer, SeqAccess, Unexpected, Visitor};
use serde::ser::{SerializeSeq, Serializer};
use serde::{Deserialize, Serialize};
pub mod hygiene;
pub use crate::hygiene::{ExpnFormat, ExpnInfo, NameAndSpan, SyntaxContext};
pub mod symbol;
pub type FileName = String;
#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Span {
pub lo: BytePos,
pub hi: BytePos,
#[serde(skip)]
pub ctxt: SyntaxContext,
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MultiSpan {
primary_spans: Vec<Span>,
span_labels: Vec<(Span, String)>,
}
impl Span {
pub fn end_point(self) -> Span {
let lo = cmp::max(self.hi.0 - 1, self.lo.0);
Span {
lo: BytePos(lo),
..self
}
}
pub fn next_point(self) -> Span {
let lo = cmp::max(self.hi.0, self.lo.0 + 1);
Span {
lo: BytePos(lo),
hi: BytePos(lo),
..self
}
}
pub fn substitute_dummy(self, other: Span) -> Span {
if self.source_equal(&DUMMY_SP) {
other
} else {
self
}
}
pub fn contains(self, other: Span) -> bool {
self.lo <= other.lo && other.hi <= self.hi
}
pub fn source_equal(&self, other: &Span) -> bool {
self.lo == other.lo && self.hi == other.hi
}
pub fn trim_start(self, other: Span) -> Option<Span> {
if self.hi > other.hi {
Some(Span {
lo: cmp::max(self.lo, other.hi),
..self
})
} else {
None
}
}
pub fn source_callsite(self) -> Span {
self.ctxt
.outer()
.expn_info()
.map(|info| info.call_site.source_callsite())
.unwrap_or(self)
}
pub fn source_callee(self) -> Option<NameAndSpan> {
fn source_callee(info: ExpnInfo) -> NameAndSpan {
match info.call_site.ctxt.outer().expn_info() {
Some(info) => source_callee(info),
None => info.callee,
}
}
self.ctxt.outer().expn_info().map(source_callee)
}
pub fn allows_unstable(&self) -> bool {
match self.ctxt.outer().expn_info() {
Some(info) => info.callee.allow_internal_unstable,
None => false,
}
}
pub fn macro_backtrace(mut self) -> Vec<MacroBacktrace> {
let mut prev_span = DUMMY_SP;
let mut result = vec![];
while let Some(info) = self.ctxt.outer().expn_info() {
let (pre, post) = match info.callee.format {
ExpnFormat::MacroAttribute(..) => ("#[", "]"),
ExpnFormat::MacroBang(..) => ("", "!"),
ExpnFormat::CompilerDesugaring(..) => ("desugaring of `", "`"),
};
let macro_decl_name = format!("{}{}{}", pre, info.callee.name(), post);
let def_site_span = info.callee.span;
if !info.call_site.source_equal(&prev_span) {
result.push(MacroBacktrace {
call_site: info.call_site,
macro_decl_name,
def_site_span,
});
}
prev_span = self;
self = info.call_site;
}
result
}
pub fn to(self, end: Span) -> Span {
if end.ctxt == SyntaxContext::empty() {
Span { lo: self.lo, ..end }
} else {
Span { hi: end.hi, ..self }
}
}
pub fn between(self, end: Span) -> Span {
Span {
lo: self.hi,
hi: end.lo,
ctxt: if end.ctxt == SyntaxContext::empty() {
end.ctxt
} else {
self.ctxt
},
}
}
pub fn until(self, end: Span) -> Span {
Span {
lo: self.lo,
hi: end.lo,
ctxt: if end.ctxt == SyntaxContext::empty() {
end.ctxt
} else {
self.ctxt
},
}
}
}
#[derive(Clone, Debug)]
pub struct SpanLabel {
pub span: Span,
pub is_primary: bool,
pub label: Option<String>,
}
fn default_span_debug(span: Span, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Span {{ lo: {:?}, hi: {:?}, ctxt: {:?} }}",
span.lo, span.hi, span.ctxt
)
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
SPAN_DEBUG.with(|span_debug| span_debug.get()(*self, f))
}
}
pub const DUMMY_SP: Span = Span {
lo: BytePos(0),
hi: BytePos(0),
ctxt: NO_EXPANSION,
};
impl MultiSpan {
pub fn from_span(primary_span: Span) -> MultiSpan {
MultiSpan {
primary_spans: vec![primary_span],
span_labels: vec![],
}
}
pub fn from_spans(vec: Vec<Span>) -> MultiSpan {
MultiSpan {
primary_spans: vec,
span_labels: vec![],
}
}
pub fn push_span_label(&mut self, span: Span, label: String) {
self.span_labels.push((span, label));
}
pub fn primary_span(&self) -> Option<Span> {
self.primary_spans.first().cloned()
}
pub fn primary_spans(&self) -> &[Span] {
&self.primary_spans
}
pub fn replace(&mut self, before: Span, after: Span) -> bool {
let mut replacements_occurred = false;
for primary_span in &mut self.primary_spans {
if *primary_span == before {
*primary_span = after;
replacements_occurred = true;
}
}
for span_label in &mut self.span_labels {
if span_label.0 == before {
span_label.0 = after;
replacements_occurred = true;
}
}
replacements_occurred
}
pub fn span_labels(&self) -> Vec<SpanLabel> {
let is_primary = |span| self.primary_spans.contains(&span);
let mut span_labels = vec![];
for &(span, ref label) in &self.span_labels {
span_labels.push(SpanLabel {
span,
is_primary: is_primary(span),
label: Some(label.clone()),
});
}
for &span in &self.primary_spans {
if !span_labels.iter().any(|sl| sl.span == span) {
span_labels.push(SpanLabel {
span,
is_primary: true,
label: None,
});
}
}
span_labels
}
}
impl From<Span> for MultiSpan {
fn from(span: Span) -> MultiSpan {
MultiSpan::from_span(span)
}
}
pub const NO_EXPANSION: SyntaxContext = crate::hygiene::NO_EXPANSION;
#[derive(Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct MultiByteChar {
pub pos: BytePos,
pub bytes: usize,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct FileMap {
pub name: FileName,
pub name_was_remapped: bool,
#[serde(skip, default = "invalid_crate")]
pub crate_of_origin: u32,
#[serde(skip)]
pub src: Option<Rc<String>>,
pub start_pos: BytePos,
pub end_pos: BytePos,
#[serde(
serialize_with = "serialize_lines",
deserialize_with = "deserialize_lines"
)]
pub lines: RefCell<Vec<BytePos>>,
pub multibyte_chars: RefCell<Vec<MultiByteChar>>,
}
fn invalid_crate() -> u32 {
::std::u32::MAX - 1
}
fn serialize_lines<S>(lines: &RefCell<Vec<BytePos>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let lines = lines.borrow();
if lines.is_empty() {
serializer.serialize_seq(Some(0))?.end()
} else {
let mut seq = serializer.serialize_seq(Some(lines.len() + 1))?;
let max_line_length = if lines.len() == 1 {
0
} else {
lines
.windows(2)
.map(|w| w[1] - w[0])
.map(|bp| bp.to_usize())
.max()
.unwrap()
};
let bytes_per_diff: u8 = match max_line_length {
0..=0xFF => 1,
0x100..=0xFFFF => 2,
_ => 4,
};
seq.serialize_element(&bytes_per_diff)?;
seq.serialize_element(&lines[0])?;
let diff_iter = (&lines[..]).windows(2).map(|w| (w[1] - w[0]));
match bytes_per_diff {
1 => {
for diff in diff_iter {
seq.serialize_element(&(diff.0 as u8))?
}
}
2 => {
for diff in diff_iter {
seq.serialize_element(&(diff.0 as u16))?
}
}
4 => {
for diff in diff_iter {
seq.serialize_element(&diff.0)?
}
}
_ => unreachable!(),
}
seq.end()
}
}
fn deserialize_lines<'de, D>(deserializer: D) -> Result<RefCell<Vec<BytePos>>, D::Error>
where
D: Deserializer<'de>,
{
struct LinesVisitor;
impl<'de> Visitor<'de> for LinesVisitor {
type Value = RefCell<Vec<BytePos>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("compressed locations of lines beginnings in the source code")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut lines = Vec::with_capacity(seq.size_hint().unwrap_or(0));
if let Some(bytes_per_diff) = seq.next_element::<u8>()? {
let mut line_start: BytePos = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
lines.push(line_start);
while let Some(diff) = match bytes_per_diff {
1 => seq.next_element::<u8>()?.map(|u| u as u32),
2 => seq.next_element::<u16>()?.map(|u| u as u32),
4 => seq.next_element::<u32>()?,
_ => {
return Err(de::Error::invalid_value(
Unexpected::Unsigned(bytes_per_diff as u64),
&"bytes per diff",
));
}
} {
line_start = line_start + BytePos(diff);
lines.push(line_start);
}
}
Ok(RefCell::new(lines))
}
}
deserializer.deserialize_seq(LinesVisitor)
}
impl fmt::Debug for FileMap {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "FileMap({})", self.name)
}
}
impl FileMap {
pub fn next_line(&self, pos: BytePos) {
let mut lines = self.lines.borrow_mut();
let line_len = lines.len();
assert!(line_len == 0 || ((*lines)[line_len - 1] < pos));
lines.push(pos);
}
pub fn get_line(&self, line_number: usize) -> Option<&str> {
match self.src {
Some(ref src) => {
let lines = self.lines.borrow();
lines.get(line_number).map(|&line| {
let begin: BytePos = line - self.start_pos;
let begin = begin.to_usize();
let slice = &src[begin..];
match slice.find('\n') {
Some(e) => &slice[..e],
None => slice,
}
})
}
None => None,
}
}
pub fn record_multibyte_char(&self, pos: BytePos, bytes: usize) {
assert!(bytes >= 2 && bytes <= 4);
let mbc = MultiByteChar { pos, bytes };
self.multibyte_chars.borrow_mut().push(mbc);
}
pub fn is_real_file(&self) -> bool {
!(self.name.starts_with('<') && self.name.ends_with('>'))
}
pub fn is_imported(&self) -> bool {
self.src.is_none()
}
pub fn byte_length(&self) -> u32 {
self.end_pos.0 - self.start_pos.0
}
pub fn count_lines(&self) -> usize {
self.lines.borrow().len()
}
pub fn lookup_line(&self, pos: BytePos) -> Option<usize> {
let lines = self.lines.borrow();
if lines.len() == 0 {
return None;
}
let line_index = lookup_line(&lines[..], pos);
assert!(line_index < lines.len() as isize);
if line_index >= 0 {
Some(line_index as usize)
} else {
None
}
}
pub fn line_bounds(&self, line_index: usize) -> (BytePos, BytePos) {
if self.start_pos == self.end_pos {
return (self.start_pos, self.end_pos);
}
let lines = self.lines.borrow();
assert!(line_index < lines.len());
if line_index == (lines.len() - 1) {
(lines[line_index], self.end_pos)
} else {
(lines[line_index], lines[line_index + 1])
}
}
}
pub trait Pos {
fn from_usize(n: usize) -> Self;
fn to_usize(&self) -> usize;
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub struct BytePos(pub u32);
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct CharPos(pub usize);
impl Pos for BytePos {
fn from_usize(n: usize) -> BytePos {
BytePos(n as u32)
}
fn to_usize(&self) -> usize {
let BytePos(n) = *self;
n as usize
}
}
impl Add for BytePos {
type Output = BytePos;
fn add(self, rhs: BytePos) -> BytePos {
BytePos((self.to_usize() + rhs.to_usize()) as u32)
}
}
impl Sub for BytePos {
type Output = BytePos;
fn sub(self, rhs: BytePos) -> BytePos {
BytePos((self.to_usize() - rhs.to_usize()) as u32)
}
}
impl Pos for CharPos {
fn from_usize(n: usize) -> CharPos {
CharPos(n)
}
fn to_usize(&self) -> usize {
let CharPos(n) = *self;
n
}
}
impl Add for CharPos {
type Output = CharPos;
fn add(self, rhs: CharPos) -> CharPos {
CharPos(self.to_usize() + rhs.to_usize())
}
}
impl Sub for CharPos {
type Output = CharPos;
fn sub(self, rhs: CharPos) -> CharPos {
CharPos(self.to_usize() - rhs.to_usize())
}
}
#[derive(Debug, Clone)]
pub struct Loc {
pub file: Rc<FileMap>,
pub line: usize,
pub col: CharPos,
}
#[derive(Debug)]
pub struct LocWithOpt {
pub filename: FileName,
pub line: usize,
pub col: CharPos,
pub file: Option<Rc<FileMap>>,
}
#[derive(Debug)]
pub struct FileMapAndLine {
pub fm: Rc<FileMap>,
pub line: usize,
}
#[derive(Debug)]
pub struct FileMapAndBytePos {
pub fm: Rc<FileMap>,
pub pos: BytePos,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LineInfo {
pub line_index: usize,
pub start_col: CharPos,
pub end_col: CharPos,
}
pub struct FileLines {
pub file: Rc<FileMap>,
pub lines: Vec<LineInfo>,
}
thread_local!(pub static SPAN_DEBUG: Cell<fn(Span, &mut fmt::Formatter) -> fmt::Result> =
Cell::new(default_span_debug));
pub struct MacroBacktrace {
pub call_site: Span,
pub macro_decl_name: String,
pub def_site_span: Option<Span>,
}
pub type FileLinesResult = Result<FileLines, SpanLinesError>;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SpanLinesError {
IllFormedSpan(Span),
DistinctSources(DistinctSources),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SpanSnippetError {
IllFormedSpan(Span),
DistinctSources(DistinctSources),
MalformedForCodemap(MalformedCodemapPositions),
SourceNotAvailable { filename: String },
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DistinctSources {
pub begin: (String, BytePos),
pub end: (String, BytePos),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MalformedCodemapPositions {
pub name: String,
pub source_len: usize,
pub begin_pos: BytePos,
pub end_pos: BytePos,
}
fn lookup_line(lines: &[BytePos], pos: BytePos) -> isize {
match lines.binary_search(&pos) {
Ok(line) => line as isize,
Err(line) => line as isize - 1,
}
}
#[cfg(test)]
mod tests {
use super::{lookup_line, BytePos};
#[test]
fn test_lookup_line() {
let lines = &[BytePos(3), BytePos(17), BytePos(28)];
assert_eq!(lookup_line(lines, BytePos(0)), -1);
assert_eq!(lookup_line(lines, BytePos(3)), 0);
assert_eq!(lookup_line(lines, BytePos(4)), 0);
assert_eq!(lookup_line(lines, BytePos(16)), 0);
assert_eq!(lookup_line(lines, BytePos(17)), 1);
assert_eq!(lookup_line(lines, BytePos(18)), 1);
assert_eq!(lookup_line(lines, BytePos(28)), 2);
assert_eq!(lookup_line(lines, BytePos(29)), 2);
}
}