Repository: typst/typst
File: crates/typst/src/diag.rs (Rust)

Code before:
                diagnostic.hint("cannot read file outside of project root");
                diagnostic
                    .hint("you can adjust the project root with the --root argument");
            }
            eco_vec![diagnostic]
        })
    }
}

/// A result type with a string error message and hints.
pub type HintedStrResult<T> = Result<T, HintedString>;

/// A string message with hints.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HintedString {
    /// The string's components as a single vector of strings.
    /// The first element of the vector contains the message.
    /// The remaining elements are the hints.
    /// This is done to reduce the size of a HintedString.
    /// The vector is guaranteed to not be empty.
    components: EcoVec<EcoString>,
}

impl HintedString {
    /// Creates a new hinted string with the given message and hints.
    pub fn new(message: EcoString, hints: impl IntoIterator<Item = EcoString>) -> Self {
        Self {
            components: std::iter::once(message).chain(hints).collect(),
        }
    }

    /// A diagnostic message describing the problem.
    pub fn message(&self) -> &EcoString {
        self.components.first().unwrap()
    }

    /// Additional hints to the user, indicating how this error could be avoided
    /// or worked around.
    pub fn hints(&self) -> &[EcoString] {
        self.components.get(1..).unwrap_or(&[])
    }

    /// Add another hint to this hinted string.
    pub fn add_hint(&mut self, hint: EcoString) {
        self.components.push(hint);
    }
}

impl<S> From<S> for HintedString
where
    S: Into<EcoString>,

Review comment (suggestion):
I think the constructor should not take the hints. Rather, we would replicate the full set of `hint`, `with_hint`, and `with_hints` from `SourceDiagnostic`. That seems like a more convenient API to me.

Code after:
{
    fn at(self, span: Span) -> SourceResult<T> {
        self.map_err(|message| {
            let mut diagnostic = SourceDiagnostic::error(span, message);
            if diagnostic.message.contains("(access denied)") {
                diagnostic.hint("cannot read file outside of project root");
                diagnostic
                    .hint("you can adjust the project root with the --root argument");
            }
            eco_vec![diagnostic]
        })
    }
}

/// A result type with a string error message and hints.
pub type HintedStrResult<T> = Result<T, HintedString>;

/// A string message with hints.
///
/// This is internally represented by a vector of strings.
/// The first element of the vector contains the message.
/// The remaining elements are the hints.
/// This is done to reduce the size of a HintedString.
/// The vector is guaranteed to not be empty.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HintedString(EcoVec<EcoString>);

impl HintedString {
    /// Creates a new hinted string with the given message.
    pub fn new(message: EcoString) -> Self {
        Self(eco_vec![message])
    }

    /// A diagnostic message describing the problem.
    pub fn message(&self) -> &EcoString {
        self.0.first().unwrap()
    }

    /// Additional hints to the user, indicating how this error could be avoided
    /// or worked around.
    pub fn hints(&self) -> &[EcoString] {
        self.0.get(1..).unwrap_or(&[])
    }

    /// Adds a single hint to the hinted string.
    pub fn hint(&mut self, hint: impl Into<EcoString>) {
        self.0.push(hint.into());
    }

    /// Adds a single hint to the hinted string.
    pub fn with_hint(mut self, hint: impl Into<EcoString>) -> Self {