Skip to content

Jinja2's error classes and messages (0432), Jinja2's generate() items (0436) - #830

Draft
rmorozov wants to merge 9 commits into
masterfrom
claude/project-thread-k68f69
Draft

rmorozov wants to merge 9 commits into
masterfrom
claude/project-thread-k68f69

Conversation

@rmorozov

@rmorozov rmorozov commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Requested by Ruslan · project thread

Problem

The upstream Jinja2 3.1.6 suite through the Python module (fallback="never") passed 607 of 856. The two largest gaps were the error contract (0432: wrong exception classes and messages, no template frame in tracebacks) and generate() (0436: one item for the whole output instead of Jinja2's items).

Result

0432, engine. New additive codes ErrorCode::TemplateSyntaxError = 1023 and TemplateAssertionError = 1024 (ExtraParams[0] is Jinja2's message). Unknown and misplaced tags say Jinja2's sentence ("Encountered unknown tag 'endif'. Jinja was looking for the following tags: ..."; UnexpectedStatement keeps the tag in [0], the sentence in [1]); a template ending inside a statement says "Unexpected end of template"; trans and required-block errors are reported at the end tag as in Jinja2; compile-time checks are TemplateAssertionError. TemplateRuntimeError names the Python class ([1] = TypeError/ValueError) where Jinja2 raises one. A name from ... import cannot find is an undefined value with Jinja2's message; {% include x %} takes a host sequence of names.

0432, module. Jinja2's classes (TemplateAssertionError, TypeError/ValueError, TemplatesNotFound), a FileSystemLoader's TemplateNotFound naming the search path, the source of an included template's syntax error, and a template frame in tracebacks (jinja2cpp._debug, as Jinja2's fake_traceback).

0436. New public RenderOptions::writeEachPiece: a sink gets one Write per piece Jinja2's generate() yields (a text run, a {{ }} value, a macro or call block's output). Template.generate()/stream() yield those items, and an error after the items written before it. The render still runs at the first item, on the caller's thread: a worker-thread lazy generate was built and dropped after verifier round 1 (thread affinity of sqlite, threading.local and RLock; a gevent deadlock; an abort at interpreter exit). The on-demand design (a same-thread fiber) is filed as 0457.

Upstream suite: 628 / 856 (73.4%), 95.9% of the supported subset. The expected-failures list drops the passing 0432 lines and both TestStreaming lines; two traceback tests are deliberate (class names are jinja2cpp.exceptions.*).

Follow-ups filed: 0456 (items Jinja2 folds into one: constants, super(), self.b(), recursive loop, empty items, macro output over 512 characters), 0457 (on-demand generate()), 0458 (include and constructor errors with Jinja2's classes, a Python loader's own TemplateNotFound message).

Public-header growth (two ErrorCode values, RenderOptions::writeEachPiece) is recorded in docs/api-2.0.md and in the G1 row of docs/release-2.0.md; it lands before G1.

Validation

  • ctest (Debug) passes; Python tests 1636 passed, 16 skipped, 2 xfailed, plus the new BaseException test.
  • scripts/preflight.sh --perf against e363ec2: all checks pass, largest instruction change +0.93%.
  • Verifier: address+undefined RelWithDebInfo build, full ctest and the touched suites under ASan/UBSan pass.
  • Parity: round 1 swept 30,783 malformed templates against Jinja2's error classes; round 2 compared generate() items with Jinja2 on 10,500 random and about 55 hand-written templates (joined output always equal, no item coarser than Jinja2's).
  • Not built here: MSVC and Apple Clang (CI only; the verifier reviewed the diff for them).

Limits

generate() renders the whole template at the first item (0457); some items are finer than Jinja2's (0456); a Python loader's get_source runs again when a template fails to compile, to show the error's line.

