Why the heading number can be zero in v0.14.0-rc.2?

#set heading(numbering: (n, ..nums) => {
  assert(n > 0, message: "n = " + repr(n))
  [#n is not zero.]
})

= Heading

图片

The above code compiles in typst v0.13.1, but panics with Assertion failed: n = 0 in typst v0.14.0-rc.2. Why?

error: assertion failed: n = 0
  ┌─ <stdin>:2:2
  │
2 │   assert(n > 0, message: "n = " + repr(n))
  │   ^^^^^^^^^^^^^^^^^^

Moreover, if I remove the assert line, then v0.14.0-rc.2 also generates 1 is not zero. How can it be?

Use case

Sometimes we want a Chapter zero, but the numbering function only accepts nonnegative numbers.

  #set heading(
-   numbering: n => numbering("第一章", n - 1),  // v0.13.1
+   numbering: n => numbering("第一章", calc.max(n - 1, 0)),  // v0.14.0; calc.abs also works
  )

(I know that the chapter zero is for sections before the first chapter, but such sections do not always exist.)

Edit: This post also applies to the final v0.14.

1 Like

That behaviour is familiar from the layout iterations that typst is using. NOTE: my understanding (below) is still based on Typst usage, not internal knowledge.

  • First layout iteration:
    Context dependent numbering is resolved using initial/stale data
    Propagate state updates
  • Second layout iteration
    Context dependent numbering is updated using state information from previous iteration - heading gets the correct number

As usual in typst, if a function panics inside context that whole context block fails (produces empty output) but compilation continues. Typst will try again in the next iteration.

However if the context block contains something that the second iteration needs, then the second iteration will fail the same way as the first.

Errors from inside context are only presented to the user if they remain in the last layout iteration - so you never see the errors that resolve themselves.

Conclusion: in typst 0.14.0-rc.2, the counter(heading).step() probably never happens if the numbering function call fails. For some reason they are linked, as if they are inside the same context block.

1 Like

In Typst 0.13, this document compiles (in 2 iterations)

#import "@preview/layout-ltd:0.1.0": layout-limiter
#show: layout-limiter.with(max-iterations: 2)

#set heading(numbering: "1")
= Heading

In Typst 0.14.0, it needs 3 iterations to complete without layout convergence warning.

1 Like