Skip to content

Check objects and call arguments as Jinja2 does (0197, 0206) - #552

Merged
rmorozov merged 7 commits into
masterfrom
claude/project-thread-sunui7
Oct 17, 2026
Merged

rmorozov merged 7 commits into
masterfrom
claude/project-thread-sunui7

Conversation

@rmorozov

Copy link
Copy Markdown
Member

Requested by Ruslan · project thread

Before: cycler(1, 2)|truncate rendered empty. namespace(a=1)|urlencode gave a=1. {% set a, b = {'x': 1, 'y': 2} %} took the items named a and b, so both were empty. A number of calls that Python rejects also rendered: range(stop=2), ['a']|map('upper', 1), ns|attr('a', 'b'), ns|attr(1), joiner('a')('x'), lipsum(namespace()) and 1 is eq(b=1).

After: each of these behaves as in Jinja2 3.1.6. The tuple assignment unpacks the dict's keys (xy), and every other call raises the TypeError Python raises.

The changes:

  • truncate on a value that is not a string follows do_truncate. A value with no len() raises (None, a bool, a number, an object). A short list or dict comes back as itself. A long one fails where Python's string code fails. end must have a len(), and an undefined checks length >= len(end) too.
  • urlencode quotes the repr of an object such as namespace().
  • Tuple assignment from a dict unpacks its keys. A host object (a reflected struct or another GenericMap) keeps the old by-name extension, because Python could not unpack it at all.
  • range rejects keyword arguments.
  • lipsum checks that n is an integer, and checks min and max when it makes a paragraph.
  • The objects' methods reject arguments: joiner's call and cycler's next() and reset().
  • attr takes one positional argument, and it must be a string. default= stays a keyword-only Jinja2C++ extension.
  • map reports the named filter's argument error at the first item.
  • The comparison tests (eq, lt, …) reject keyword arguments.

Deliberate divergences:

  • tests.select_unread_extra_arg is now a deliberate divergence under 0101. select stays eager, which is the same model as filters.select_single_pass.
  • range() with no arguments still renders empty, as before.

Filed 0212 for what 0197 could not reach:

  • An object used as a dict key. Jinja2 accepts it, but the 0197 brief expected a TypeError. It is pinned by the corpus case globals.object_dict_key.
  • An object passed through a user callable.
  • JSON-bound objects, which still unpack by name. This needs a public-API flag to tell them apart from reflected structs.

How: the corpus lines for 0197 and 0206 are deleted from test/parity/divergences/. New corpus cases cover each behaviour that had none. Unit tests that encoded the old behaviour were updated:

  • In forloop_test.cpp, the range keyword rows are gone.
  • SetTest.Tuple1AssignmentTest now expects the keys.
  • A new reflected-struct case pins the by-name extension.

Roles: verifier (1 round, which found 1 blocking issue: a missing include flagged by clang-tidy, fixed). Its non-blocking findings were fixed here (truncate end/undefined, lipsum(0)) or filed in 0212. scripts/preflight.sh --perf is clean; the worst benchmark moved +1.58% (Render/strings). Red pushes: none yet.


Generated by Claude Code

truncate takes len() of any value and urlencode quotes an object's repr;
a tuple assignment from a dict unpacks its keys (host objects keep the
by-name extension); lipsum, attr, joiner and cycler methods check their
arguments; map reports the named filter's argument error; range and the
comparison tests reject keyword arguments. The unread-select case becomes
a deliberate divergence under 0101; object dict keys and objects through
user callables are filed as 0210.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
…o range

Fixes found by the verifier: truncate rejects an end with no len() and
checks length >= len(end) for an undefined value too; lipsum with no
paragraphs no longer checks min < max. The JSON-object unpacking finding
goes to 0210.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
@rmorozov rmorozov self-assigned this Oct 17, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HFw7ujEwV7pcqD96PpJnDR
@rmorozov
rmorozov marked this pull request as ready for review October 17, 2026 17:52
rmorozov added a commit that referenced this pull request Oct 17, 2026
@rmorozov
rmorozov merged commit 9246e70 into master Oct 17, 2026
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