Skip to content

feat(vars): add SetVar for set and frozenset vars - #7642

Open
FarhanAliRaza wants to merge 9 commits into
mainfrom
farhan/friendly-hypatia-9dyews
Open

FarhanAliRaza wants to merge 9 commits into
mainfrom
farhan/friendly-hypatia-9dyews

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7623

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Changes To Core Features

What changed

A set-typed state var or literal was an ArrayVar, so it exposed sequence operations a set does not have, and frozenset was rejected by LiteralVar.create. #7622 widened ArrayVar instead and was reverted in #7632.

This adds SetVar / LiteralSetVar in reflex_base/vars/set.py for set[T] and frozenset[T], following the DateTimeVar pattern: a typed var over an untyped wire value.

  • Wire format stays a JSON array. serialize_set is unchanged (and frozenset gains the same serializer, so it is now a valid state var type) and nothing in applyDelta changes. An array also round-trips through event arguments, which a real JS Set would not (JSON.stringify(new Set(["a"])) is "{}"). The issue's "rehydrate as Set on the frontend" prerequisite is dropped for that reason.
  • Operations compile to small pySet* helpers in state.js (next to pyFlatMap) that key elements by value, so a set[tuple[int, int]] gets Python answers rather than array-identity comparison: contains/has, length/size, union, intersection, difference, symmetric_difference, issubset, issuperset, isdisjoint, and to_array() which is the JS identity typed as Sequence[T] (optional sets stay optional). Set-returning operations keep frozenset-ness and carry the element type through.
  • Routing. set is removed from ArrayVar.python_types; figure_out_type, Var.to, var_operation, Field.__get__ and ComputedVar.__get__ route set/frozenset to SetVar, so State.tags.contains("x") is a BooleanVar statically with no cast. rx.foreach accepts a SetVar by calling to_array().
  • Deprecation path. Indexing, map, filter, join, reverse, pluck, reduce, flat_map, + (either side), * (either side, including NumberVar * SetVar), the comparisons <, <=, >, >= and contains with a field still work on a set var for one release with a console.deprecate pointing at .to_array(), and are marked @deprecated for type checkers.
  • ==, | and & keep their generic var meaning (reference equality, logical or/and); the docs say so and point at the named methods.
  • A set literal renders its elements sorted when they compare, so compiled output no longer depends on the process hash seed.
  • LiteralArrayVar's element var-data and JSON helpers are extracted so LiteralSetVar shares them.

Tests

  • tests/units/reflex_base/vars/test_set.py: literal creation and static types (typing.assert_type under pyright), the rendered JS and helper import of every operation, tuple-element sets, operand validation, to_array (including optional sets), the deprecated fallbacks and concatenation in both directions, state fields (set, frozenset, optional) and computed vars, rx.foreach, VarData propagation.
  • tests/integration/tests_playwright/test_set_operations.py: backend add/discard re-render every operation and a foreach; a frozenset field; a set of tuples compared by value; an unset optional set renders no children; a set is sent back through an event argument and received intact (as a list, which is existing behavior for event args).
  • The helper JS was checked in Node against the same cases before being wired in.
  • uv run ruff check ., uv run ruff format ., uv run pyright reflex tests packages/reflex-base/src/reflex_base/vars packages/reflex-components-core/src are clean. The unit suite passes apart from tests/units/reflex_cli/v2, which fails identically on main in this environment (the hosting CLI rejects the 0.0.1.dev version string).

Docs and example apps checked for breakage

  • docs/enterprise/mantine/pill.md uses tags: set[str] with rx.foreach only: still works through to_array().
  • docs/guides/ai_applications.md uses frozenset in a backend dataclass only: unaffected.
  • No docs page indexes or maps a set var, so nothing hits the deprecation.
  • docs/vars/var-operations.md gains a "Set Operations" section and docs/api-reference/var_system.md lists SetVar.

Out of scope, as the issue says

  • Whether a set var satisfies a Sequence[...]-typed prop (typehint_issubclass) is unchanged.

https://claude.ai/code/session_01FTaqSdpd95XTrwgxAHExRd

A set-typed state var or literal was an ArrayVar, so it exposed sequence
operations (indexing, map, slicing) that a set does not have, and a
frozenset was rejected by LiteralVar.create outright.

Add SetVar and LiteralSetVar for set[T] and frozenset[T]. The wire value
stays the JSON array that serialize_set already produces, which also
round-trips through event arguments, so no frontend rehydration is
needed: each operation compiles to JavaScript over that array, the way
DateTimeVar compiles over a string. contains, length, union,
intersection, difference, symmetric_difference, issubset, issuperset and
isdisjoint return SetVar or BooleanVar with the element type carried
through; to_array is the JS identity typed as a Sequence, and rx.foreach
calls it.

Remove set from ArrayVar's python types and route set and frozenset to
SetVar in figure_out_type, Var.to, var_operation and the Field and
ComputedVar overloads. The sequence operations still work on a set var
for one release with a deprecation warning pointing at to_array().

A set literal renders its elements sorted when they compare, so the
compiled output no longer depends on the process hash seed.

Closes #7623

