mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
528 lines
15 KiB
Rust
528 lines
15 KiB
Rust
use crate::{Arena, Handle, UniqueArena};
|
|
use std::{error::Error, fmt, ops::Range};
|
|
|
|
/// A source code span, used for error reporting.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
|
pub struct Span {
|
|
start: u32,
|
|
end: u32,
|
|
}
|
|
|
|
impl Span {
|
|
pub const UNDEFINED: Self = Self { start: 0, end: 0 };
|
|
/// Creates a new `Span` from a range of byte indices
|
|
///
|
|
/// Note: end is exclusive, it doesn't belong to the `Span`
|
|
pub const fn new(start: u32, end: u32) -> Self {
|
|
Span { start, end }
|
|
}
|
|
|
|
/// Returns a new `Span` starting at `self` and ending at `other`
|
|
pub const fn until(&self, other: &Self) -> Self {
|
|
Span {
|
|
start: self.start,
|
|
end: other.end,
|
|
}
|
|
}
|
|
|
|
/// Modifies `self` to contain the smallest `Span` possible that
|
|
/// contains both `self` and `other`
|
|
pub fn subsume(&mut self, other: Self) {
|
|
*self = if !self.is_defined() {
|
|
// self isn't defined so use other
|
|
other
|
|
} else if !other.is_defined() {
|
|
// other isn't defined so don't try to subsume
|
|
*self
|
|
} else {
|
|
// Both self and other are defined so calculate the span that contains them both
|
|
Span {
|
|
start: self.start.min(other.start),
|
|
end: self.end.max(other.end),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the smallest `Span` possible that contains all the `Span`s
|
|
/// defined in the `from` iterator
|
|
pub fn total_span<T: Iterator<Item = Self>>(from: T) -> Self {
|
|
let mut span: Self = Default::default();
|
|
for other in from {
|
|
span.subsume(other);
|
|
}
|
|
span
|
|
}
|
|
|
|
/// Converts `self` to a range if the span is not unknown
|
|
pub fn to_range(self) -> Option<Range<usize>> {
|
|
if self.is_defined() {
|
|
Some(self.start as usize..self.end as usize)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Check whether `self` was defined or is a default/unknown span
|
|
pub fn is_defined(&self) -> bool {
|
|
*self != Self::default()
|
|
}
|
|
|
|
/// Return a [`SourceLocation`] for this span in the provided source.
|
|
pub fn location(&self, source: &str) -> SourceLocation {
|
|
let prefix = &source[..self.start as usize];
|
|
let line_number = prefix.matches('\n').count() as u32 + 1;
|
|
let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
|
|
let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1;
|
|
|
|
SourceLocation {
|
|
line_number,
|
|
line_position,
|
|
offset: self.start,
|
|
length: self.end - self.start,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Range<usize>> for Span {
|
|
fn from(range: Range<usize>) -> Self {
|
|
Span {
|
|
start: range.start as u32,
|
|
end: range.end as u32,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::Index<Span> for str {
|
|
type Output = str;
|
|
|
|
#[inline]
|
|
fn index(&self, span: Span) -> &str {
|
|
&self[span.start as usize..span.end as usize]
|
|
}
|
|
}
|
|
|
|
/// A human-readable representation for a span, tailored for text source.
|
|
///
|
|
/// Corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
|
|
/// the WebGPU specification, except that `offset` and `length` are in bytes
|
|
/// (UTF-8 code units), instead of UTF-16 code units.
|
|
///
|
|
/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub struct SourceLocation {
|
|
/// 1-based line number.
|
|
pub line_number: u32,
|
|
/// 1-based column of the start of this span
|
|
pub line_position: u32,
|
|
/// 0-based Offset in code units (in bytes) of the start of the span.
|
|
pub offset: u32,
|
|
/// Length in code units (in bytes) of the span.
|
|
pub length: u32,
|
|
}
|
|
|
|
/// A source code span together with "context", a user-readable description of what part of the error it refers to.
|
|
pub type SpanContext = (Span, String);
|
|
|
|
/// Wrapper class for [`Error`], augmenting it with a list of [`SpanContext`]s.
|
|
#[derive(Debug, Clone)]
|
|
pub struct WithSpan<E> {
|
|
inner: E,
|
|
#[cfg(feature = "span")]
|
|
spans: Vec<SpanContext>,
|
|
}
|
|
|
|
impl<E> fmt::Display for WithSpan<E>
|
|
where
|
|
E: fmt::Display,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.inner.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
impl<E> PartialEq for WithSpan<E>
|
|
where
|
|
E: PartialEq,
|
|
{
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.inner.eq(&other.inner)
|
|
}
|
|
}
|
|
|
|
impl<E> Error for WithSpan<E>
|
|
where
|
|
E: Error,
|
|
{
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
self.inner.source()
|
|
}
|
|
}
|
|
|
|
impl<E> WithSpan<E> {
|
|
/// Create a new [`WithSpan`] from an [`Error`], containing no spans.
|
|
pub const fn new(inner: E) -> Self {
|
|
Self {
|
|
inner,
|
|
#[cfg(feature = "span")]
|
|
spans: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Reverse of [`Self::new`], discards span information and returns an inner error.
|
|
#[allow(clippy::missing_const_for_fn)] // ignore due to requirement of #![feature(const_precise_live_drops)]
|
|
pub fn into_inner(self) -> E {
|
|
self.inner
|
|
}
|
|
|
|
pub const fn as_inner(&self) -> &E {
|
|
&self.inner
|
|
}
|
|
|
|
/// Iterator over stored [`SpanContext`]s.
|
|
pub fn spans(&self) -> impl ExactSizeIterator<Item = &SpanContext> {
|
|
#[cfg(feature = "span")]
|
|
return self.spans.iter();
|
|
#[cfg(not(feature = "span"))]
|
|
return std::iter::empty();
|
|
}
|
|
|
|
/// Add a new span with description.
|
|
#[cfg_attr(
|
|
not(feature = "span"),
|
|
allow(unused_variables, unused_mut, clippy::missing_const_for_fn)
|
|
)]
|
|
pub fn with_span<S>(mut self, span: Span, description: S) -> Self
|
|
where
|
|
S: ToString,
|
|
{
|
|
#[cfg(feature = "span")]
|
|
if span.is_defined() {
|
|
self.spans.push((span, description.to_string()));
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Add a [`SpanContext`].
|
|
pub fn with_context(self, span_context: SpanContext) -> Self {
|
|
let (span, description) = span_context;
|
|
self.with_span(span, description)
|
|
}
|
|
|
|
/// Add a [`Handle`] from either [`Arena`] or [`UniqueArena`], borrowing its span information from there
|
|
/// and annotating with a type and the handle representation.
|
|
pub(crate) fn with_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self {
|
|
self.with_context(arena.get_span_context(handle))
|
|
}
|
|
|
|
/// Convert inner error using [`From`].
|
|
pub fn into_other<E2>(self) -> WithSpan<E2>
|
|
where
|
|
E2: From<E>,
|
|
{
|
|
WithSpan {
|
|
inner: self.inner.into(),
|
|
#[cfg(feature = "span")]
|
|
spans: self.spans,
|
|
}
|
|
}
|
|
|
|
/// Convert inner error into another type. Joins span information contained in `self`
|
|
/// with what is returned from `func`.
|
|
pub fn and_then<F, E2>(self, func: F) -> WithSpan<E2>
|
|
where
|
|
F: FnOnce(E) -> WithSpan<E2>,
|
|
{
|
|
#[cfg_attr(not(feature = "span"), allow(unused_mut))]
|
|
let mut res = func(self.inner);
|
|
#[cfg(feature = "span")]
|
|
res.spans.extend(self.spans);
|
|
res
|
|
}
|
|
|
|
#[cfg(feature = "span")]
|
|
/// Return a [`SourceLocation`] for our first span, if we have one.
|
|
pub fn location(&self, source: &str) -> Option<SourceLocation> {
|
|
if self.spans.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(self.spans[0].0.location(source))
|
|
}
|
|
|
|
#[cfg(not(feature = "span"))]
|
|
#[allow(clippy::missing_const_for_fn)]
|
|
/// Return a [`SourceLocation`] for our first span, if we have one.
|
|
pub fn location(&self, _source: &str) -> Option<SourceLocation> {
|
|
None
|
|
}
|
|
|
|
#[cfg(feature = "span")]
|
|
fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()>
|
|
where
|
|
E: Error,
|
|
{
|
|
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
|
let diagnostic = Diagnostic::error()
|
|
.with_message(self.inner.to_string())
|
|
.with_labels(
|
|
self.spans()
|
|
.map(|&(span, ref desc)| {
|
|
Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
|
|
})
|
|
.collect(),
|
|
)
|
|
.with_notes({
|
|
let mut notes = Vec::new();
|
|
let mut source: &dyn Error = &self.inner;
|
|
while let Some(next) = Error::source(source) {
|
|
notes.push(next.to_string());
|
|
source = next;
|
|
}
|
|
notes
|
|
});
|
|
diagnostic
|
|
}
|
|
|
|
/// Emits a summary of the error to standard error stream.
|
|
#[cfg(feature = "span")]
|
|
pub fn emit_to_stderr(&self, source: &str)
|
|
where
|
|
E: Error,
|
|
{
|
|
self.emit_to_stderr_with_path(source, "wgsl")
|
|
}
|
|
|
|
/// Emits a summary of the error to standard error stream.
|
|
#[cfg(feature = "span")]
|
|
pub fn emit_to_stderr_with_path(&self, source: &str, path: &str)
|
|
where
|
|
E: Error,
|
|
{
|
|
use codespan_reporting::{files, term};
|
|
use term::termcolor::{ColorChoice, StandardStream};
|
|
|
|
let files = files::SimpleFile::new(path, source);
|
|
let config = term::Config::default();
|
|
let writer = StandardStream::stderr(ColorChoice::Auto);
|
|
term::emit(&mut writer.lock(), &config, &files, &self.diagnostic())
|
|
.expect("cannot write error");
|
|
}
|
|
|
|
/// Emits a summary of the error to a string.
|
|
#[cfg(feature = "span")]
|
|
pub fn emit_to_string(&self, source: &str) -> String
|
|
where
|
|
E: Error,
|
|
{
|
|
self.emit_to_string_with_path(source, "wgsl")
|
|
}
|
|
|
|
/// Emits a summary of the error to a string.
|
|
#[cfg(feature = "span")]
|
|
pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String
|
|
where
|
|
E: Error,
|
|
{
|
|
use codespan_reporting::{files, term};
|
|
use term::termcolor::NoColor;
|
|
|
|
let files = files::SimpleFile::new(path, source);
|
|
let config = codespan_reporting::term::Config::default();
|
|
let mut writer = NoColor::new(Vec::new());
|
|
term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error");
|
|
String::from_utf8(writer.into_inner()).unwrap()
|
|
}
|
|
}
|
|
|
|
/// Convenience trait for [`Error`] to be able to apply spans to anything.
|
|
pub(crate) trait AddSpan: Sized {
|
|
type Output;
|
|
/// See [`WithSpan::new`].
|
|
fn with_span(self) -> Self::Output;
|
|
/// See [`WithSpan::with_span`].
|
|
fn with_span_static(self, span: Span, description: &'static str) -> Self::Output;
|
|
/// See [`WithSpan::with_context`].
|
|
fn with_span_context(self, span_context: SpanContext) -> Self::Output;
|
|
/// See [`WithSpan::with_handle`].
|
|
fn with_span_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self::Output;
|
|
}
|
|
|
|
/// Trait abstracting over getting a span from an [`Arena`] or a [`UniqueArena`].
|
|
pub(crate) trait SpanProvider<T> {
|
|
fn get_span(&self, handle: Handle<T>) -> Span;
|
|
fn get_span_context(&self, handle: Handle<T>) -> SpanContext {
|
|
match self.get_span(handle) {
|
|
x if !x.is_defined() => (Default::default(), "".to_string()),
|
|
known => (
|
|
known,
|
|
format!("{} {:?}", std::any::type_name::<T>(), handle),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> SpanProvider<T> for Arena<T> {
|
|
fn get_span(&self, handle: Handle<T>) -> Span {
|
|
self.get_span(handle)
|
|
}
|
|
}
|
|
|
|
impl<T> SpanProvider<T> for UniqueArena<T> {
|
|
fn get_span(&self, handle: Handle<T>) -> Span {
|
|
self.get_span(handle)
|
|
}
|
|
}
|
|
|
|
impl<E> AddSpan for E
|
|
where
|
|
E: Error,
|
|
{
|
|
type Output = WithSpan<Self>;
|
|
fn with_span(self) -> WithSpan<Self> {
|
|
WithSpan::new(self)
|
|
}
|
|
|
|
fn with_span_static(self, span: Span, description: &'static str) -> WithSpan<Self> {
|
|
WithSpan::new(self).with_span(span, description)
|
|
}
|
|
|
|
fn with_span_context(self, span_context: SpanContext) -> WithSpan<Self> {
|
|
WithSpan::new(self).with_context(span_context)
|
|
}
|
|
|
|
fn with_span_handle<T, A: SpanProvider<T>>(
|
|
self,
|
|
handle: Handle<T>,
|
|
arena: &A,
|
|
) -> WithSpan<Self> {
|
|
WithSpan::new(self).with_handle(handle, arena)
|
|
}
|
|
}
|
|
|
|
/// Convenience trait for [`Result`], adding a [`MapErrWithSpan::map_err_inner`]
|
|
/// mapping to [`WithSpan::and_then`].
|
|
pub trait MapErrWithSpan<E, E2>: Sized {
|
|
type Output: Sized;
|
|
fn map_err_inner<F, E3>(self, func: F) -> Self::Output
|
|
where
|
|
F: FnOnce(E) -> WithSpan<E3>,
|
|
E2: From<E3>;
|
|
}
|
|
|
|
impl<T, E, E2> MapErrWithSpan<E, E2> for Result<T, WithSpan<E>> {
|
|
type Output = Result<T, WithSpan<E2>>;
|
|
fn map_err_inner<F, E3>(self, func: F) -> Result<T, WithSpan<E2>>
|
|
where
|
|
F: FnOnce(E) -> WithSpan<E3>,
|
|
E2: From<E3>,
|
|
{
|
|
self.map_err(|e| e.and_then(func).into_other::<E2>())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn span_location() {
|
|
let source = "12\n45\n\n89\n";
|
|
assert_eq!(
|
|
Span { start: 0, end: 1 }.location(source),
|
|
SourceLocation {
|
|
line_number: 1,
|
|
line_position: 1,
|
|
offset: 0,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 1, end: 2 }.location(source),
|
|
SourceLocation {
|
|
line_number: 1,
|
|
line_position: 2,
|
|
offset: 1,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 2, end: 3 }.location(source),
|
|
SourceLocation {
|
|
line_number: 1,
|
|
line_position: 3,
|
|
offset: 2,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 3, end: 5 }.location(source),
|
|
SourceLocation {
|
|
line_number: 2,
|
|
line_position: 1,
|
|
offset: 3,
|
|
length: 2
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 4, end: 6 }.location(source),
|
|
SourceLocation {
|
|
line_number: 2,
|
|
line_position: 2,
|
|
offset: 4,
|
|
length: 2
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 5, end: 6 }.location(source),
|
|
SourceLocation {
|
|
line_number: 2,
|
|
line_position: 3,
|
|
offset: 5,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 6, end: 7 }.location(source),
|
|
SourceLocation {
|
|
line_number: 3,
|
|
line_position: 1,
|
|
offset: 6,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 7, end: 8 }.location(source),
|
|
SourceLocation {
|
|
line_number: 4,
|
|
line_position: 1,
|
|
offset: 7,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 8, end: 9 }.location(source),
|
|
SourceLocation {
|
|
line_number: 4,
|
|
line_position: 2,
|
|
offset: 8,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 9, end: 10 }.location(source),
|
|
SourceLocation {
|
|
line_number: 4,
|
|
line_position: 3,
|
|
offset: 9,
|
|
length: 1
|
|
}
|
|
);
|
|
assert_eq!(
|
|
Span { start: 10, end: 11 }.location(source),
|
|
SourceLocation {
|
|
line_number: 5,
|
|
line_position: 1,
|
|
offset: 10,
|
|
length: 1
|
|
}
|
|
);
|
|
}
|