summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorlolstork <137357423+lolstork@users.noreply.github.com>2023-07-19 12:52:47 +0200
committerGitHub <noreply@github.com>2023-07-19 12:52:47 +0200
commitb37c1e27314ed9b9341dd82c1bbc8238121c7578 (patch)
treecb8d9d4d179fdbcdbc94b3104d068cd21bf24074 /crates
parent8a57395ee48ecee02c2eb833d232979730f0e445 (diff)
Add infrastructure for compiler warnings (#1731)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-cli/src/compile.rs55
-rw-r--r--crates/typst-cli/src/watch.rs5
-rw-r--r--crates/typst-docs/src/html.rs6
-rw-r--r--crates/typst/src/diag.rs78
-rw-r--r--crates/typst/src/eval/mod.rs61
-rw-r--r--crates/typst/src/eval/tracer.rs70
-rw-r--r--crates/typst/src/ide/analyze.rs49
-rw-r--r--crates/typst/src/lib.rs7
-rw-r--r--crates/typst/src/model/mod.rs6
9 files changed, 215 insertions, 122 deletions
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index 9dfa4d41..f6819319 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -4,9 +4,9 @@ use std::path::Path;
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term::{self, termcolor};
use termcolor::{ColorChoice, StandardStream};
-use typst::diag::{bail, SourceError, StrResult};
+use typst::diag::{bail, Severity, SourceDiagnostic, StrResult};
use typst::doc::Document;
-use typst::eval::eco_format;
+use typst::eval::{eco_format, Tracer};
use typst::geom::Color;
use typst::syntax::{FileId, Source};
use typst::World;
@@ -46,9 +46,13 @@ pub fn compile_once(
world.reset();
world.source(world.main()).map_err(|err| err.to_string())?;
- let result = typst::compile(world);
+ let mut tracer = Tracer::default();
+
+ let result = typst::compile(world, &mut tracer);
let duration = start.elapsed();
+ let warnings = tracer.warnings();
+
match result {
// Export the PDF / PNG.
Ok(document) => {
@@ -56,9 +60,16 @@ pub fn compile_once(
tracing::info!("Compilation succeeded in {duration:?}");
if watching {
- Status::Success(duration).print(command).unwrap();
+ if warnings.is_empty() {
+ Status::Success(duration).print(command).unwrap();
+ } else {
+ Status::PartialSuccess(duration).print(command).unwrap();
+ }
}
+ print_diagnostics(world, &[], &warnings, command.diagnostic_format)
+ .map_err(|_| "failed to print diagnostics")?;
+
if let Some(open) = command.open.take() {
open_file(open.as_deref(), &command.output())?;
}
@@ -73,7 +84,7 @@ pub fn compile_once(
Status::Error.print(command).unwrap();
}
- print_diagnostics(world, *errors, command.diagnostic_format)
+ print_diagnostics(world, &errors, &warnings, command.diagnostic_format)
.map_err(|_| "failed to print diagnostics")?;
}
}
@@ -143,7 +154,8 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> {
/// Print diagnostic messages to the terminal.
fn print_diagnostics(
world: &SystemWorld,
- errors: Vec<SourceError>,
+ errors: &[SourceDiagnostic],
+ warnings: &[SourceDiagnostic],
diagnostic_format: DiagnosticFormat,
) -> Result<(), codespan_reporting::files::Error> {
let mut w = match diagnostic_format {
@@ -156,23 +168,28 @@ fn print_diagnostics(
config.display_style = term::DisplayStyle::Short;
}
- for error in errors {
- // The main diagnostic.
- let diag = Diagnostic::error()
- .with_message(error.message)
- .with_notes(
- error
- .hints
- .iter()
- .map(|e| (eco_format!("hint: {e}")).into())
- .collect(),
- )
- .with_labels(vec![Label::primary(error.span.id(), world.range(error.span))]);
+ for diagnostic in warnings.iter().chain(errors.iter()) {
+ let diag = match diagnostic.severity {
+ Severity::Error => Diagnostic::error(),
+ Severity::Warning => Diagnostic::warning(),
+ }
+ .with_message(diagnostic.message.clone())
+ .with_notes(
+ diagnostic
+ .hints
+ .iter()
+ .map(|e| (eco_format!("hint: {e}")).into())
+ .collect(),
+ )
+ .with_labels(vec![Label::primary(
+ diagnostic.span.id(),
+ world.range(diagnostic.span),
+ )]);
term::emit(&mut w, &config, world, &diag)?;
// Stacktrace-like helper diagnostics.
- for point in error.trace {
+ for point in &diagnostic.trace {
let message = point.v.to_string();
let help = Diagnostic::help().with_message(message).with_labels(vec![
Label::primary(point.span.id(), world.range(point.span)),
diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs
index cf9c05ba..759d27ec 100644
--- a/crates/typst-cli/src/watch.rs
+++ b/crates/typst-cli/src/watch.rs
@@ -138,6 +138,7 @@ fn is_event_relevant(event: &notify::Event, output: &Path) -> bool {
pub enum Status {
Compiling,
Success(std::time::Duration),
+ PartialSuccess(std::time::Duration),
Error,
}
@@ -176,6 +177,9 @@ impl Status {
match self {
Self::Compiling => "compiling ...".into(),
Self::Success(duration) => format!("compiled successfully in {duration:.2?}"),
+ Self::PartialSuccess(duration) => {
+ format!("compiled with warnings in {duration:.2?}")
+ }
Self::Error => "compiled with errors".into(),
}
}
@@ -184,6 +188,7 @@ impl Status {
let styles = term::Styles::default();
match self {
Self::Error => styles.header_error,
+ Self::PartialSuccess(_) => styles.header_warning,
_ => styles.header_note,
}
}
diff --git a/crates/typst-docs/src/html.rs b/crates/typst-docs/src/html.rs
index ed49f9fe..b021d4a7 100644
--- a/crates/typst-docs/src/html.rs
+++ b/crates/typst-docs/src/html.rs
@@ -4,7 +4,7 @@ use comemo::Prehashed;
use pulldown_cmark as md;
use typed_arena::Arena;
use typst::diag::FileResult;
-use typst::eval::Datetime;
+use typst::eval::{Datetime, Tracer};
use typst::font::{Font, FontBook};
use typst::geom::{Point, Size};
use typst::syntax::{FileId, Source};
@@ -428,7 +428,9 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
let id = FileId::new(None, Path::new("/main.typ"));
let source = Source::new(id, compile);
let world = DocWorld(source);
- let mut frames = match typst::compile(&world) {
+ let mut tracer = Tracer::default();
+
+ let mut frames = match typst::compile(&world, &mut tracer) {
Ok(doc) => doc.pages,
Err(err) => {
let msg = &err[0].message;
diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs
index 85fb10a2..1cc4a045 100644
--- a/crates/typst/src/diag.rs
+++ b/crates/typst/src/diag.rs
@@ -33,7 +33,7 @@ macro_rules! __bail {
};
($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
- return Err(Box::new(vec![$crate::diag::SourceError::new(
+ return Err(Box::new(vec![$crate::diag::SourceDiagnostic::error(
$span,
$crate::diag::eco_format!($fmt, $($arg),*),
)]))
@@ -43,7 +43,7 @@ macro_rules! __bail {
#[doc(inline)]
pub use crate::__bail as bail;
-/// Construct an [`EcoString`] or [`SourceError`].
+/// Construct an [`EcoString`] or [`SourceDiagnostic`] with severity `Error`.
#[macro_export]
#[doc(hidden)]
macro_rules! __error {
@@ -52,7 +52,19 @@ macro_rules! __error {
};
($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
- $crate::diag::SourceError::new(
+ $crate::diag::SourceDiagnostic::error(
+ $span,
+ $crate::diag::eco_format!($fmt, $($arg),*),
+ )
+ };
+}
+
+/// Construct a [`SourceDiagnostic`] with severity `Warning`.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __warning {
+ ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
+ $crate::diag::SourceDiagnostic::warning(
$span,
$crate::diag::eco_format!($fmt, $($arg),*),
)
@@ -61,33 +73,58 @@ macro_rules! __error {
#[doc(inline)]
pub use crate::__error as error;
+#[doc(inline)]
+pub use crate::__warning as warning;
#[doc(hidden)]
pub use ecow::{eco_format, EcoString};
/// A result that can carry multiple source errors.
-pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
+pub type SourceResult<T> = Result<T, Box<Vec<SourceDiagnostic>>>;
-/// An error in a source file.
+/// An error or warning in a source file.
///
/// The contained spans will only be detached if any of the input source files
/// were detached.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct SourceError {
- /// The span of the erroneous node in the source code.
+pub struct SourceDiagnostic {
+ /// Whether the diagnostic is an error or a warning.
+ pub severity: Severity,
+ /// The span of the relevant node in the source code.
pub span: Span,
/// A diagnostic message describing the problem.
pub message: EcoString,
- /// The trace of function calls leading to the error.
+ /// The trace of function calls leading to the problem.
pub trace: Vec<Spanned<Tracepoint>>,
- /// Additonal hints to the user, indicating how this error could be avoided
+ /// Additonal hints to the user, indicating how this problem could be avoided
/// or worked around.
pub hints: Vec<EcoString>,
}
-impl SourceError {
+/// The severity of a [`SourceDiagnostic`].
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub enum Severity {
+ /// A fatal error.
+ Error,
+ /// A non-fatal warning.
+ Warning,
+}
+
+impl SourceDiagnostic {
/// Create a new, bare error.
- pub fn new(span: Span, message: impl Into<EcoString>) -> Self {
+ pub fn error(span: Span, message: impl Into<EcoString>) -> Self {
+ Self {
+ severity: Severity::Error,
+ span,
+ trace: vec![],
+ message: message.into(),
+ hints: vec![],
+ }
+ }
+
+ /// Create a new, bare warning.
+ pub fn warning(span: Span, message: impl Into<EcoString>) -> Self {
Self {
+ severity: Severity::Warning,
span,
trace: vec![],
message: message.into(),
@@ -95,16 +132,23 @@ impl SourceError {
}
}
- /// Adds user-facing hints to the error.
+ /// Adds a single hint to the diagnostic.
+ pub fn with_hint(mut self, hint: EcoString) -> Self {
+ self.hints.push(hint);
+ self
+ }
+
+ /// Adds user-facing hints to the diagnostic.
pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
self.hints.extend(hints);
self
}
}
-impl From<SyntaxError> for SourceError {
+impl From<SyntaxError> for SourceDiagnostic {
fn from(error: SyntaxError) -> Self {
Self {
+ severity: Severity::Error,
span: error.span,
message: error.message,
trace: vec![],
@@ -113,7 +157,7 @@ impl From<SyntaxError> for SourceError {
}
}
-/// A part of an error's [trace](SourceError::trace).
+/// A part of a diagnostic's [trace](SourceDiagnostic::trace).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Tracepoint {
/// A function call.
@@ -194,7 +238,7 @@ where
S: Into<EcoString>,
{
fn at(self, span: Span) -> SourceResult<T> {
- self.map_err(|message| Box::new(vec![SourceError::new(span, message)]))
+ self.map_err(|message| Box::new(vec![SourceDiagnostic::error(span, message)]))
}
}
@@ -214,7 +258,9 @@ pub struct HintedString {
impl<T> At<T> for Result<T, HintedString> {
fn at(self, span: Span) -> SourceResult<T> {
self.map_err(|diags| {
- Box::new(vec![SourceError::new(span, diags.message).with_hints(diags.hints)])
+ Box::new(vec![
+ SourceDiagnostic::error(span, diags.message).with_hints(diags.hints)
+ ])
})
}
}
diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs
index f72eeaa4..b4048e1b 100644
--- a/crates/typst/src/eval/mod.rs
+++ b/crates/typst/src/eval/mod.rs
@@ -24,6 +24,7 @@ mod none;
pub mod ops;
mod scope;
mod symbol;
+mod tracer;
#[doc(hidden)]
pub use {
@@ -53,6 +54,7 @@ pub use self::none::NoneValue;
pub use self::scope::{Scope, Scopes};
pub use self::str::{format_str, Regex, Str};
pub use self::symbol::Symbol;
+pub use self::tracer::Tracer;
pub use self::value::{Dynamic, Type, Value};
use std::collections::HashSet;
@@ -66,7 +68,8 @@ use unicode_segmentation::UnicodeSegmentation;
use self::func::{CapturesVisitor, Closure};
use crate::diag::{
- bail, error, At, FileError, SourceError, SourceResult, StrResult, Trace, Tracepoint,
+ bail, error, warning, At, FileError, SourceDiagnostic, SourceResult, StrResult,
+ Trace, Tracepoint,
};
use crate::model::{
Content, DelayedErrors, Introspector, Label, Locator, Recipe, ShowableSelector,
@@ -292,7 +295,7 @@ pub enum FlowEvent {
impl FlowEvent {
/// Return an error stating that this control flow is forbidden.
- pub fn forbidden(&self) -> SourceError {
+ pub fn forbidden(&self) -> SourceDiagnostic {
match *self {
Self::Break(span) => {
error!(span, "cannot break outside of loop")
@@ -351,47 +354,6 @@ impl<'a> Route<'a> {
}
}
-/// Traces which values existed for an expression at a span.
-#[derive(Default, Clone)]
-pub struct Tracer {
- span: Option<Span>,
- values: Vec<Value>,
-}
-
-impl Tracer {
- /// The maximum number of traced items.
- pub const MAX: usize = 10;
-
- /// Create a new tracer, possibly with a span under inspection.
- pub fn new(span: Option<Span>) -> Self {
- Self { span, values: vec![] }
- }
-
- /// Get the traced values.
- pub fn finish(self) -> Vec<Value> {
- self.values
- }
-}
-
-#[comemo::track]
-impl Tracer {
- /// The traced span if it is part of the given source file.
- fn span(&self, id: FileId) -> Option<Span> {
- if self.span.map(Span::id) == Some(id) {
- self.span
- } else {
- None
- }
- }
-
- /// Trace a value for the span.
- fn trace(&mut self, v: Value) {
- if self.values.len() < Self::MAX {
- self.values.push(v);
- }
- }
-}
-
/// Evaluate an expression.
pub(super) trait Eval {
/// The output of evaluating the expression.
@@ -616,7 +578,18 @@ impl Eval for ast::Strong {
#[tracing::instrument(name = "Strong::eval", skip_all)]
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.strong)(self.body().eval(vm)?))
+ let body = self.body();
+ if body.exprs().next().is_none() {
+ vm.vt
+ .tracer
+ .warn(warning!(self.span(), "no text within stars").with_hint(
+ EcoString::from(
+ "using multiple consecutive stars (e.g. **) has no additional effect",
+ ),
+ ));
+ }
+
+ Ok((vm.items.strong)(body.eval(vm)?))
}
}
diff --git a/crates/typst/src/eval/tracer.rs b/crates/typst/src/eval/tracer.rs
new file mode 100644
index 00000000..0be6c189
--- /dev/null
+++ b/crates/typst/src/eval/tracer.rs
@@ -0,0 +1,70 @@
+use std::collections::HashSet;
+
+use ecow::{eco_vec, EcoVec};
+
+use super::Value;
+use crate::diag::SourceDiagnostic;
+use crate::syntax::{FileId, Span};
+use crate::util::hash128;
+
+/// Traces warnings and which values existed for an expression at a span.
+#[derive(Default, Clone)]
+pub struct Tracer {
+ span: Option<Span>,
+ values: EcoVec<Value>,
+ warnings: EcoVec<SourceDiagnostic>,
+ warnings_set: HashSet<u128>,
+}
+
+impl Tracer {
+ /// The maximum number of traced items.
+ pub const MAX: usize = 10;
+
+ /// Create a new tracer, possibly with a span under inspection.
+ pub fn new(span: Option<Span>) -> Self {
+ Self {
+ span,
+ values: eco_vec![],
+ warnings: eco_vec![],
+ warnings_set: HashSet::new(),
+ }
+ }
+
+ /// Get the traced values.
+ pub fn values(self) -> EcoVec<Value> {
+ self.values
+ }
+
+ /// Get the stored warnings.
+ pub fn warnings(self) -> EcoVec<SourceDiagnostic> {
+ self.warnings
+ }
+}
+
+#[comemo::track]
+impl Tracer {
+ /// The traced span if it is part of the given source file.
+ pub fn span(&self, id: FileId) -> Option<Span> {
+ if self.span.map(Span::id) == Some(id) {
+ self.span
+ } else {
+ None
+ }
+ }
+
+ /// Trace a value for the span.
+ pub fn trace(&mut self, v: Value) {
+ if self.values.len() < Self::MAX {
+ self.values.push(v);
+ }
+ }
+
+ /// Add a warning.
+ pub fn warn(&mut self, warning: SourceDiagnostic) {
+ // Check if warning is a duplicate.
+ let hash = hash128(&(&warning.span, &warning.message));
+ if self.warnings_set.insert(hash) {
+ self.warnings.push(warning);
+ }
+ }
+}
diff --git a/crates/typst/src/ide/analyze.rs b/crates/typst/src/ide/analyze.rs
index dad466c1..c143720a 100644
--- a/crates/typst/src/ide/analyze.rs
+++ b/crates/typst/src/ide/analyze.rs
@@ -1,5 +1,5 @@
use comemo::Track;
-use ecow::EcoString;
+use ecow::{eco_vec, EcoString, EcoVec};
use crate::doc::Frame;
use crate::eval::{eval, Module, Route, Tracer, Value};
@@ -8,18 +8,18 @@ use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::World;
/// Try to determine a set of possible values for an expression.
-pub fn analyze_expr(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
+pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<Value> {
match node.cast::<ast::Expr>() {
- Some(ast::Expr::None(_)) => vec![Value::None],
- Some(ast::Expr::Auto(_)) => vec![Value::Auto],
- Some(ast::Expr::Bool(v)) => vec![Value::Bool(v.get())],
- Some(ast::Expr::Int(v)) => vec![Value::Int(v.get())],
- Some(ast::Expr::Float(v)) => vec![Value::Float(v.get())],
- Some(ast::Expr::Numeric(v)) => vec![Value::numeric(v.get())],
- Some(ast::Expr::Str(v)) => vec![Value::Str(v.get().into())],
+ Some(ast::Expr::None(_)) => eco_vec![Value::None],
+ Some(ast::Expr::Auto(_)) => eco_vec![Value::Auto],
+ Some(ast::Expr::Bool(v)) => eco_vec![Value::Bool(v.get())],
+ Some(ast::Expr::Int(v)) => eco_vec![Value::Int(v.get())],
+ Some(ast::Expr::Float(v)) => eco_vec![Value::Float(v.get())],
+ Some(ast::Expr::Numeric(v)) => eco_vec![Value::numeric(v.get())],
+ Some(ast::Expr::Str(v)) => eco_vec![Value::Str(v.get().into())],
Some(ast::Expr::FieldAccess(access)) => {
- let Some(child) = node.children().next() else { return vec![] };
+ let Some(child) = node.children().next() else { return eco_vec![] };
analyze_expr(world, &child)
.into_iter()
.filter_map(|target| target.field(&access.field()).ok())
@@ -33,36 +33,17 @@ pub fn analyze_expr(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Val
}
}
- let route = Route::default();
let mut tracer = Tracer::new(Some(node.span()));
- typst::eval::eval(
- world.track(),
- route.track(),
- tracer.track_mut(),
- &world.main(),
- )
- .and_then(|module| {
- typst::model::typeset(
- world.track(),
- tracer.track_mut(),
- &module.content(),
- )
- })
- .ok();
-
- tracer.finish()
+ crate::compile(world, &mut tracer).ok();
+ tracer.values()
}
- _ => vec![],
+ _ => eco_vec![],
}
}
/// Try to load a module from the current source file.
-pub fn analyze_import(
- world: &(dyn World + 'static),
- source: &Source,
- path: &str,
-) -> Option<Module> {
+pub fn analyze_import(world: &dyn World, source: &Source, path: &str) -> Option<Module> {
let route = Route::default();
let mut tracer = Tracer::default();
let id = source.id().join(path).ok()?;
@@ -77,7 +58,7 @@ pub fn analyze_import(
/// - A split offset: All labels before this offset belong to nodes, all after
/// belong to a bibliography.
pub fn analyze_labels(
- world: &(dyn World + 'static),
+ world: &dyn World,
frames: &[Frame],
) -> (Vec<(Label, Option<EcoString>)>, usize) {
let mut output = vec![];
diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs
index 861cb853..3365bbaa 100644
--- a/crates/typst/src/lib.rs
+++ b/crates/typst/src/lib.rs
@@ -66,10 +66,9 @@ use crate::syntax::{FileId, PackageSpec, Source, Span};
use crate::util::Bytes;
/// Compile a source file into a fully layouted document.
-#[tracing::instrument(skip(world))]
-pub fn compile(world: &dyn World) -> SourceResult<Document> {
+#[tracing::instrument(skip_all)]
+pub fn compile(world: &dyn World, tracer: &mut Tracer) -> SourceResult<Document> {
let route = Route::default();
- let mut tracer = Tracer::default();
// Call `track` just once to keep comemo's ID stable.
let world = world.track();
@@ -83,7 +82,7 @@ pub fn compile(world: &dyn World) -> SourceResult<Document> {
&world.main(),
)?;
- // Typeset the module's contents.
+ // Typeset it.
model::typeset(world, tracer, &module.content())
}
diff --git a/crates/typst/src/model/mod.rs b/crates/typst/src/model/mod.rs
index ee940236..cab1f71f 100644
--- a/crates/typst/src/model/mod.rs
+++ b/crates/typst/src/model/mod.rs
@@ -28,7 +28,7 @@ use std::mem::ManuallyDrop;
use comemo::{Track, Tracked, TrackedMut, Validate};
-use crate::diag::{SourceError, SourceResult};
+use crate::diag::{SourceDiagnostic, SourceResult};
use crate::doc::Document;
use crate::eval::Tracer;
use crate::World;
@@ -137,12 +137,12 @@ impl Vt<'_> {
/// Holds delayed errors.
#[derive(Default, Clone)]
-pub struct DelayedErrors(Vec<SourceError>);
+pub struct DelayedErrors(Vec<SourceDiagnostic>);
#[comemo::track]
impl DelayedErrors {
/// Push a delayed error.
- fn push(&mut self, error: SourceError) {
+ fn push(&mut self, error: SourceDiagnostic) {
self.0.push(error);
}
}