1use std::fmt::{self, Display, Formatter};
4use std::io;
5use std::path::{Path, PathBuf};
6use std::str::Utf8Error;
7use std::string::FromUtf8Error;
8
9use comemo::Tracked;
10use ecow::{eco_vec, EcoVec};
11use typst_syntax::package::{PackageSpec, PackageVersion};
12use typst_syntax::{Span, Spanned, SyntaxError};
13
14use crate::engine::Engine;
15use crate::{World, WorldExt};
16
17#[macro_export]
39#[doc(hidden)]
40macro_rules! __bail {
41 (
43 $fmt:literal $(, $arg:expr)*
44 $(; hint: $hint:literal $(, $hint_arg:expr)*)*
45 $(,)?
46 ) => {
47 return Err($crate::diag::error!(
48 $fmt $(, $arg)*
49 $(; hint: $hint $(, $hint_arg)*)*
50 ))
51 };
52
53 ($error:expr) => {
55 return Err(::ecow::eco_vec![$error])
56 };
57
58 ($($tts:tt)*) => {
60 return Err(::ecow::eco_vec![$crate::diag::error!($($tts)*)])
61 };
62}
63
64#[macro_export]
67#[doc(hidden)]
68macro_rules! __error {
69 ($fmt:literal $(, $arg:expr)* $(,)?) => {
71 $crate::diag::eco_format!($fmt, $($arg),*).into()
72 };
73
74 (
76 $fmt:literal $(, $arg:expr)*
77 $(; hint: $hint:literal $(, $hint_arg:expr)*)*
78 $(,)?
79 ) => {
80 $crate::diag::HintedString::new(
81 $crate::diag::eco_format!($fmt, $($arg),*)
82 ) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))*
83 };
84
85 (
87 $span:expr, $fmt:literal $(, $arg:expr)*
88 $(; hint: $hint:literal $(, $hint_arg:expr)*)*
89 $(,)?
90 ) => {
91 $crate::diag::SourceDiagnostic::error(
92 $span,
93 $crate::diag::eco_format!($fmt, $($arg),*),
94 ) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))*
95 };
96}
97
98#[macro_export]
115#[doc(hidden)]
116macro_rules! __warning {
117 (
118 $span:expr,
119 $fmt:literal $(, $arg:expr)*
120 $(; hint: $hint:literal $(, $hint_arg:expr)*)*
121 $(,)?
122 ) => {
123 $crate::diag::SourceDiagnostic::warning(
124 $span,
125 $crate::diag::eco_format!($fmt, $($arg),*),
126 ) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))*
127 };
128}
129
130#[rustfmt::skip]
131#[doc(inline)]
132pub use {
133 crate::__bail as bail,
134 crate::__error as error,
135 crate::__warning as warning,
136 ecow::{eco_format, EcoString},
137};
138
139pub type SourceResult<T> = Result<T, EcoVec<SourceDiagnostic>>;
141
142#[derive(Debug, Clone, Eq, PartialEq, Hash)]
144pub struct Warned<T> {
145 pub output: T,
147 pub warnings: EcoVec<SourceDiagnostic>,
149}
150
151#[derive(Debug, Clone, Eq, PartialEq, Hash)]
156pub struct SourceDiagnostic {
157 pub severity: Severity,
159 pub span: Span,
161 pub message: EcoString,
163 pub trace: EcoVec<Spanned<Tracepoint>>,
165 pub hints: EcoVec<EcoString>,
168}
169
170#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
172pub enum Severity {
173 Error,
175 Warning,
177}
178
179impl SourceDiagnostic {
180 pub fn error(span: Span, message: impl Into<EcoString>) -> Self {
182 Self {
183 severity: Severity::Error,
184 span,
185 trace: eco_vec![],
186 message: message.into(),
187 hints: eco_vec![],
188 }
189 }
190
191 pub fn warning(span: Span, message: impl Into<EcoString>) -> Self {
193 Self {
194 severity: Severity::Warning,
195 span,
196 trace: eco_vec![],
197 message: message.into(),
198 hints: eco_vec![],
199 }
200 }
201
202 pub fn hint(&mut self, hint: impl Into<EcoString>) {
204 self.hints.push(hint.into());
205 }
206
207 pub fn with_hint(mut self, hint: impl Into<EcoString>) -> Self {
209 self.hint(hint);
210 self
211 }
212
213 pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
215 self.hints.extend(hints);
216 self
217 }
218}
219
220impl From<SyntaxError> for SourceDiagnostic {
221 fn from(error: SyntaxError) -> Self {
222 Self {
223 severity: Severity::Error,
224 span: error.span,
225 message: error.message,
226 trace: eco_vec![],
227 hints: error.hints,
228 }
229 }
230}
231
232pub trait DeprecationSink {
234 fn emit(&mut self, message: &str);
236
237 fn emit_with_hints(&mut self, message: &str, hints: &[&str]);
240}
241
242impl DeprecationSink for () {
243 fn emit(&mut self, _: &str) {}
244 fn emit_with_hints(&mut self, _: &str, _: &[&str]) {}
245}
246
247impl DeprecationSink for (&mut Vec<SourceDiagnostic>, Span) {
248 fn emit(&mut self, message: &str) {
249 self.0.push(SourceDiagnostic::warning(self.1, message));
250 }
251
252 fn emit_with_hints(&mut self, message: &str, hints: &[&str]) {
253 self.0.push(
254 SourceDiagnostic::warning(self.1, message)
255 .with_hints(hints.iter().copied().map(Into::into)),
256 );
257 }
258}
259
260impl DeprecationSink for (&mut Engine<'_>, Span) {
261 fn emit(&mut self, message: &str) {
262 self.0.sink.warn(SourceDiagnostic::warning(self.1, message));
263 }
264
265 fn emit_with_hints(&mut self, message: &str, hints: &[&str]) {
266 self.0.sink.warn(
267 SourceDiagnostic::warning(self.1, message)
268 .with_hints(hints.iter().copied().map(Into::into)),
269 );
270 }
271}
272
273#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
275pub enum Tracepoint {
276 Call(Option<EcoString>),
278 Show(EcoString),
280 Import,
282}
283
284impl Display for Tracepoint {
285 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
286 match self {
287 Tracepoint::Call(Some(name)) => {
288 write!(f, "error occurred in this call of function `{name}`")
289 }
290 Tracepoint::Call(None) => {
291 write!(f, "error occurred in this function call")
292 }
293 Tracepoint::Show(name) => {
294 write!(f, "error occurred while applying show rule to this {name}")
295 }
296 Tracepoint::Import => {
297 write!(f, "error occurred while importing this module")
298 }
299 }
300 }
301}
302
303pub trait Trace<T> {
305 fn trace<F>(self, world: Tracked<dyn World + '_>, make_point: F, span: Span) -> Self
307 where
308 F: Fn() -> Tracepoint;
309}
310
311impl<T> Trace<T> for SourceResult<T> {
312 fn trace<F>(self, world: Tracked<dyn World + '_>, make_point: F, span: Span) -> Self
313 where
314 F: Fn() -> Tracepoint,
315 {
316 self.map_err(|mut errors| {
317 let Some(trace_range) = world.range(span) else { return errors };
318 for error in errors.make_mut().iter_mut() {
319 if let Some(error_range) = world.range(error.span) {
321 if error.span.id() == span.id()
322 && trace_range.start <= error_range.start
323 && trace_range.end >= error_range.end
324 {
325 continue;
326 }
327 }
328
329 error.trace.push(Spanned::new(make_point(), span));
330 }
331 errors
332 })
333 }
334}
335
336pub type StrResult<T> = Result<T, EcoString>;
338
339pub trait At<T> {
342 fn at(self, span: Span) -> SourceResult<T>;
344}
345
346impl<T, S> At<T> for Result<T, S>
347where
348 S: Into<EcoString>,
349{
350 fn at(self, span: Span) -> SourceResult<T> {
351 self.map_err(|message| {
352 let mut diagnostic = SourceDiagnostic::error(span, message);
353 if diagnostic.message.contains("(access denied)") {
354 diagnostic.hint("cannot read file outside of project root");
355 diagnostic
356 .hint("you can adjust the project root with the --root argument");
357 }
358 eco_vec![diagnostic]
359 })
360 }
361}
362
363pub type HintedStrResult<T> = Result<T, HintedString>;
365
366#[derive(Debug, Clone, Eq, PartialEq, Hash)]
374pub struct HintedString(EcoVec<EcoString>);
375
376impl HintedString {
377 pub fn new(message: EcoString) -> Self {
379 Self(eco_vec![message])
380 }
381
382 pub fn message(&self) -> &EcoString {
384 self.0.first().unwrap()
385 }
386
387 pub fn hints(&self) -> &[EcoString] {
390 self.0.get(1..).unwrap_or(&[])
391 }
392
393 pub fn hint(&mut self, hint: impl Into<EcoString>) {
395 self.0.push(hint.into());
396 }
397
398 pub fn with_hint(mut self, hint: impl Into<EcoString>) -> Self {
400 self.hint(hint);
401 self
402 }
403
404 pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
406 self.0.extend(hints);
407 self
408 }
409}
410
411impl<S> From<S> for HintedString
412where
413 S: Into<EcoString>,
414{
415 fn from(value: S) -> Self {
416 Self::new(value.into())
417 }
418}
419
420impl<T> At<T> for HintedStrResult<T> {
421 fn at(self, span: Span) -> SourceResult<T> {
422 self.map_err(|err| {
423 let mut components = err.0.into_iter();
424 let message = components.next().unwrap();
425 let diag = SourceDiagnostic::error(span, message).with_hints(components);
426 eco_vec![diag]
427 })
428 }
429}
430
431pub trait Hint<T> {
433 fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T>;
435}
436
437impl<T, S> Hint<T> for Result<T, S>
438where
439 S: Into<EcoString>,
440{
441 fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
442 self.map_err(|message| HintedString::new(message.into()).with_hint(hint))
443 }
444}
445
446impl<T> Hint<T> for HintedStrResult<T> {
447 fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
448 self.map_err(|mut error| {
449 error.hint(hint.into());
450 error
451 })
452 }
453}
454
455pub type FileResult<T> = Result<T, FileError>;
457
458#[derive(Debug, Clone, Eq, PartialEq, Hash)]
460pub enum FileError {
461 NotFound(PathBuf),
463 AccessDenied,
465 IsDirectory,
467 NotSource,
469 InvalidUtf8,
471 Package(PackageError),
473 Other(Option<EcoString>),
477}
478
479impl FileError {
480 pub fn from_io(err: io::Error, path: &Path) -> Self {
482 match err.kind() {
483 io::ErrorKind::NotFound => Self::NotFound(path.into()),
484 io::ErrorKind::PermissionDenied => Self::AccessDenied,
485 io::ErrorKind::InvalidData
486 if err.to_string().contains("stream did not contain valid UTF-8") =>
487 {
488 Self::InvalidUtf8
489 }
490 _ => Self::Other(Some(eco_format!("{err}"))),
491 }
492 }
493}
494
495impl std::error::Error for FileError {}
496
497impl Display for FileError {
498 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
499 match self {
500 Self::NotFound(path) => {
501 write!(f, "file not found (searched at {})", path.display())
502 }
503 Self::AccessDenied => f.pad("failed to load file (access denied)"),
504 Self::IsDirectory => f.pad("failed to load file (is a directory)"),
505 Self::NotSource => f.pad("not a typst source file"),
506 Self::InvalidUtf8 => f.pad("file is not valid utf-8"),
507 Self::Package(error) => error.fmt(f),
508 Self::Other(Some(err)) => write!(f, "failed to load file ({err})"),
509 Self::Other(None) => f.pad("failed to load file"),
510 }
511 }
512}
513
514impl From<Utf8Error> for FileError {
515 fn from(_: Utf8Error) -> Self {
516 Self::InvalidUtf8
517 }
518}
519
520impl From<FromUtf8Error> for FileError {
521 fn from(_: FromUtf8Error) -> Self {
522 Self::InvalidUtf8
523 }
524}
525
526impl From<PackageError> for FileError {
527 fn from(err: PackageError) -> Self {
528 Self::Package(err)
529 }
530}
531
532impl From<FileError> for EcoString {
533 fn from(err: FileError) -> Self {
534 eco_format!("{err}")
535 }
536}
537
538pub type PackageResult<T> = Result<T, PackageError>;
540
541#[derive(Debug, Clone, Eq, PartialEq, Hash)]
545pub enum PackageError {
546 NotFound(PackageSpec),
548 VersionNotFound(PackageSpec, PackageVersion),
550 NetworkFailed(Option<EcoString>),
552 MalformedArchive(Option<EcoString>),
554 Other(Option<EcoString>),
556}
557
558impl std::error::Error for PackageError {}
559
560impl Display for PackageError {
561 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
562 match self {
563 Self::NotFound(spec) => {
564 write!(f, "package not found (searched for {spec})",)
565 }
566 Self::VersionNotFound(spec, latest) => {
567 write!(
568 f,
569 "package found, but version {} does not exist (latest is {})",
570 spec.version, latest,
571 )
572 }
573 Self::NetworkFailed(Some(err)) => {
574 write!(f, "failed to download package ({err})")
575 }
576 Self::NetworkFailed(None) => f.pad("failed to download package"),
577 Self::MalformedArchive(Some(err)) => {
578 write!(f, "failed to decompress package ({err})")
579 }
580 Self::MalformedArchive(None) => {
581 f.pad("failed to decompress package (archive malformed)")
582 }
583 Self::Other(Some(err)) => write!(f, "failed to load package ({err})"),
584 Self::Other(None) => f.pad("failed to load package"),
585 }
586 }
587}
588
589impl From<PackageError> for EcoString {
590 fn from(err: PackageError) -> Self {
591 eco_format!("{err}")
592 }
593}
594
595pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> EcoString {
597 match error {
598 roxmltree::Error::UnexpectedCloseTag(expected, actual, pos) => {
599 eco_format!(
600 "failed to parse {format} (found closing tag '{actual}' \
601 instead of '{expected}' in line {})",
602 pos.row
603 )
604 }
605 roxmltree::Error::UnknownEntityReference(entity, pos) => {
606 eco_format!(
607 "failed to parse {format} (unknown entity '{entity}' in line {})",
608 pos.row
609 )
610 }
611 roxmltree::Error::DuplicatedAttribute(attr, pos) => {
612 eco_format!(
613 "failed to parse {format} (duplicate attribute '{attr}' in line {})",
614 pos.row
615 )
616 }
617 roxmltree::Error::NoRootNode => {
618 eco_format!("failed to parse {format} (missing root node)")
619 }
620 err => eco_format!("failed to parse {format} ({err})"),
621 }
622}