lowering: Option/Result matches as isinstance ladders, slotted classes, nullary singletons (#87, #89) - #94
Merged
Conversation
simontreanor
added a commit
that referenced
this pull request
Aug 30, 2026
…after #90, #91 and #93 Resolves the overlap between the lookup peephole (#93) and the Option/Result ladder (#94): both hooks run in the match lowering, lookup first since it needs no Option at all; the two new list helpers return the _None_ singleton; tests whose scrutinee both passes rewrote now expect the combined output.
Owner
Author
|
Merge order: #90, #91, #93 first, then this one. The last commit here merges those three branches and resolves the overlap with #93 (both match-lowering hooks kept, lookup peephole first; the two new list helpers return the |
simontreanor
force-pushed
the
perf/option-nullary-representation
branch
from
August 30, 2026 09:03
70662a1 to
bf4a8e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three changes to the emitted Python's representation of the built-in two-case types and field-less constructors, from the Scrabble move generator findings in #87 and #89. Nothing about the language changes; every change is a property of the output.
DESIGN.md§5.5 is the contract,INTERNALS.mdthe mechanics.What changed
Option/Resultmatches lower to anisinstanceladder. Amatchwhose arms areSome p/None(orOk p/Error e) with irrefutable payload patterns, optionally ending in a catch-all, now emits what a person writes:The last arm of an exhaustive match is a plain
elseand the defensivecase _: raiseis gone (the ladder's exhaustiveness rule, both constructors unguarded or an unguarded catch-all, is the checker's rule for this shape). Guarded arms in return position become their ownifand fall through; a guarded match in value position, and any refutable payload pattern (case Some 0:), keep thematchlowering.result {}/option {}use the same ladder perlet!/do!and forward a failure as the value in hand (return rinstead of rebuildingError(e)or callingNone_()). User ADTs still emitmatch/case.Every emitted dataclass is
slots=True(variants, records,Some/None_/Ok/Error/_Exception). Instances lose__dict__, which is the one visible interop change (vars(p)no longer works;dataclasses.asdict/replace,==, hashing and<are unchanged and covered by e2e tests).Nullary constructors are singletons. Each field-less variant and the prelude's
Noneis built once right after its class (_Across = Across(),_None_ = None_()) and every use as a value loads that name; the prelude helpers return_None_on a miss. Patterns still test the class (case Across():,isinstance(o, None_)), so a hand-built instance from Python matches and compares equal as before. If the program binds the singleton's name itself (let _Across = …), that constructor keeps the call form. Cross-module references load the exporting module's singleton (palette._Red), decided by the same rule on both sides; project modules import_None_from_pyfun_rtalongsideSome/None_.Measurements
Micro-benchmark of the issue's shapes (an
Optionmatch onMap.tryFindand onList.findIndex, a nullarymatchand==, one million iterations; emitted bymainvs this branch, CPython 3.14.6, median of 5, identical output): 2.269s → 1.756s (23% faster).bench/run.py(median of 5; these workloads have noOptionmatches on their hot path, so this is a regression check forslots=Trueand the singletons rather than a demonstration):Outputs verified identical to the baselines. expr_eval's small gain is the slotted ADT classes.
Tests
New snapshot tests in
tests/compile.rsfor every ladder shape (return and value position, guards, tuple payload,Result, catch-all variable, the refutable and guarded-value-position fallbacks, a user ADT still emittingmatch), the singleton (value reference, the name-collision fallback for a user constructor and forNone),slots=Trueon every class, plus e2e runs covering all of them and record update/compare/hash under slots.tests/project.rscovers cross-module singletons (load, collision fallback, and an e2e run through_pyfun_rt).docs/verify_lessons.pypasses; the lesson and internals pages that show emitted Python are updated.Decisions the issues left open: hidden active-pattern case classes are not given singletons (built once per recognizer call and matched on the spot); the ladder does not test with
isagainst the singleton, so Python-side constructed values keep matching.Closes #87
Closes #89