Process

  • Roles: implementer (engine half of 0432), verifier ×2, security review.
  • Verifier rounds: 2. Round 1: 3 blocking findings, all in the worker-thread generate, removed; its medium and low findings fixed or filed. Round 2: no blocking; the medium (macro output split at 512 characters) is documented and in 0456; L1 (a BaseException dropped the earlier items) fixed; the rest filed in 0456/0458 or documented.
  • Security review: 0 findings, 0 fixed, 0 declined (12 candidates checked and dropped: lexer positions, autoescape through writeEachPiece, include names, _debug.py compile).
  • Codex: 1 finding (P2: include of a host iterator drained it before loading; fixed in 0f37cfd), 1 fixed, 0 declined.
  • Red CI pushes: 2 of 4. The first push (bdd023c) failed on MSVC (C4324: the new bool before OutStream's alignas buffer padded the struct; moved after the buffer) and in the Python jobs (the chat-template corpus test caught only TemplateError, but 0432 now raises Python's TypeError where transformers records one; I had run the Python tests with that file ignored locally, where a module-level importorskip skipped it, now a fixture). d479fb0 was green. 0f37cfd failed clang-tidy (an rvalue parameter not moved; I pushed it with preflight red), fixed in 3c2328b.

Generated by Claude Code

…ngine)

Tags that are unknown or not where an open statement looks for them now say what Jinja2
does (parser.py _fail_ut_eof): "Encountered unknown tag 'endif'. Jinja was looking for the
following tags: ...". The code stays UnexpectedStatement, ExtraParams[0] is the tag and
ExtraParams[1] the sentence; an unknown name (`{% unknown_tag %}`) now reports it too instead
of UnexpectedToken. A template that ends inside a statement is the new
ErrorCode::TemplateSyntaxError ("Unexpected end of template. ..."), as are the trans-block
and required-block errors (reported at the end tag, as Jinja2 does); the compile-time checks
(unknown pluralization variable, a trans variable defined twice, a `caller` parameter with no
default that the body uses) are the new ErrorCode::TemplateAssertionError.

ErrorCode::TemplateRuntimeError gets ExtraParams[1] "TypeError" or "ValueError" where the
engine knows the class Jinja2 raises: not iterable, an undefined slice bound, the doubled
caller argument, and an invalid xmlattr key, whose message is now Jinja2's with the key's repr.

A name a `from ... import` cannot find is an undefined value whose message is Jinja2's ("the
template 'module' (imported on line 1) does not export the requested name 'x'"), and
`{% include x %}` tries the names of a host sequence in turn, as for a template list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
0432 module: TemplateAssertionError for the new code, Python's TypeError and
ValueError where the engine names them, TemplatesNotFound for an include of
several names, the search path in a FileSystemLoader's TemplateNotFound, the
source of an included template's syntax error, and a template frame in
tracebacks (jinja2cpp._debug, as Jinja2's fake_traceback). The engine half
adds a Jinja2 message for a hyphenated block name.

0436: Template.generate runs the render on a daemon thread that takes turns
with the caller, at most one 512-character batch ahead, in a copy of the
caller's context; close, del and atexit end it, Ctrl-C stops it. Items are
Jinja2's through the new RenderOptions::writeEachPiece. Follow-up 0452.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
… read loader sources for errors only (0436, 0432)

generate() renders on the caller's thread at the first item and yields
Jinja2's items (writeEachPiece). The on-demand design moves to 0454.
Loaders re-run a Python get_source only to show an error's source line.
Template frames keep their file name; files 0452, 0454 and 0455.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
…und 2 limits (0436, 0432)

A macro output past the 512-character buffer arrives in pieces (0456), more empty and
folded items (0456), a Python loader's own TemplateNotFound message (0458), the error-path
get_source call (docs/python.md), and the public-header growth in api-2.0.md and the G1 row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
@rmorozov rmorozov self-assigned this Nov 11, 2026
…ng before an alignas member)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
…aises (0432)

transformers records these as TypeError; the module now raises Python's class where
the engine names it. The transformers importorskip moves into a fixture, so the
corpus test runs without transformers installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8

Copy link
Copy Markdown
Member Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T02:39:34.961245Z d479fb0 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d479fb098a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/statements.cpp Resolved
…2 on #830)

The names were drained into a list first, so an iterator that raised or never ended after a
found name failed the include; Python's select_template stops at the first found name. The
names tried are kept for the error, each counted against the sequence limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
…ce-param-not-moved)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWNL7kzwUCnXqkdTKL5Gb8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants