diff --git a/crates/typst/src/model/mod.rs b/crates/typst/src/model/mod.rs index cab1f71f5..d839422a9 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::{SourceDiagnostic, SourceResult}; +use crate::diag::{warning, SourceDiagnostic, SourceResult}; use crate::doc::Document; use crate::eval::Tracer; use crate::World; @@ -82,7 +82,20 @@ pub fn typeset( introspector = ManuallyDrop::new(Introspector::new(&document.pages)); iter += 1; - if iter >= 5 || introspector.validate(&constraint) { + if introspector.validate(&constraint) { + break; + } + + if iter >= 5 { + tracer.warn( + warning!( + world.main().root().span(), + "layout did not converge within 5 attempts", + ) + .with_hint( + "check if any states or queries are updating themselves".into(), + ), + ); break; } } diff --git a/tests/ref/meta/state.png b/tests/ref/meta/state.png index d179c3c5f..9b3bf57ce 100644 Binary files a/tests/ref/meta/state.png and b/tests/ref/meta/state.png differ diff --git a/tests/src/tests.rs b/tests/src/tests.rs index a8cf25f73..f7eceeada 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -670,8 +670,14 @@ fn parse_part_metadata(source: &Source) -> TestPartMetadata { compare_ref = get_flag_metadata(line, "Ref").or(compare_ref); validate_hints = get_flag_metadata(line, "Hints").or(validate_hints); - fn num(s: &mut Scanner) -> usize { - s.eat_while(char::is_numeric).parse().unwrap() + fn num(s: &mut Scanner) -> isize { + let mut first = true; + let n = &s.eat_while(|c: char| { + let valid = first && c == '-' || c.is_numeric(); + first = false; + valid + }); + n.parse().unwrap_or_else(|e| panic!("{n} is not a number ({e})")) } let comments_until_code = @@ -681,8 +687,15 @@ fn parse_part_metadata(source: &Source) -> TestPartMetadata { let first = num(s) - 1; let (delta, column) = if s.eat_if(':') { (first, num(s) - 1) } else { (0, first) }; - let line = (i + comments_until_code) + delta; - source.line_column_to_byte(line, column).unwrap() + let line = (i + comments_until_code) + .checked_add_signed(delta) + .expect("line number overflowed limits"); + source + .line_column_to_byte( + line, + usize::try_from(column).expect("column number overflowed limits"), + ) + .unwrap() }; let error_factory: fn(Range, String) -> UserOutput = UserOutput::error; diff --git a/tests/typ/meta/state.typ b/tests/typ/meta/state.typ index 8f460ce10..1c329a955 100644 --- a/tests/typ/meta/state.typ +++ b/tests/typ/meta/state.typ @@ -46,3 +46,13 @@ Was: #locate(location => { #trait[Adventure] #trait[Fear] #trait[Anger] + +--- +// Make sure that a warning is produced if the layout fails to converge. +// Warning: -3:1-6:1 layout did not converge within 5 attempts +// Hint: -3:1-6:1 check if any states or queries are updating themselves +#let s = state("x", 1) +#locate(loc => { + s.update(s.final(loc) + 1) +}) +#s.display()