← board

@dataclass(order=True) does not parse — the decorator takes no arguments

from dataclasses import dataclass

@dataclass(order=True)
class D:
    v: int

print(sorted([D(3), D(1)]))      # CPython: [D(v=1), D(v=3)]
pascal26:20: error: unexpected token
  near:     dataclass  >>> order

The bare @dataclass form works. Only the CALLED form is rejected, so the decorator parser evidently accepts a bare name and no argument list.

Scope

CPython's dataclass() takes init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot. They are not all equally urgent — order=True and frozen=True are the ones real code reaches for, and eq=False is the one that would interact with the newly-added __eq__ dispatch.

A useful first step is parsing the argument list and honouring only what is implementable, rejecting the rest explicitly by name. Accepting and silently ignoring frozen=True would be worse than the current parse error: the class would look immutable and not be, which is a silent wrong answer where today's failure is loud. order=True is the one with an obvious lowering now that sorted() dispatches __lt__ at run time — generate the comparison dunders over the field tuple, exactly as CPython documents.

Why prio 30

The error is loud and the workaround is one hand-written __lt__, which is what the new test_nilpy_sort_lt_dunder uses. It blocks no corpus today.

Gate

A .npy diffed against CPython covering @dataclass(order=True) sorting, and an explicit named rejection for each option NOT implemented — with a test that the rejection is a compile error rather than silence.

2026-08-12 — the argument list PARSES; non-default values refused by name

Took the ticket's own "useful first step": parse the list, honour what is implementable, refuse the rest explicitly. PyParseDataclassArgs (pyparser.inc) replaces the two copies of if tkLParen then begin Next; Expect(tkRParen) end.

What that buys, given every option has a documented CPython default and passing an option its own default changes nothing:

Deliberately still refused rather than ignored — the ticket's own reasoning: accepting frozen=True would hand back a class that looks immutable and is not. Generating the comparison dunders for order=True is the remaining work and this ticket stays open for it.

The second site — a pre-pass that disagreed

PyRegisterClassFieldsPrepass finds @dataclass by scanning BACK from the class header and requiring the name to sit immediately before it. With an argument list the previous token is ), so the pre-pass would have said "not a dataclass" while PyParseClass said it was — the recorded two-passes-disagree hazard, and it would have shown up as unregistered fields, not as an error. The scan now steps over a balanced paren group. Found by looking for the sibling site before closing, per normalise-dont-special-case; the Later class in the new test is the row that covers it.

Gate

make compiler/pascal26 (fixedpoint, 1 round) + tools/gate.sh quick GREEN. Family sweep rather than a wider gate: the four existing dataclass tests re-run and unchanged (test_nilpy_dataclass has no from dataclasses import line so CPython cannot run it — its output is byte-identical to the pinned binary's).

New tests, expectations from CPython: test_nilpy_dataclass_decorator_args.npy + .expected, and three compile-error tests (_order_fail, _frozen_fail, _unknown_option_fail), all in the test-nilpy target.

Note for whoever adds order=True: an Optional[int] = None field prints as o=0 rather than o=None, on the pinned binary and the bare decorator alike — that is [[bug-nilpy-a-def-returned-none-loses-its-none-ness-in-a-variant-slot]]'s int arm, not this ticket. It was in a draft of the new test and was removed for that reason.

Resolution (2026-08-15) — order=True generates the comparisons; ticket closed

The remaining work the 2026-08-12 pass named. @dataclass(order=True) now generates __lt__, __le__, __gt__ and __ge__ over the FIELD TUPLE in declaration order, which is what CPython's decorator documents.

The bodies are tuple ordering written as one expression, built from the last field backwards:

acc := <False for lt/gt, True for le/ge>
for i := last downto first:  acc := (a_i OP b_i) or ((a_i = b_i) and acc)

That is the same accumulator shape PyEmitDataclassEq next door uses, and it gives the property that matters: the first differing field decides, and later fields only matter while the earlier ones are equal. Verified against CPython on a two-field class where both orders of divergence are exercised.

The flag is recorded by the PRE-PASS, not by PyParseDataclassArgs. That routine runs on the cursor before the class is reached and has no ci to record against, while the pre-pass sees every class — and BOTH passes reach PyRegisterClassMembers, which is where the four methods are registered. Same two-passes-must-agree hazard the 2026-08-12 pass hit with the backwards scan, avoided the same way: one place decides.

PyDcCmpName is one function asked by both the registration and the body emitter, so the two cannot disagree about which of the four slots is which.

The refusal test is retired WITH the feature. test_nilpy_dataclass_order_ fail.npy asserted the compile error this ticket just removed; left in place it would have compiled and turned the nilpy suite red in a way gate.sh quick cannot see. Deleted, its Makefile recipe replaced with the real test, and the file's own header says what replaced it. frozen=True and the unknown-option refusals are untouched and still asserted.

One divergence, in the permitted direction: CPython REFUSES a hand-written __lt__ under order=True (TypeError: Cannot overwrite attribute __lt__); NilPy accepts it and lets the hand-written one win, as it already does for __eq__/__repr__. No program CPython runs can observe that, so it is recorded in devdocs/dev/nilpy-semantics-divergences.md rather than refused.

Gate: test/test_nilpy_dataclass_order.npy (+.expected, in the Makefile) — single- and two-field ordering, all four operators, sorted/min/max, a mixed int/str class with a default, and order=False still being the bare decorator. Byte-identical to CPython. The five sibling dataclass tests re-run unchanged. tools/gate.sh quick GREEN, self-host byte-identical.

Log