From 28d026fb69a3d11a14b842f5cc0f2d6ec9a4b10b Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Fri, 20 May 2022 19:01:16 +0200 Subject: [PATCH 1/2] Add a helper for getting the location of a span in some textual source. --- src/front/wgsl/mod.rs | 18 ++--- src/lib.rs | 2 +- src/span.rs | 150 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 13 deletions(-) diff --git a/src/front/wgsl/mod.rs b/src/front/wgsl/mod.rs index 94c3510d7f..76a61e98ea 100644 --- a/src/front/wgsl/mod.rs +++ b/src/front/wgsl/mod.rs @@ -16,6 +16,7 @@ use crate::{ proc::{ ensure_block_returns, Alignment, Layouter, ResolveContext, ResolveError, TypeResolution, }, + span::SourceLocation, span::Span as NagaSpan, Bytes, ConstantInner, FastHashMap, ScalarValue, }; @@ -29,7 +30,7 @@ use self::{ }; use codespan_reporting::{ diagnostic::{Diagnostic, Label}, - files::{Files, SimpleFile}, + files::SimpleFile, term::{ self, termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}, @@ -1367,17 +1368,10 @@ impl ParseError { /// Returns the 1-based line number and column of the first label in the /// error message. - pub fn location(&self, source: &str) -> (usize, usize) { - let files = SimpleFile::new("wgsl", source); - match self.labels.get(0) { - Some(label) => { - let location = files - .location((), label.0.start) - .expect("invalid span location"); - (location.line_number, location.column_number) - } - None => (1, 1), - } + pub fn location(&self, source: &str) -> Option { + self.labels + .get(0) + .map(|label| NagaSpan::new(label.0.start as u32, label.0.end as u32).location(source)) } } diff --git a/src/lib.rs b/src/lib.rs index 80e7e6f789..60bbe0e28a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,7 +211,7 @@ pub mod valid; pub use crate::arena::{Arena, Handle, Range, UniqueArena}; -pub use crate::span::{Span, SpanContext, WithSpan}; +pub use crate::span::{SourceLocation, Span, SpanContext, WithSpan}; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; #[cfg(feature = "deserialize")] diff --git a/src/span.rs b/src/span.rs index 0c60ee005c..1c2249c4a0 100644 --- a/src/span.rs +++ b/src/span.rs @@ -59,6 +59,22 @@ impl Span { pub fn is_defined(&self) -> bool { *self != Self::default() } + + /// Returns the 1-based line number and column of the 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> for Span { @@ -70,6 +86,22 @@ impl From> for Span { } } +/// A human-readable representation for span, tailored for text source. +/// +/// Corresponds to the positional members of `GPUCompilationMessage` from the WebGPU specification, +/// using utf8 instead of utf16 as reference encoding. +#[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); @@ -186,6 +218,20 @@ impl WithSpan { res.spans.extend(self.spans); res } + + #[cfg(feature = "span")] + pub fn location(&self, source: &str) -> Option { + if self.spans.is_empty() { + return None; + } + + Some(self.spans[0].0.location(source)) + } + + #[cfg(not(feature = "span"))] + pub fn location(&self, _source: &str) -> Option { + None + } } /// Convenience trait for [`Error`] to be able to apply spans to anything. @@ -273,3 +319,107 @@ impl MapErrWithSpan for Result> { self.map_err(|e| e.and_then(func).into_other::()) } } + +#[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 + } + ); +} From 9574b195e89630d1ff293f92e1f21fa982622fa8 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 25 May 2022 17:10:20 -0700 Subject: [PATCH 2/2] Doc tweaks. --- src/front/wgsl/mod.rs | 3 +-- src/span.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/front/wgsl/mod.rs b/src/front/wgsl/mod.rs index 76a61e98ea..a7c1fdced6 100644 --- a/src/front/wgsl/mod.rs +++ b/src/front/wgsl/mod.rs @@ -1366,8 +1366,7 @@ impl ParseError { writer.into_string() } - /// Returns the 1-based line number and column of the first label in the - /// error message. + /// Returns a [`SourceLocation`] for the first label in the error message. pub fn location(&self, source: &str) -> Option { self.labels .get(0) diff --git a/src/span.rs b/src/span.rs index 1c2249c4a0..6f2583a5eb 100644 --- a/src/span.rs +++ b/src/span.rs @@ -60,8 +60,7 @@ impl Span { *self != Self::default() } - /// Returns the 1-based line number and column of the this span in - /// the provided source. + /// 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; @@ -86,10 +85,13 @@ impl From> for Span { } } -/// A human-readable representation for span, tailored for text source. +/// A human-readable representation for a span, tailored for text source. /// -/// Corresponds to the positional members of `GPUCompilationMessage` from the WebGPU specification, -/// using utf8 instead of utf16 as reference encoding. +/// 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. @@ -220,6 +222,7 @@ impl WithSpan { } #[cfg(feature = "span")] + /// Return a [`SourceLocation`] for our first span, if we have one. pub fn location(&self, source: &str) -> Option { if self.spans.is_empty() { return None; @@ -229,6 +232,7 @@ impl WithSpan { } #[cfg(not(feature = "span"))] + /// Return a [`SourceLocation`] for our first span, if we have one. pub fn location(&self, _source: &str) -> Option { None }