1#[doc(hidden)]
8pub use ecow::{EcoString, EcoVec, eco_format, eco_vec};
9
10use std::backtrace::{Backtrace, BacktraceStatus};
11use std::fmt::{self, Display, Formatter, Write as _};
12use std::io;
13use std::path::{Path, PathBuf};
14use std::str::Utf8Error;
15use std::string::FromUtf8Error;
16
17use az::SaturatingAs;
18use comemo::Tracked;
19use typst_syntax::package::{PackageSpec, PackageVersion};
20use typst_syntax::{
21 DiagSpan, Lines, RealizeError, Span, Spanned, SyntaxDiagnostic, VirtualRoot,
22};
23use utf8_iter::ErrorReportingUtf8Chars;
24
25use crate::engine::Engine;
26use crate::loading::{LoadSource, Loaded};
27use crate::{World, WorldExt};
28
29#[macro_export]
50#[doc(hidden)]
51#[clippy::format_args]
52macro_rules! __bail {
54 (
57 $fmt:literal $(, $arg:expr)* $(,)?
58 $(; hint: $hint:literal $(, $hint_arg:expr)*)*
59 $(;)?
60 ) => {
61 return Err($crate::diag::error!(
62 $fmt $(, $arg)*
63 $(; hint: $hint $(, $hint_arg)*)*
64 ))
65 };
66
67 ($error:expr) => {
69 return Err($crate::diag::eco_vec![$error])
70 };
71
72 ($($tts:tt)*) => {
74 return Err($crate::diag::eco_vec![$crate::diag::error!($($tts)*)])
75 };
76}
77
78#[macro_export]
97#[doc(hidden)]
98#[clippy::format_args]
99macro_rules! __error {
101 ($fmt:literal $(, $arg:expr)* $(,)?) => {
103 $crate::diag::eco_format!($fmt $(, $arg)*).into()
104 };
105
106 (
108 $fmt:literal $(, $arg:expr)* $(,)?
109 $(; hint: $hint:literal $(, $hint_arg:expr)*)*
110 $(;)?
111 ) => {
112 $crate::diag::HintedString::new(
113 $crate::diag::eco_format!($fmt $(, $arg)*)
114 ) $(.with_hint($crate::diag::eco_format!($hint $(, $hint_arg)*)))*
115 };
116
117 (
120 $span:expr, $fmt:literal $(, $arg:expr)* $(,)?
121 $(; hint $([$hint_span:expr])? : $hint:literal $(, $hint_arg:expr)*)*
122 $(;)?
123 ) => {{
124 #[allow(unused_mut)]
125 let mut err = $crate::diag::SourceDiagnostic::error(
126 $span,
127 $crate::diag::eco_format!($fmt $(, $arg)*)
128 );
129 $($crate::diag::error!(hint$([$hint_span])?: err, $hint $(, $hint_arg)*);)*
131 err
132 }};
133
134 (hint: $err:ident, $hint:literal $(, $hint_arg:expr)*) => {
138 $err.hint($crate::diag::eco_format!($hint $(, $hint_arg)*))
139 };
140 (hint[$hint_span:expr]: $err:ident, $hint:literal $(, $hint_arg:expr)*) => {
141 $err.spanned_hint($crate::diag::eco_format!($hint $(, $hint_arg)*), $hint_span)
142 };
143}
144
145#[macro_export]
164#[doc(hidden)]
165#[clippy::format_args]
166macro_rules! __warning {
168 (
169 $span:expr, $fmt:literal $(, $arg:expr)* $(,)?
170 $(; hint $([$hint_span:expr])? : $hint:literal $(, $hint_arg:expr)*)*
171 $(;)?
172 ) => {{
173 #[allow(unused_mut)]
174 let mut warning = $crate::diag::SourceDiagnostic::warning(
175 $span,
176 $crate::diag::eco_format!($fmt $(, $arg)*)
177 );
178 $($crate::diag::error!(hint$([$hint_span])?: warning, $hint $(, $hint_arg)*);)*
180 warning
181 }};
182}
183
184#[rustfmt::skip]
201#[doc(inline)]
202pub use {
203 __bail as bail,
204 __error as error,
205 __warning as warning,
206};
207
208pub type SourceResult<T> = Result<T, EcoVec<SourceDiagnostic>>;
211
212pub trait CollectCombinedResult {
218 type Item;
219
220 fn collect_combined_result<B>(self) -> SourceResult<B>
221 where
222 B: FromIterator<Self::Item>;
223}
224
225impl<I, T> CollectCombinedResult for I
226where
227 I: Iterator<Item = SourceResult<T>>,
228{
229 type Item = T;
230
231 fn collect_combined_result<B>(self) -> SourceResult<B>
232 where
233 B: FromIterator<Self::Item>,
234 {
235 let mut errors = EcoVec::new();
236 let collected = self
237 .filter_map(|result| match result {
238 Ok(item) => Some(item),
239 Err(errs) => {
240 errors.extend(errs);
241 None
242 }
243 })
244 .collect();
245 if !errors.is_empty() {
246 return Err(errors);
247 }
248 Ok(collected)
249 }
250}
251
252pub trait ParallelCollectCombinedResult {
256 type Item;
257
258 fn collect_combined_result<B>(self) -> SourceResult<B>
259 where
260 B: FromIterator<Self::Item>;
261}
262
263impl<I, T> ParallelCollectCombinedResult for I
264where
265 I: rayon::iter::ParallelIterator<Item = SourceResult<T>>,
266 T: Send,
267{
268 type Item = T;
269
270 fn collect_combined_result<B>(self) -> SourceResult<B>
271 where
272 B: FromIterator<Self::Item>,
273 {
274 self.collect::<Vec<_>>().into_iter().collect_combined_result()
278 }
279}
280
281#[derive(Debug, Clone, Eq, PartialEq, Hash)]
283pub struct Warned<T> {
284 pub output: T,
286 pub warnings: EcoVec<SourceDiagnostic>,
288}
289
290impl<T> Warned<T> {
291 pub fn map<R, F: FnOnce(T) -> R>(self, f: F) -> Warned<R> {
293 Warned { output: f(self.output), warnings: self.warnings }
294 }
295}
296
297#[derive(Debug, Clone, Eq, PartialEq, Hash)]
303pub struct SourceDiagnostic {
304 pub severity: Severity,
306 pub span: DiagSpan,
309 pub message: EcoString,
311 pub trace: EcoVec<Spanned<Tracepoint>>,
313 pub hints: EcoVec<Spanned<EcoString, DiagSpan>>,
321}
322
323#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
325pub enum Severity {
326 Error,
328 Warning,
330}
331
332impl SourceDiagnostic {
333 pub fn error(span: impl Into<DiagSpan>, message: impl Into<EcoString>) -> Self {
335 Self {
336 severity: Severity::Error,
337 span: span.into(),
338 trace: eco_vec![],
339 message: message.into(),
340 hints: eco_vec![],
341 }
342 }
343
344 pub fn warning(span: impl Into<DiagSpan>, message: impl Into<EcoString>) -> Self {
346 Self {
347 severity: Severity::Warning,
348 span: span.into(),
349 trace: eco_vec![],
350 message: message.into(),
351 hints: eco_vec![],
352 }
353 }
354
355 pub fn hint(&mut self, hint: impl Into<EcoString>) {
357 self.hints.push(Spanned::detached(hint.into()));
358 }
359
360 pub fn spanned_hint(
362 &mut self,
363 hint: impl Into<EcoString>,
364 span: impl Into<DiagSpan>,
365 ) {
366 self.hints.push(Spanned::new(hint.into(), span.into()));
367 }
368
369 pub fn with_hint(mut self, hint: impl Into<EcoString>) -> Self {
371 self.hint(hint);
372 self
373 }
374
375 pub fn with_spanned_hint(
377 mut self,
378 hint: impl Into<EcoString>,
379 span: impl Into<DiagSpan>,
380 ) -> Self {
381 self.spanned_hint(hint, span);
382 self
383 }
384
385 pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
387 self.hints.extend(hints.into_iter().map(Spanned::detached));
388 self
389 }
390
391 pub fn with_tracepoint(mut self, tracepoint: Tracepoint, span: Span) -> Self {
393 self.trace.push(Spanned::new(tracepoint, span));
394 self
395 }
396}
397
398impl From<SyntaxDiagnostic> for SourceDiagnostic {
399 fn from(syntax_diag: SyntaxDiagnostic) -> Self {
400 let SyntaxDiagnostic { is_error, span, message, hints } = syntax_diag;
401 Self {
402 severity: if is_error { Severity::Error } else { Severity::Warning },
403 span,
404 message,
405 trace: eco_vec![],
406 hints,
407 }
408 }
409}
410
411pub trait WarningSink {
413 fn emit(&mut self, message: HintedString);
415}
416
417impl WarningSink for () {
418 fn emit(&mut self, _: HintedString) {}
419}
420
421impl WarningSink for (&mut Engine<'_>, Span) {
422 fn emit(&mut self, hinted: HintedString) {
423 self.0.sink.warn(
424 SourceDiagnostic::warning(self.1, hinted.message())
425 .with_hints(hinted.hints().iter().cloned()),
426 );
427 }
428}
429
430#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
432pub enum Tracepoint {
433 Call(Option<EcoString>),
435 Show(EcoString),
437 Import(EcoString),
439 Include(EcoString),
441}
442
443impl Display for Tracepoint {
444 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
445 match self {
446 Tracepoint::Call(Some(name)) => write!(f, "while calling `{name}`"),
447 Tracepoint::Call(None) => write!(f, "while calling function"),
448 Tracepoint::Show(name) => write!(f, "while showing {name} element"),
449 Tracepoint::Import(name) => write!(f, "while importing `{name}`"),
450 Tracepoint::Include(name) => write!(f, "while including `{name}`"),
451 }
452 }
453}
454
455pub trait Trace<T> {
457 fn trace<F>(self, world: Tracked<dyn World + '_>, make_point: F, span: Span) -> Self
459 where
460 F: Fn() -> Tracepoint;
461}
462
463impl<T> Trace<T> for SourceResult<T> {
464 fn trace<F>(self, world: Tracked<dyn World + '_>, make_point: F, span: Span) -> Self
465 where
466 F: Fn() -> Tracepoint,
467 {
468 self.map_err(|mut errors| {
469 let Some(trace_range) = world.range(span) else { return errors };
470 for error in errors.make_mut().iter_mut() {
471 if let Some(error_range) = world.range(error.span)
473 && error.span.id() == span.id()
474 && trace_range.start <= error_range.start
475 && trace_range.end >= error_range.end
476 {
477 continue;
478 }
479
480 error.trace.push(Spanned::new(make_point(), span));
481 }
482 errors
483 })
484 }
485}
486
487pub type StrResult<T> = Result<T, EcoString>;
490
491pub trait At<T> {
494 fn at(self, span: Span) -> SourceResult<T>;
496}
497
498impl<T, S> At<T> for Result<T, S>
499where
500 S: Into<EcoString>,
501{
502 fn at(self, span: Span) -> SourceResult<T> {
503 self.map_err(|message| eco_vec![SourceDiagnostic::error(span, message)])
504 }
505}
506
507pub type HintedStrResult<T> = Result<T, HintedString>;
510
511#[derive(Debug, Clone, Eq, PartialEq, Hash)]
520pub struct HintedString(EcoVec<EcoString>);
521
522impl HintedString {
523 pub fn new(message: EcoString) -> Self {
525 Self(eco_vec![message])
526 }
527
528 pub fn message(&self) -> &EcoString {
530 self.0.first().unwrap()
531 }
532
533 pub fn hints(&self) -> &[EcoString] {
536 self.0.get(1..).unwrap_or(&[])
537 }
538
539 pub fn hint(&mut self, hint: impl Into<EcoString>) {
541 self.0.push(hint.into());
542 }
543
544 pub fn with_hint(mut self, hint: impl Into<EcoString>) -> Self {
546 self.hint(hint);
547 self
548 }
549
550 pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
552 self.0.extend(hints);
553 self
554 }
555}
556
557impl<S> From<S> for HintedString
558where
559 S: Into<EcoString>,
560{
561 fn from(value: S) -> Self {
562 Self::new(value.into())
563 }
564}
565
566impl<T> At<T> for HintedStrResult<T> {
567 fn at(self, span: Span) -> SourceResult<T> {
568 self.map_err(|err| {
569 let mut components = err.0.into_iter();
570 let message = components.next().unwrap();
571 let diag = SourceDiagnostic::error(span, message).with_hints(components);
572 eco_vec![diag]
573 })
574 }
575}
576
577pub trait Hint<T> {
579 fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T>;
581}
582
583impl<T, S> Hint<T> for Result<T, S>
584where
585 S: Into<EcoString>,
586{
587 fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
588 self.map_err(|message| HintedString::new(message.into()).with_hint(hint))
589 }
590}
591
592impl<T> Hint<T> for HintedStrResult<T> {
593 fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
594 self.map_err(|mut error| {
595 error.hint(hint.into());
596 error
597 })
598 }
599}
600
601pub type FileResult<T> = Result<T, FileError>;
603
604#[derive(Debug, Clone, Eq, PartialEq, Hash)]
606pub enum FileError {
607 NotFound(PathBuf),
609 AccessDenied,
611 IsDirectory,
613 NotSource,
615 InvalidUtf8,
617 Realize(RealizeError),
620 Package(PackageError),
622 Other(Option<EcoString>),
626}
627
628impl FileError {
629 pub fn from_io(err: io::Error, path: &Path) -> Self {
631 match err.kind() {
632 io::ErrorKind::NotFound => Self::NotFound(path.into()),
633 io::ErrorKind::PermissionDenied => Self::AccessDenied,
634 io::ErrorKind::InvalidData
635 if err.to_string().contains("stream did not contain valid UTF-8") =>
636 {
637 Self::InvalidUtf8
638 }
639 _ => Self::Other(Some(eco_format!("{err}"))),
640 }
641 }
642}
643
644impl std::error::Error for FileError {}
645
646impl Display for FileError {
647 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
648 match self {
649 Self::NotFound(path) => {
650 write!(f, "file not found (searched at {})", path.display())
651 }
652 Self::AccessDenied => f.pad("failed to load file (access denied)"),
653 Self::IsDirectory => f.pad("failed to load file (is a directory)"),
654 Self::NotSource => f.pad("not a Typst source file"),
655 Self::InvalidUtf8 => f.pad("file is not valid UTF-8"),
656 Self::Realize(err) => write!(f, "failed to load file ({err})"),
657 Self::Package(err) => err.fmt(f),
658 Self::Other(Some(err)) => write!(f, "failed to load file ({err})"),
659 Self::Other(None) => f.pad("failed to load file"),
660 }
661 }
662}
663
664impl From<Utf8Error> for FileError {
665 fn from(_: Utf8Error) -> Self {
666 Self::InvalidUtf8
667 }
668}
669
670impl From<FromUtf8Error> for FileError {
671 fn from(_: FromUtf8Error) -> Self {
672 Self::InvalidUtf8
673 }
674}
675
676impl From<RealizeError> for FileError {
677 fn from(err: RealizeError) -> Self {
678 Self::Realize(err)
679 }
680}
681
682impl From<PackageError> for FileError {
683 fn from(err: PackageError) -> Self {
684 Self::Package(err)
685 }
686}
687
688impl From<FileError> for EcoString {
689 fn from(err: FileError) -> Self {
690 eco_format!("{err}")
691 }
692}
693
694pub type PackageResult<T> = Result<T, PackageError>;
696
697#[derive(Debug, Clone, Eq, PartialEq, Hash)]
701pub enum PackageError {
702 NotFound(PackageSpec),
704 VersionNotFound(PackageSpec, PackageVersion),
706 NetworkFailed(Option<EcoString>),
708 MalformedArchive(Option<EcoString>),
710 Other(Option<EcoString>),
712}
713
714impl std::error::Error for PackageError {}
715
716impl Display for PackageError {
717 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
718 match self {
719 Self::NotFound(spec) => {
720 write!(f, "package not found (searched for {spec})",)
721 }
722 Self::VersionNotFound(spec, latest) => {
723 write!(
724 f,
725 "package found, but version {} does not exist (latest is {})",
726 spec.version, latest,
727 )
728 }
729 Self::NetworkFailed(Some(err)) => {
730 write!(f, "failed to download package ({err})")
731 }
732 Self::NetworkFailed(None) => f.pad("failed to download package"),
733 Self::MalformedArchive(Some(err)) => {
734 write!(f, "failed to decompress package ({err})")
735 }
736 Self::MalformedArchive(None) => {
737 f.pad("failed to decompress package (archive malformed)")
738 }
739 Self::Other(Some(err)) => write!(f, "failed to load package ({err})"),
740 Self::Other(None) => f.pad("failed to load package"),
741 }
742 }
743}
744
745impl From<PackageError> for EcoString {
746 fn from(err: PackageError) -> Self {
747 eco_format!("{err}")
748 }
749}
750
751pub type LoadResult<T> = Result<T, LoadError>;
753
754#[derive(Debug, Clone, Eq, PartialEq, Hash)]
761pub struct LoadError {
762 text_pos: Option<ReportTextPos>,
765 message: EcoString,
767}
768
769impl LoadError {
770 pub fn text(
773 pos: impl Into<ReportTextPos>,
774 message: impl std::fmt::Display,
775 error: impl std::fmt::Display,
776 ) -> Self {
777 Self {
778 text_pos: Some(pos.into()),
779 message: eco_format!("{message} ({error})"),
780 }
781 }
782
783 pub fn binary(
787 message: impl std::fmt::Display,
788 error: impl std::fmt::Display,
789 ) -> Self {
790 Self {
791 text_pos: None,
792 message: eco_format!("{message} ({error})"),
793 }
794 }
795}
796
797impl From<Utf8Error> for LoadError {
798 fn from(err: Utf8Error) -> Self {
799 let start = err.valid_up_to();
800 let end = start + err.error_len().unwrap_or(0);
801 LoadError::text(
802 start..end,
803 "failed to convert to string",
804 "file is not valid UTF-8",
805 )
806 }
807}
808
809pub trait LoadedWithin {
812 type Output;
814
815 fn within(self, loaded: &Loaded) -> Self::Output;
817}
818
819impl<E> LoadedWithin for E
820where
821 E: Into<LoadError>,
822{
823 type Output = SourceDiagnostic;
824
825 fn within(self, loaded: &Loaded) -> Self::Output {
826 let LoadError { text_pos: pos, message } = self.into();
827 if let Some(pos) = pos {
828 load_err_in_text(loaded, pos, message)
829 } else {
830 load_err_in_binary(loaded, None, message)
831 }
832 }
833}
834
835impl<T, E> LoadedWithin for Result<T, E>
836where
837 E: Into<LoadError>,
838{
839 type Output = SourceResult<T>;
840
841 fn within(self, loaded: &Loaded) -> Self::Output {
842 self.map_err(|err| eco_vec![err.within(loaded)])
843 }
844}
845
846fn load_err_in_text(
849 loaded: &Loaded,
850 pos: ReportTextPos,
851 mut message: EcoString,
852) -> SourceDiagnostic {
853 let Ok(lines) = loaded.data.lines() else {
857 return load_err_in_binary(loaded, Some(pos), message);
858 };
859 match loaded.source.v {
860 LoadSource::Path(file_id) => {
861 if let Some(range) = pos.range(&lines) {
862 let span = DiagSpan::from_range(file_id, range);
863 return SourceDiagnostic::error(span, message);
864 }
865
866 let span = DiagSpan::from_range(file_id, 0..loaded.data.len());
870 if let Some(pair) = pos.line_col(&lines) {
871 message.pop();
872 let (line, col) = pair.numbers();
873 write!(&mut message, " at {line}:{col})").ok();
874 }
875 SourceDiagnostic::error(span, message)
876 }
877 LoadSource::Bytes => {
878 if let Some(pair) = pos.line_col(&lines) {
879 message.pop();
880 let (line, col) = pair.numbers();
881 write!(&mut message, " at {line}:{col})").ok();
882 }
883 SourceDiagnostic::error(loaded.source.span, message)
884 }
885 }
886}
887
888fn load_err_in_binary(
890 loaded: &Loaded,
891 pos: Option<ReportTextPos>,
892 mut message: EcoString,
893) -> SourceDiagnostic {
894 let line_col = pos
895 .and_then(|pos| pos.try_line_col(&loaded.data))
896 .map(|p| p.numbers());
897 match loaded.source.v {
898 LoadSource::Path(file) => {
899 message.pop();
900 match file.root() {
901 VirtualRoot::Project => {
902 write!(&mut message, " in {}", file.vpath().get_without_slash()).ok();
903 }
904 VirtualRoot::Package(package) => {
905 write!(
906 &mut message,
907 " in {package}{}",
908 file.vpath().get_with_slash()
909 )
910 .ok();
911 }
912 }
913 if let Some((line, col)) = line_col {
914 write!(&mut message, ":{line}:{col}").ok();
915 }
916 message.push(')');
917 }
918 LoadSource::Bytes => {
919 if let Some((line, col)) = line_col {
920 message.pop();
921 write!(&mut message, " at {line}:{col})").ok();
922 }
923 }
924 }
925 SourceDiagnostic::error(loaded.source.span, message)
926}
927
928#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
930pub enum ReportTextPos {
931 Full(std::ops::Range<u32>, LineCol),
933 Range(std::ops::Range<u32>),
935 LineCol(LineCol),
937 #[default]
938 None,
939}
940
941impl From<std::ops::Range<usize>> for ReportTextPos {
942 fn from(value: std::ops::Range<usize>) -> Self {
943 Self::Range(value.start.saturating_as()..value.end.saturating_as())
944 }
945}
946
947impl From<LineCol> for ReportTextPos {
948 fn from(value: LineCol) -> Self {
949 Self::LineCol(value)
950 }
951}
952
953impl ReportTextPos {
954 pub fn full(range: std::ops::Range<usize>, pair: LineCol) -> Self {
956 let range = range.start.saturating_as()..range.end.saturating_as();
957 Self::Full(range, pair)
958 }
959
960 fn range(&self, lines: &Lines<String>) -> Option<std::ops::Range<usize>> {
962 match self {
963 ReportTextPos::Full(range, _) => {
964 Some(range.start as usize..range.end as usize)
965 }
966 ReportTextPos::Range(range) => Some(range.start as usize..range.end as usize),
967 &ReportTextPos::LineCol(pair) => {
968 let i =
969 lines.line_column_to_byte(pair.line as usize, pair.col as usize)?;
970 Some(i..i)
971 }
972 ReportTextPos::None => None,
973 }
974 }
975
976 fn line_col(&self, lines: &Lines<String>) -> Option<LineCol> {
978 match self {
979 &ReportTextPos::Full(_, pair) => Some(pair),
980 ReportTextPos::Range(range) => {
981 let (line, col) = lines.byte_to_line_column(range.start as usize)?;
982 Some(LineCol::zero_based(line, col))
983 }
984 &ReportTextPos::LineCol(pair) => Some(pair),
985 ReportTextPos::None => None,
986 }
987 }
988
989 fn try_line_col(&self, bytes: &[u8]) -> Option<LineCol> {
992 match self {
993 &ReportTextPos::Full(_, pair) => Some(pair),
994 ReportTextPos::Range(range) => {
995 LineCol::try_from_byte_pos(range.start as usize, bytes)
996 }
997 &ReportTextPos::LineCol(pair) => Some(pair),
998 ReportTextPos::None => None,
999 }
1000 }
1001}
1002
1003#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1005pub struct LineCol {
1006 line: u32,
1008 col: u32,
1010}
1011
1012impl LineCol {
1013 pub fn zero_based(line: usize, col: usize) -> Self {
1015 Self {
1016 line: line.saturating_as(),
1017 col: col.saturating_as(),
1018 }
1019 }
1020
1021 pub fn one_based(line: usize, col: usize) -> Self {
1023 Self::zero_based(line.saturating_sub(1), col.saturating_sub(1))
1024 }
1025
1026 pub fn try_from_byte_pos(pos: usize, bytes: &[u8]) -> Option<Self> {
1028 let bytes = &bytes[..pos];
1029 let mut line = 0;
1030 #[allow(clippy::double_ended_iterator_last)]
1031 let line_start = memchr::memchr_iter(b'\n', bytes)
1032 .inspect(|_| line += 1)
1033 .last()
1034 .map(|i| i + 1)
1035 .unwrap_or(bytes.len());
1036
1037 let col = ErrorReportingUtf8Chars::new(&bytes[line_start..]).count();
1038 Some(LineCol::zero_based(line, col))
1039 }
1040
1041 pub fn indices(&self) -> (usize, usize) {
1043 (self.line as usize, self.col as usize)
1044 }
1045
1046 pub fn numbers(&self) -> (usize, usize) {
1048 (self.line as usize + 1, self.col as usize + 1)
1049 }
1050}
1051
1052pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> LoadError {
1054 let pos = LineCol::one_based(error.pos().row as usize, error.pos().col as usize);
1055 let message = match error {
1056 roxmltree::Error::UnexpectedCloseTag(expected, actual, _) => {
1057 eco_format!(
1058 "failed to parse {format} (found closing tag '{actual}' instead of '{expected}')"
1059 )
1060 }
1061 roxmltree::Error::UnknownEntityReference(entity, _) => {
1062 eco_format!("failed to parse {format} (unknown entity '{entity}')")
1063 }
1064 roxmltree::Error::DuplicatedAttribute(attr, _) => {
1065 eco_format!("failed to parse {format} (duplicate attribute '{attr}')")
1066 }
1067 roxmltree::Error::NoRootNode => {
1068 eco_format!("failed to parse {format} (missing root node)")
1069 }
1070 err => eco_format!("failed to parse {format} ({err})"),
1071 };
1072
1073 LoadError { text_pos: Some(pos.into()), message }
1074}
1075
1076#[track_caller]
1079pub fn assert_internal(cond: bool, msg: &str) -> HintedStrResult<()> {
1080 if !cond { Err(internal_error(msg)) } else { Ok(()) }
1081}
1082
1083#[track_caller]
1085pub fn panic_internal(msg: &str) -> HintedStrResult<()> {
1086 Err(internal_error(msg))
1087}
1088
1089pub trait ExpectInternal<T> {
1092 fn expect_internal(self, msg: &str) -> HintedStrResult<T>;
1094}
1095
1096impl<T> ExpectInternal<T> for Option<T> {
1097 #[track_caller]
1098 fn expect_internal(self, msg: &str) -> HintedStrResult<T> {
1099 match self {
1100 Some(val) => Ok(val),
1101 None => Err(internal_error(msg)),
1102 }
1103 }
1104}
1105
1106#[track_caller]
1109fn internal_error(msg: &str) -> HintedString {
1110 let loc = std::panic::Location::caller();
1111 let mut error = error!(
1112 "internal error: {msg} (occurred at {loc})";
1113 hint: "please report this as a bug";
1114 );
1115
1116 if cfg!(debug_assertions) {
1117 let backtrace = Backtrace::capture();
1118 if backtrace.status() == BacktraceStatus::Captured {
1119 error.hint(eco_format!("compiler backtrace:\n{backtrace}"));
1120 } else {
1121 error.hint("set `RUST_BACKTRACE` to `1` or `full` to capture a backtrace");
1122 }
1123 }
1124
1125 error
1126}