Claude-Session: https://claude.ai/code/session_01FTaqSdpd95XTrwgxAHExRd
@FarhanAliRaza
FarhanAliRaza requested review from a team and Alek99 as code owners October 22, 2026 20:09
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T20:12:33.440142Z 4fc60c7 PR opened
ℹ️ 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: 4fc60c7bda

ℹ️ 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".

@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; both numbered previous findings are fixed.

Summary

Adds SetVar and LiteralSetVar for set and frozenset, while keeping their frontend values as JSON arrays.

  • Set variables now support set membership, combinations, and comparisons.
  • Set and frozenset values keep their types in literals and state variables.
  • Older array-style calls on set variables now warn callers to use to_array().
  • rx.foreach now iterates set variables through their array view.

Reviews (7) · Last reviewed commit: "fix(vars): key booleans as numbers and r..." · Reviewed by Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 14 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread docs/api-reference/var_system.md Resolved
Comment thread docs/vars/var-operations.md Resolved
@codspeed

codspeed Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 10 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 148 untouched benchmarks


Comparing farhan/friendly-hypatia-9dyews (686a928) with main (58d798f)

Open in CodSpeed

Address review findings on the SetVar change:

- Move the set operations into pySet* helpers in state.js that key
  elements by value, so a set of tuples (nested arrays on the frontend)
  gets Python answers from contains, union, intersection and the rest
  instead of array-identity comparison.
- Register a serializer for frozenset, so a frozenset state field or
  computed var is valid and reaches the frontend as an array.
- Keep an optional set optional in to_array(), so rx.foreach still
  applies its null guard.
- Let the deprecated `set + set` and `array + set` fallbacks compile by
  viewing set operands as arrays.
- Set the deprecation version to 0.10.1, the next release after v0.10.0.
- Check VarData propagation through a set literal in the tests.

Claude-Session: https://claude.ai/code/session_01FTaqSdpd95XTrwgxAHExRd
symmetric_difference with a plain array operand kept that array's
duplicates; build it as a union so the result is a set. Key strings and
objects with different prefixes in pySetKey, so a string can never share
a key with a tuple's JSON.

Claude-Session: https://claude.ai/code/session_01FTaqSdpd95XTrwgxAHExRd
@masenf masenf added the breaking Public API facing change considered for minor release bump pre-v1.0 label Oct 22, 2026
@masenf

masenf commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

marking as a [breaking] change, because before you could index into set-typed state vars in the frontend code. not sure if that was ever "correct", but it's a behavior change we need to expose prominently

Copy link
Copy Markdown
Contributor Author

Agreed. 7374337 moves the changelog fragment for this to the breaking type (in reflex-base and mirrored in the root reflex news), stating that a set var is no longer a sequence in frontend code and that indexing, map, join, + and the rest still compile with a deprecation warning until 1.0, with .to_array() as the replacement.


Generated by Claude Code

@masenf masenf added the documentation Improvements or additions to documentation label Oct 27, 2026
@FarhanAliRaza FarhanAliRaza added the on deck PRs lined up to review / merge next label Oct 30, 2026
…set var deprecation path

Comparisons (<, <=, >, >=) and contains with a field compiled on set vars
before SetVar; they now delegate to to_array() with a deprecation warning
instead of raising. NumberVar * SetVar routes to the set's deprecated
multiplication like ArrayVar does.

Also document that ==, | and & keep their generic var meaning on set vars,
reuse SET_TYPE in the var_operation overload, and drop the _element_type
pass-through.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files (changes from recent commits).

Tip: Get a Slack DM when cubic finishes reviewing your PRs. Connect Slack

View guided diff | Turn on auto-fix | Re-trigger cubic

…etVar

reflex-base 0.10.1 was released after this branch was opened, so the
`>= 0.10.1.dev0` floor was met by a published reflex-base without
SetVar and check-min-deps failed on foreach.py. Floor at 0.10.2.dev0,
the dev release of the version after that tag, as the root package
already does.

Also admit SetVar in the NumberVar.__mul__ and __rmul__ overloads, which
the runtime path already accepts.

Claude-Session: https://claude.ai/code/session_01FTaqSdpd95XTrwgxAHExRd
…sons

pySetKey now keys a boolean as the number it equals in Python, so
{1} | {True} has one element and contains(False) finds 0, as a Python
set would. ArrayVar comparisons with a SetVar on the right reflect onto
the set's deprecated comparison path, so `list_var < set_var` keeps
compiling with the deprecation warning, as `list_var + set_var` does.

Claude-Session: https://claude.ai/code/session_01FTaqSdpd95XTrwgxAHExRd

FarhanAliRaza commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

CI note on 686a928: unit-tests (ubuntu-latest, 3.11) failed on tests/units/test_state.py::test_state_manager_lock_expire (assert loop_exception is not None) in its second, Redis-backed pytest pass, after the first pass of the same job reported 15327 passed. That test checks Redis lock-expiry timing through an asyncio exception handler and lives in state-manager code this PR does not touch, so I treated it as a timing flake. I couldn't re-run it from here (403); it has since been re-run and passed, so every leg and gate on this head is green.


Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Public API facing change considered for minor release bump pre-v1.0 documentation Improvements or additions to documentation on deck PRs lined up to review / merge next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a SetVar backed by the JS Set object

2 participants