← board

frozenset(...)

What is nearly free

A NilPy set is a TPyList stamped PYSEQ_SET (PyMarkAsSet), and every set form — set(...), a {a, b} literal, a set comprehension — goes through that one stamp. frozenset(xs) produces the same value, so recognising the name beside set in PyIsSetCall and friends is the bulk of it.

Immutability is NOT the hard part. A NilPy frozenset that can be mutated is the dialect being laxer than CPython, which is a feature and not a defect by Track N's own upward-compatibility rule (devdocs/dev/nilpy-semantics-divergences.md): no program that CPython accepts and runs can observe it, because CPython would have raised.

What the ticket must actually decide

frozenset is visible in two places, and getting these wrong IS a defect because ordinary working code observes them:

So it wants a THIRD sequence kind (PYSEQ_FROZENSET) beside list/tuple/set rather than reusing the set stamp, which is the same call [[bug-nilpy-list-tuple-and-set-are-indistinguishable-to-isinstance]] already made once for set-vs-list. That is a pylib FKind value plus its arms in the renderer, isinstance, and type().__name__ — the three-rendering-paths and subscript-protocol matrices in this repo's NilPy notes are the checklist.

Gate

A .npy diffed against CPython: construction from a list/tuple/generator, in, len, iteration, equality with a set, repr, type(x).__name__, isinstance(x, frozenset) and isinstance(x, set) (CPython: a frozenset is NOT a set instance), plus html5lib/constants.py getting past line 305.

DONE 2026-08-13

frozenset(...) constructs, prints, compares and answers isinstance — every row of this ticket's gate matching CPython: construction from a list, a tuple, a string and a generator expression, in, len, iteration, equality with a set, repr, type(x).__name__, isinstance(x, frozenset), isinstance(x, set) (False, as CPython has it) and the empty frozenset().

Built exactly as the ticket said to: a THIRD sequence kind, PYSEQ_FROZENSET, rather than reusing the set stamp. The value side is the shared path — one parse, one pyset_of, then a different stamp on the way out — and the three places the kind is VISIBLE each grew an arm:

Equality is the row where kind identity is WRONG, and it was the one worth thinking about: CPython's frozenset({1, 2}) == {1, 2} is True — frozenset is a different TYPE, not a different value — while both stay unequal to a list. So pylist_eq's guard (added hours earlier for set-vs-sequence) tests set-LIKENESS, not kind equality.

Immutability is not enforced, per the ticket's own reasoning: a mutable frozenset is the dialect being laxer than CPython, which no program CPython accepts and runs can observe.

PyMarkAsSet was generalised to PyMarkAsSeqKind(node, marker) with the two stamps as thin wrappers — one builder, so a fourth kind is a line rather than a copy.

Test test/test_nilpy_frozenset.{npy,expected} (.expected from CPython), wired into test-nilpy; the four existing set tests re-run unchanged. Gate: self-host fixedpoint + gate.sh quick GREEN.

Not verified here: whether html5lib/constants.py now gets past line 305 — that belongs to [[feature-nilpy-thirdparty-libraries-as-targets]], which owns the corpus run.

Log