diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index e7ba0dcf1..09ae1b428 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -190,7 +190,7 @@ impl SystemWorld { source.lines() } else if let Some(bytes) = slot.file.get() { let bytes = bytes.as_ref().expect("file is not valid"); - Lines::from_bytes(bytes.as_slice()).expect("file is not valid utf-8") + Lines::from_bytes(bytes).expect("file is not valid utf-8") } else { panic!("file id does not point to any source file"); } diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index 05d60d330..a8f4a0c81 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use typst_library::diag::{warning, At, LoadedAt, SourceResult, StrResult}; +use typst_library::diag::{warning, At, LoadedWithin, SourceResult, StrResult}; use typst_library::engine::Engine; use typst_library::foundations::{Bytes, Derived, Packed, Smart, StyleChain}; use typst_library::introspection::Locator; @@ -27,17 +27,17 @@ pub fn layout_image( // Take the format that was explicitly defined, or parse the extension, // or try to detect the format. - let Derived { source, derived: data } = &elem.source; + let Derived { source, derived: loaded } = &elem.source; let format = match elem.format(styles) { Smart::Custom(v) => v, - Smart::Auto => determine_format(source, &data.bytes).at(span)?, + Smart::Auto => determine_format(source, &loaded.data).at(span)?, }; // Warn the user if the image contains a foreign object. Not perfect // because the svg could also be encoded, but that's an edge case. if format == ImageFormat::Vector(VectorFormat::Svg) { let has_foreign_object = - memchr::memmem::find(&data.bytes, b" ImageKind::Raster( RasterImage::new( - data.bytes.clone(), + loaded.data.clone(), format, elem.icc(styles).as_ref().map(|icc| icc.derived.clone()), ) @@ -61,11 +61,11 @@ pub fn layout_image( ), ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg( SvgImage::with_fonts( - data.bytes.clone(), + loaded.data.clone(), engine.world, &families(styles).map(|f| f.as_str()).collect::>(), ) - .in_text(data)?, + .within(loaded)?, ), }; diff --git a/crates/typst-library/src/diag.rs b/crates/typst-library/src/diag.rs index 9622524e3..90caabcc3 100644 --- a/crates/typst-library/src/diag.rs +++ b/crates/typst-library/src/diag.rs @@ -1,6 +1,6 @@ //! Diagnostics. -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Formatter, Write as _}; use std::io; use std::path::{Path, PathBuf}; use std::str::Utf8Error; @@ -581,9 +581,9 @@ pub type LoadResult = Result; /// [`FileId`]: typst_syntax::FileId #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LoadError { - pub pos: ReportPos, - pub message: EcoString, - pub error: EcoString, + pos: ReportPos, + /// Must contain a message formatted like this: `"failed to do thing (cause)"`. + message: EcoString, } impl LoadError { @@ -594,109 +594,98 @@ impl LoadError { ) -> Self { Self { pos: pos.into(), - message: eco_format!("{message}"), - error: eco_format!("{error}"), + message: eco_format!("{message} ({error})"), } } } /// Convert a [`LoadResult`] to a [`SourceResult`] by adding the [`Loaded`] context. -pub trait LoadedAt { - /// Add the span information. - fn in_text(self, data: &Loaded) -> SourceResult; - - /// Add the span information. - fn in_invalid_text(self, data: &Loaded) -> SourceResult; +pub trait LoadedWithin { + /// Report an error, possibly in an external file. + fn within(self, loaded: &Loaded) -> SourceResult; } -impl LoadedAt for Result { - /// Report an error, possibly in an external file. - fn in_text(self, data: &Loaded) -> SourceResult { - self.map_err(|err| data.err_in_text(err.pos, err.message, err.error)) - } - - /// Report an error in invalid text. - fn in_invalid_text(self, data: &Loaded) -> SourceResult { - self.map_err(|err| data.err_in_invalid_text(err.pos, err.message, err.error)) +impl LoadedWithin for Result { + fn within(self, loaded: &Loaded) -> SourceResult { + self.map_err(|err| load_err_in_text(loaded, err.pos, err.message)) } } -impl Loaded { - /// Report an error, possibly in an external file. - pub fn err_in_text( - &self, - pos: impl Into, - msg: impl std::fmt::Display, - error: impl std::fmt::Display, - ) -> EcoVec { - let pos = pos.into(); - // This also does utf-8 validation. Only report an error in an external - // file if it is human readable (valid utf-8), otherwise fall back to - // `err_in_invalid_text`. - let lines = Lines::from_bytes(&self.bytes); - match (self.source.v, lines) { - (LoadSource::Path(file_id), Ok(lines)) => { - if let Some(range) = pos.range(&lines) { - let span = Span::from_range(file_id, range); - return eco_vec!(error!(span, "{msg} ({error})")); - } +/// Report an error, possibly in an external file. This will delegate to +/// [`load_err_in_invalid_text`] if the data isn't valid utf-8. +fn load_err_in_text( + loaded: &Loaded, + pos: impl Into, + mut message: EcoString, +) -> EcoVec { + let pos = pos.into(); + // This also does utf-8 validation. Only report an error in an external + // file if it is human readable (valid utf-8), otherwise fall back to + // `load_err_in_invalid_text`. + let lines = Lines::from_bytes(&loaded.data); + match (loaded.source.v, lines) { + (LoadSource::Path(file_id), Ok(lines)) => { + if let Some(range) = pos.range(&lines) { + let span = Span::from_range(file_id, range); + return eco_vec![SourceDiagnostic::error(span, message)]; + } - // Either `ReportPos::None` was provided, or resolving the range - // from the line/column failed. If present report the possibly - // wrong line/column in the error message anyway. - let span = Span::from_range(file_id, 0..self.bytes.len()); - let error = if let Some(pair) = pos.line_col(&lines) { - let (line, col) = pair.numbers(); - error!(span, "{msg} ({error} at {line}:{col})") - } else { - error!(span, "{msg} ({error})") - }; - eco_vec![error] + // Either `ReportPos::None` was provided, or resolving the range + // from the line/column failed. If present report the possibly + // wrong line/column in the error message anyway. + let span = Span::from_range(file_id, 0..loaded.data.len()); + if let Some(pair) = pos.line_col(&lines) { + message.pop(); + let (line, col) = pair.numbers(); + write!(&mut message, " at {line}:{col})").ok(); } - (LoadSource::Bytes, Ok(lines)) => { - let error = if let Some(pair) = pos.line_col(&lines) { - let (line, col) = pair.numbers(); - error!(self.source.span, "{msg} ({error} at {line}:{col})") - } else { - error!(self.source.span, "{msg} ({error})") - }; - eco_vec![error] - } - _ => self.err_in_invalid_text(pos, msg, error), + eco_vec![SourceDiagnostic::error(span, message)] } + (LoadSource::Bytes, Ok(lines)) => { + if let Some(pair) = pos.line_col(&lines) { + message.pop(); + let (line, col) = pair.numbers(); + write!(&mut message, " at {line}:{col})").ok(); + } + eco_vec![SourceDiagnostic::error(loaded.source.span, message)] + } + _ => load_err_in_invalid_text(loaded, pos, message), } +} - /// Report an error (possibly from an external file) that isn't valid utf-8. - pub fn err_in_invalid_text( - &self, - pos: impl Into, - msg: impl std::fmt::Display, - error: impl std::fmt::Display, - ) -> EcoVec { - let line_col = pos.into().try_line_col(&self.bytes).map(|p| p.numbers()); - let error = match (self.source.v, line_col) { - (LoadSource::Path(file), _) => { - let path = if let Some(package) = file.package() { - format!("{package}{}", file.vpath().as_rooted_path().display()) - } else { - format!("{}", file.vpath().as_rootless_path().display()) - }; - - if let Some((line, col)) = line_col { - error!(self.source.span, "{msg} ({error} in {path}:{line}:{col})") - } else { - error!(self.source.span, "{msg} ({error} in {path})") - } +/// Report an error (possibly from an external file) that isn't valid utf-8. +fn load_err_in_invalid_text( + loaded: &Loaded, + pos: impl Into, + mut message: EcoString, +) -> EcoVec { + let line_col = pos.into().try_line_col(&loaded.data).map(|p| p.numbers()); + match (loaded.source.v, line_col) { + (LoadSource::Path(file), _) => { + message.pop(); + if let Some(package) = file.package() { + write!( + &mut message, + " in {package}{}", + file.vpath().as_rooted_path().display() + ) + .ok(); + } else { + write!(&mut message, " in {}", file.vpath().as_rootless_path().display()) + .ok(); + }; + if let Some((line, col)) = line_col { + write!(&mut message, ":{line}:{col}").ok(); } - (LoadSource::Bytes, Some((line, col))) => { - error!(self.source.span, "{msg} ({error} at {line}:{col})") - } - (LoadSource::Bytes, None) => { - error!(self.source.span, "{msg} ({error})") - } - }; - eco_vec![error] + message.push(')'); + } + (LoadSource::Bytes, Some((line, col))) => { + message.pop(); + write!(&mut message, " at {line}:{col})").ok(); + } + (LoadSource::Bytes, None) => (), } + eco_vec![SourceDiagnostic::error(loaded.source.span, message)] } #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] @@ -819,22 +808,21 @@ impl LineCol { /// Format a user-facing error message for an XML-like file format. pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> LoadError { let pos = LineCol::one_based(error.pos().row as usize, error.pos().col as usize); - let message = eco_format!("failed to parse {format}"); - let error = match error { + let message = match error { roxmltree::Error::UnexpectedCloseTag(expected, actual, _) => { - eco_format!("found closing tag '{actual}' instead of '{expected}'") + eco_format!("failed to parse {format} (found closing tag '{actual}' instead of '{expected}')") } roxmltree::Error::UnknownEntityReference(entity, _) => { - eco_format!("unknown entity '{entity}'") + eco_format!("failed to parse {format} (unknown entity '{entity}')") } roxmltree::Error::DuplicatedAttribute(attr, _) => { - eco_format!("duplicate attribute '{attr}'") + eco_format!("failed to parse {format} (duplicate attribute '{attr}')") } roxmltree::Error::NoRootNode => { - eco_format!("missing root node") + eco_format!("failed to parse {format} (missing root node)") } - err => eco_format!("{err}"), + err => eco_format!("failed to parse {format} ({err})"), }; - LoadError { pos: pos.into(), message, error } + LoadError { pos: pos.into(), message } } diff --git a/crates/typst-library/src/foundations/bytes.rs b/crates/typst-library/src/foundations/bytes.rs index d633c99ad..6bbf6bb58 100644 --- a/crates/typst-library/src/foundations/bytes.rs +++ b/crates/typst-library/src/foundations/bytes.rs @@ -9,7 +9,7 @@ use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; use typst_utils::LazyHash; -use crate::diag::{bail, StrResult}; +use crate::diag::{bail, LoadError, LoadResult, StrResult}; use crate::foundations::{cast, func, scope, ty, Array, Reflect, Repr, Str, Value}; /// A sequence of bytes. @@ -112,6 +112,21 @@ impl Bytes { } } + pub fn load_str(&self) -> LoadResult<&str> { + match self.inner().as_any().downcast_ref::() { + Some(string) => Ok(string.as_str()), + None => self.as_str().map_err(|err| { + let start = err.valid_up_to(); + let end = start + err.error_len().unwrap_or(0); + LoadError::new( + start..end, + "failed to convert to string", + "file is not valid utf-8", + ) + }), + } + } + /// Resolve an index or throw an out of bounds error. fn locate(&self, index: i64) -> StrResult { self.locate_opt(index).ok_or_else(|| out_of_bounds(index, self.len())) diff --git a/crates/typst-library/src/foundations/plugin.rs b/crates/typst-library/src/foundations/plugin.rs index e713fa688..a04443bf4 100644 --- a/crates/typst-library/src/foundations/plugin.rs +++ b/crates/typst-library/src/foundations/plugin.rs @@ -151,8 +151,8 @@ pub fn plugin( /// A [path]($syntax/#paths) to a WebAssembly file or raw WebAssembly bytes. source: Spanned, ) -> SourceResult { - let data = source.load(engine.world)?; - Plugin::module(data.bytes).at(source.span) + let loaded = source.load(engine.world)?; + Plugin::module(loaded.data).at(source.span) } #[scope] diff --git a/crates/typst-library/src/loading/cbor.rs b/crates/typst-library/src/loading/cbor.rs index 20837a9d9..d95f73844 100644 --- a/crates/typst-library/src/loading/cbor.rs +++ b/crates/typst-library/src/loading/cbor.rs @@ -23,8 +23,8 @@ pub fn cbor( /// A [path]($syntax/#paths) to a CBOR file or raw CBOR bytes. source: Spanned, ) -> SourceResult { - let data = source.load(engine.world)?; - ciborium::from_reader(data.bytes.as_slice()) + let loaded = source.load(engine.world)?; + ciborium::from_reader(loaded.data.as_slice()) .map_err(|err| eco_format!("failed to parse CBOR ({err})")) .at(source.span) } diff --git a/crates/typst-library/src/loading/csv.rs b/crates/typst-library/src/loading/csv.rs index 8386b5a52..d5b54a06c 100644 --- a/crates/typst-library/src/loading/csv.rs +++ b/crates/typst-library/src/loading/csv.rs @@ -1,11 +1,10 @@ use az::SaturatingAs; -use ecow::EcoVec; use typst_syntax::Spanned; -use crate::diag::{bail, LineCol, ReportPos, SourceDiagnostic, SourceResult}; +use crate::diag::{bail, LineCol, LoadError, LoadedWithin, ReportPos, SourceResult}; use crate::engine::Engine; use crate::foundations::{cast, func, scope, Array, Dict, IntoValue, Type, Value}; -use crate::loading::{DataSource, Load, Loaded, Readable}; +use crate::loading::{DataSource, Load, Readable}; /// Reads structured data from a CSV file. /// @@ -45,7 +44,7 @@ pub fn csv( #[default(RowType::Array)] row_type: RowType, ) -> SourceResult { - let data = source.load(engine.world)?; + let loaded = source.load(engine.world)?; let mut builder = ::csv::ReaderBuilder::new(); let has_headers = row_type == RowType::Dict; @@ -54,7 +53,7 @@ pub fn csv( // Counting lines from 1 by default. let mut line_offset: usize = 1; - let mut reader = builder.from_reader(data.bytes.as_slice()); + let mut reader = builder.from_reader(loaded.data.as_slice()); let mut headers: Option<::csv::StringRecord> = None; if has_headers { @@ -64,7 +63,8 @@ pub fn csv( reader .headers() .cloned() - .map_err(|err| format_csv_error(&data, err, 1))?, + .map_err(|err| format_csv_error(err, 1)) + .within(&loaded)?, ); } @@ -74,7 +74,7 @@ pub fn csv( // incorrect with `has_headers` set to `false`. See issue: // https://github.com/BurntSushi/rust-csv/issues/184 let line = line + line_offset; - let row = result.map_err(|err| format_csv_error(&data, err, line))?; + let row = result.map_err(|err| format_csv_error(err, line)).within(&loaded)?; let item = if let Some(headers) = &headers { let mut dict = Dict::new(); for (field, value) in headers.iter().zip(&row) { @@ -164,11 +164,7 @@ cast! { } /// Format the user-facing CSV error message. -fn format_csv_error( - data: &Loaded, - err: ::csv::Error, - line: usize, -) -> EcoVec { +fn format_csv_error(err: ::csv::Error, line: usize) -> LoadError { let msg = "failed to parse CSV"; let pos = (err.kind().position()) .map(|pos| { @@ -178,13 +174,13 @@ fn format_csv_error( .unwrap_or(LineCol::one_based(line, 1).into()); match err.kind() { ::csv::ErrorKind::Utf8 { .. } => { - data.err_in_text(pos, msg, "file is not valid utf-8") + LoadError::new(pos, msg, "file is not valid utf-8") } ::csv::ErrorKind::UnequalLengths { expected_len, len, .. } => { let err = format!("found {len} instead of {expected_len} fields in line {line}"); - data.err_in_text(pos, msg, err) + LoadError::new(pos, msg, err) } - _ => data.err_in_text(pos, "failed to parse CSV", err), + _ => LoadError::new(pos, "failed to parse CSV", err), } } diff --git a/crates/typst-library/src/loading/json.rs b/crates/typst-library/src/loading/json.rs index a6a2f5b59..7d0732ba0 100644 --- a/crates/typst-library/src/loading/json.rs +++ b/crates/typst-library/src/loading/json.rs @@ -1,7 +1,7 @@ use ecow::eco_format; use typst_syntax::Spanned; -use crate::diag::{At, LineCol, SourceResult}; +use crate::diag::{At, LineCol, LoadError, LoadedWithin, SourceResult}; use crate::engine::Engine; use crate::foundations::{func, scope, Str, Value}; use crate::loading::{DataSource, Load, Readable}; @@ -54,11 +54,13 @@ pub fn json( /// A [path]($syntax/#paths) to a JSON file or raw JSON bytes. source: Spanned, ) -> SourceResult { - let data = source.load(engine.world)?; - serde_json::from_slice(data.bytes.as_slice()).map_err(|err| { - let pos = LineCol::one_based(err.line(), err.column()); - data.err_in_text(pos, "failed to parse JSON", err) - }) + let loaded = source.load(engine.world)?; + serde_json::from_slice(loaded.data.as_slice()) + .map_err(|err| { + let pos = LineCol::one_based(err.line(), err.column()); + LoadError::new(pos, "failed to parse JSON", err) + }) + .within(&loaded) } #[scope] diff --git a/crates/typst-library/src/loading/mod.rs b/crates/typst-library/src/loading/mod.rs index 259060a72..ec307d946 100644 --- a/crates/typst-library/src/loading/mod.rs +++ b/crates/typst-library/src/loading/mod.rs @@ -27,7 +27,7 @@ pub use self::toml_::*; pub use self::xml_::*; pub use self::yaml_::*; -use crate::diag::{At, LoadError, LoadResult, LoadedAt, SourceResult}; +use crate::diag::{At, LoadedWithin, SourceResult}; use crate::foundations::OneOrMultiple; use crate::foundations::{cast, Bytes, Scope, Str}; use crate::World; @@ -124,16 +124,16 @@ impl Load for Spanned<&OneOrMultiple> { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Loaded { pub source: Spanned, - pub bytes: Bytes, + pub data: Bytes, } impl Loaded { pub fn new(source: Spanned, bytes: Bytes) -> Self { - Self { source, bytes } + Self { source, data: bytes } } pub fn load_str(&self) -> SourceResult<&str> { - self.bytes.load_str().in_invalid_text(self) + self.data.load_str().within(self) } } @@ -144,24 +144,6 @@ pub enum LoadSource { Bytes, } -pub trait LoadStr { - fn load_str(&self) -> LoadResult<&str>; -} - -impl> LoadStr for T { - fn load_str(&self) -> LoadResult<&str> { - std::str::from_utf8(self.as_ref()).map_err(|err| { - let start = err.valid_up_to(); - let end = start + err.error_len().unwrap_or(0); - LoadError::new( - start..end, - "failed to convert to string", - "file is not valid utf-8", - ) - }) - } -} - /// A value that can be read from a file. #[derive(Debug, Clone, PartialEq, Hash)] pub enum Readable { diff --git a/crates/typst-library/src/loading/read.rs b/crates/typst-library/src/loading/read.rs index 94082a424..f28313775 100644 --- a/crates/typst-library/src/loading/read.rs +++ b/crates/typst-library/src/loading/read.rs @@ -35,10 +35,10 @@ pub fn read( #[default(Some(Encoding::Utf8))] encoding: Option, ) -> SourceResult { - let data = path.map(DataSource::Path).load(engine.world)?; + let loaded = path.map(DataSource::Path).load(engine.world)?; Ok(match encoding { - None => Readable::Bytes(data.bytes), - Some(Encoding::Utf8) => Readable::Str(data.load_str()?.into()), + None => Readable::Bytes(loaded.data), + Some(Encoding::Utf8) => Readable::Str(loaded.load_str()?.into()), }) } diff --git a/crates/typst-library/src/loading/toml.rs b/crates/typst-library/src/loading/toml.rs index 38ee39c41..c1264ee2d 100644 --- a/crates/typst-library/src/loading/toml.rs +++ b/crates/typst-library/src/loading/toml.rs @@ -1,7 +1,7 @@ use ecow::eco_format; use typst_syntax::Spanned; -use crate::diag::{At, LoadError, LoadedAt, ReportPos, SourceResult}; +use crate::diag::{At, LoadError, LoadedWithin, ReportPos, SourceResult}; use crate::engine::Engine; use crate::foundations::{func, scope, Str, Value}; use crate::loading::{DataSource, Load, Readable}; @@ -32,9 +32,9 @@ pub fn toml( /// A [path]($syntax/#paths) to a TOML file or raw TOML bytes. source: Spanned, ) -> SourceResult { - let data = source.load(engine.world)?; - let raw = data.load_str()?; - ::toml::from_str(raw).map_err(format_toml_error).in_text(&data) + let loaded = source.load(engine.world)?; + let raw = loaded.load_str()?; + ::toml::from_str(raw).map_err(format_toml_error).within(&loaded) } #[scope] diff --git a/crates/typst-library/src/loading/xml.rs b/crates/typst-library/src/loading/xml.rs index 623bad3ea..12b9c74a9 100644 --- a/crates/typst-library/src/loading/xml.rs +++ b/crates/typst-library/src/loading/xml.rs @@ -1,7 +1,7 @@ use roxmltree::ParsingOptions; use typst_syntax::Spanned; -use crate::diag::{format_xml_like_error, LoadError, LoadedAt, SourceResult}; +use crate::diag::{format_xml_like_error, LoadError, LoadedWithin, SourceResult}; use crate::engine::Engine; use crate::foundations::{dict, func, scope, Array, Dict, IntoValue, Str, Value}; use crate::loading::{DataSource, Load, Readable}; @@ -60,14 +60,14 @@ pub fn xml( /// A [path]($syntax/#paths) to an XML file or raw XML bytes. source: Spanned, ) -> SourceResult { - let data = source.load(engine.world)?; - let text = data.load_str()?; + let loaded = source.load(engine.world)?; + let text = loaded.load_str()?; let document = roxmltree::Document::parse_with_options( text, ParsingOptions { allow_dtd: true, ..Default::default() }, ) .map_err(format_xml_error) - .in_text(&data)?; + .within(&loaded)?; Ok(convert_xml(document.root())) } diff --git a/crates/typst-library/src/loading/yaml.rs b/crates/typst-library/src/loading/yaml.rs index 77ca9c209..c6dd54050 100644 --- a/crates/typst-library/src/loading/yaml.rs +++ b/crates/typst-library/src/loading/yaml.rs @@ -1,10 +1,10 @@ -use ecow::{eco_format, EcoVec}; +use ecow::eco_format; use typst_syntax::Spanned; -use crate::diag::{At, LineCol, ReportPos, SourceDiagnostic, SourceResult}; +use crate::diag::{At, LineCol, LoadError, LoadedWithin, ReportPos, SourceResult}; use crate::engine::Engine; use crate::foundations::{func, scope, Str, Value}; -use crate::loading::{DataSource, Load, Loaded, Readable}; +use crate::loading::{DataSource, Load, Readable}; /// Reads structured data from a YAML file. /// @@ -44,9 +44,10 @@ pub fn yaml( /// A [path]($syntax/#paths) to a YAML file or raw YAML bytes. source: Spanned, ) -> SourceResult { - let data = source.load(engine.world)?; - serde_yaml::from_slice(data.bytes.as_slice()) - .map_err(|err| format_yaml_error(&data, err)) + let loaded = source.load(engine.world)?; + serde_yaml::from_slice(loaded.data.as_slice()) + .map_err(format_yaml_error) + .within(&loaded) } #[scope] @@ -76,10 +77,7 @@ impl yaml { } } -pub fn format_yaml_error( - data: &Loaded, - error: serde_yaml::Error, -) -> EcoVec { +pub fn format_yaml_error(error: serde_yaml::Error) -> LoadError { let pos = error .location() .map(|loc| { @@ -88,5 +86,5 @@ pub fn format_yaml_error( ReportPos::full(range, line_col) }) .unwrap_or_default(); - data.err_in_text(pos, "failed to parse YAML", error) + LoadError::new(pos, "failed to parse YAML", error) } diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index f1e3c7665..912ce07e6 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -20,8 +20,8 @@ use typst_syntax::{Span, Spanned}; use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr}; use crate::diag::{ - bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedAt, ReportPos, - SourceDiagnostic, SourceResult, StrResult, + bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedWithin, ReportPos, + SourceResult, StrResult, }; use crate::engine::{Engine, Sink}; use crate::foundations::{ @@ -34,7 +34,7 @@ use crate::layout::{ BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem, Sides, Sizing, TrackSizings, }; -use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, LoadStr, Loaded}; +use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded}; use crate::model::{ CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem, Url, @@ -297,8 +297,8 @@ impl Bibliography { world: Tracked, sources: Spanned>, ) -> SourceResult, Self>> { - let data = sources.load(world)?; - let bibliography = Self::decode(&data)?; + let loaded = sources.load(world)?; + let bibliography = Self::decode(&loaded)?; Ok(Derived::new(sources.v, bibliography)) } @@ -355,10 +355,10 @@ impl Debug for Bibliography { } /// Decode on library from one data source. -fn decode_library(data: &Loaded) -> SourceResult { - let str = data.load_str()?; +fn decode_library(loaded: &Loaded) -> SourceResult { + let data = loaded.load_str()?; - if let LoadSource::Path(file_id) = data.source.v { + if let LoadSource::Path(file_id) = loaded.source.v { // If we got a path, use the extension to determine whether it is // YAML or BibLaTeX. let ext = file_id @@ -369,25 +369,27 @@ fn decode_library(data: &Loaded) -> SourceResult { .unwrap_or_default(); match ext.to_lowercase().as_str() { - "yml" | "yaml" => hayagriva::io::from_yaml_str(str) - .map_err(|err| format_yaml_error(data, err)), - "bib" => hayagriva::io::from_biblatex_str(str) - .map_err(|errors| format_biblatex_error(data, errors)), + "yml" | "yaml" => hayagriva::io::from_yaml_str(data) + .map_err(format_yaml_error) + .within(loaded), + "bib" => hayagriva::io::from_biblatex_str(data) + .map_err(format_biblatex_error) + .within(loaded), _ => bail!( - data.source.span, + loaded.source.span, "unknown bibliography format (must be .yml/.yaml or .bib)" ), } } else { // If we just got bytes, we need to guess. If it can be decoded as // hayagriva YAML, we'll use that. - let haya_err = match hayagriva::io::from_yaml_str(str) { + let haya_err = match hayagriva::io::from_yaml_str(data) { Ok(library) => return Ok(library), Err(err) => err, }; // If it can be decoded as BibLaTeX, we use that isntead. - let bib_errs = match hayagriva::io::from_biblatex_str(str) { + let bib_errs = match hayagriva::io::from_biblatex_str(data) { // If the file is almost valid yaml, but contains no `@` character // it will be successfully parsed as an empty BibLaTeX library, // since BibLaTeX does support arbitrary text outside of entries. @@ -401,7 +403,7 @@ fn decode_library(data: &Loaded) -> SourceResult { // and emit the more appropriate error. let mut yaml = 0; let mut biblatex = 0; - for c in str.chars() { + for c in data.chars() { match c { ':' => yaml += 1, '{' => biblatex += 1, @@ -411,22 +413,19 @@ fn decode_library(data: &Loaded) -> SourceResult { match bib_errs { Some(bib_errs) if biblatex >= yaml => { - Err(format_biblatex_error(data, bib_errs)) + Err(format_biblatex_error(bib_errs)).within(&loaded) } - _ => Err(format_yaml_error(data, haya_err)), + _ => Err(format_yaml_error(haya_err)).within(&loaded), } } } /// Format a BibLaTeX loading error. -fn format_biblatex_error( - data: &Loaded, - errors: Vec, -) -> EcoVec { +fn format_biblatex_error(errors: Vec) -> LoadError { // TODO: return multiple errors? let Some(error) = errors.into_iter().next() else { // TODO: can this even happen, should we just unwrap? - return data.err_in_text(ReportPos::None, "failed to parse BibLaTeX", "???"); + return LoadError::new(ReportPos::None, "failed to parse BibLaTeX", "???"); }; let (range, msg) = match error { @@ -434,7 +433,7 @@ fn format_biblatex_error( BibLaTeXError::Type(error) => (error.span, error.kind.to_string()), }; - data.err_in_text(range, "failed to parse BibLaTeX", msg) + LoadError::new(range, "failed to parse BibLaTeX", msg) } /// A loaded CSL style. @@ -450,8 +449,8 @@ impl CslStyle { let style = match &source { CslSource::Named(style) => Self::from_archived(*style), CslSource::Normal(source) => { - let data = Spanned::new(source, span).load(world)?; - Self::from_data(&data.bytes).in_text(&data)? + let loaded = Spanned::new(source, span).load(world)?; + Self::from_data(&loaded.data).within(&loaded)? } }; Ok(Derived::new(source, style)) diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index 8a5d91cf8..c954f152c 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -11,7 +11,7 @@ use typst_utils::ManuallyHash; use unicode_segmentation::UnicodeSegmentation; use super::Lang; -use crate::diag::{LineCol, LoadError, LoadResult, LoadedAt, ReportPos, SourceResult}; +use crate::diag::{LineCol, LoadError, LoadResult, LoadedWithin, ReportPos, SourceResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed, @@ -19,7 +19,7 @@ use crate::foundations::{ }; use crate::html::{tag, HtmlElem}; use crate::layout::{BlockBody, BlockElem, Em, HAlignment}; -use crate::loading::{DataSource, Load, LoadStr}; +use crate::loading::{DataSource, Load}; use crate::model::{Figurable, ParElem}; use crate::text::{FontFamily, FontList, LinebreakElem, LocalName, TextElem, TextSize}; use crate::visualize::Color; @@ -539,10 +539,10 @@ impl RawSyntax { world: Tracked, sources: Spanned>, ) -> SourceResult, Vec>> { - let data = sources.load(world)?; - let list = data + let loaded = sources.load(world)?; + let list = loaded .iter() - .map(|data| Self::decode(&data.bytes).in_text(data)) + .map(|data| Self::decode(&data.data).within(data)) .collect::>()?; Ok(Derived::new(sources.v, list)) } @@ -599,8 +599,8 @@ impl RawTheme { world: Tracked, source: Spanned, ) -> SourceResult> { - let data = source.load(world)?; - let theme = Self::decode(&data.bytes).in_text(&data)?; + let loaded = source.load(world)?; + let theme = Self::decode(&loaded.data).within(&loaded)?; Ok(Derived::new(source.v, theme)) } diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 47505c10a..77be4c8c8 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -65,8 +65,8 @@ pub struct ImageElem { #[required] #[parse( let source = args.expect::>("source")?; - let data = source.load(engine.world)?; - Derived::new(source.v, data) + let loaded = source.load(engine.world)?; + Derived::new(source.v, loaded) )] pub source: Derived, @@ -154,8 +154,8 @@ pub struct ImageElem { /// to `{auto}`, Typst will try to extract an ICC profile from the image. #[parse(match args.named::>>("icc")? { Some(Spanned { v: Smart::Custom(source), span }) => Some(Smart::Custom({ - let data = Spanned::new(&source, span).load(engine.world)?; - Derived::new(source, data.bytes) + let loaded = Spanned::new(&source, span).load(engine.world)?; + Derived::new(source, loaded.data) })), Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto), None => None, @@ -194,8 +194,8 @@ impl ImageElem { scaling: Option>, ) -> StrResult { let bytes = data.v.into_bytes(); - let data = Loaded::new(Spanned::new(LoadSource::Bytes, data.span), bytes.clone()); - let source = Derived::new(DataSource::Bytes(bytes), data); + let loaded = Loaded::new(Spanned::new(LoadSource::Bytes, data.span), bytes.clone()); + let source = Derived::new(DataSource::Bytes(bytes), loaded); let mut elem = ImageElem::new(source); if let Some(format) = format { elem.push_format(format);