Skip to content

react-compiler: intern the nested types of ValidateNoRefAccessInRender - #43559

Open
robobun wants to merge 5 commits into
mainfrom
robobun/f3743722/react-compiler-ref-access-types
Open

robobun wants to merge 5 commits into
mainfrom
robobun/f3743722/react-compiler-ref-access-types

Conversation

@robobun

@robobun robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun build --react-compiler takes 6 s for a 12 line component and 94 s for a 30 line one. The time is in join_ref_access_types and the derived clone of RefAccessType (src/react_compiler/validation/validate_no_ref_access_in_render.rs).
  • The trigger: a closure returns a let local, and the local is later assigned a value that holds the closure. Its type then holds its own previous type several times.
  • The pass nests types by value (Box). The type multiplies on each of the 10 passes, and every join clones it.

Fix

  • Nested types are entries of a table, RefAccessTypeInterner (an ArrayHashMap), one per distinct type. The repro makes 30 entries.
  • A join of two entries is computed once, unless it makes a new ref id.
  • The result does not change. Old and new code ran side by side on 2,781 fixture functions and 387 generated components: same errors and types. Release output is byte-identical on 1,807 fixture inputs and 4,586 generated components.
  • Verified: two new tests in test/bundler/transpiler/react-compiler.test.ts (one times out on 1.4.3), that file, and react-compiler-fixtures.test.ts. Self-reviewed: 9 concerns raised, 8 addressed (Notes).

Background

  • The pass rejects a component that reads ref.current during render. It runs at most 10 passes over a map from identifier to RefAccessType. If the map does not settle, the component stays as written, as upstream.
  • A Structure type describes an object or a function: value joins what the object holds, fn_type.return_type is what the function returns.
  • A join of two different refs makes a new ref id. Ids decide which if (ref.current == null) guard allows which write, so such a join is not reused.
Notes

Repro (from the report, 6 s on 1.4.3, bun build slow.jsx --react-compiler --target=browser --external '*'):

import { useState, useCallback } from "react";
import { mutate } from "lib";
export function Comp(props) {
  const [st] = useState(props.a);
  let v0 = [st, st];
  const v1 = useCallback(() => { return v0; }, [st]);
  const v2 = { k0: v1, k3: v0 };
  const { v3, ...v4 } = v0;
  const v5 = v3 || st;
  if (st) { v0 = st ? v5 : v5; }
  if (props.b) { v0 = [...v2, v1]; }
  const { v11, ...v12 } = v0;
  mutate(v0, v12);
  return v5;
}

Growth. Nodes in the largest type after each pass, counted in Babel, which has the same types: 7, 27, 91, 297, 959, 3,087, 9,927, 31,913, 102,583, 329,739, and 3.5 million nodes over the whole map. Structurally distinct nodes in the whole map after the same passes: 5, 10, 13, 16, 19, 22, 25, 28, 31, 34.

Timings (release builds, linux x64, CPU time of one bun build, peak RSS in brackets):

input main this PR Babel 1.0.0
the repro above 5.6 s (174 MB) 0.02 s (25 MB) stops earlier (see Upstream)
the repro, useCallback removed 5.1 s 0.01 s 1.1 s
30 line component of the report 94 s (1.2 GB) 0.02 s (25 MB) 17.7 s (hooks removed)
Comp of the new test no result in 200 s (5.8 GB then) 0.01 s (25 MB) out of memory after 38 s
worst generated component no result in 25 min (30 GB then) 0.02 s (32 MB)
const a1 = [ref]; const a2 = [a1]; ..., 1,000 statements, last one used 14.9 s 0.09 s
600 ordinary components, 921 KB, all memoized, median of 31 runs 3.82 s (1,296 MB) 3.71 s (1,292 MB)

The nested array row is the cubic case that the Notes of #43230 describe. The last row is the common case. The difference there is inside the noise of the machine (minimum 2.71 s against 2.44 s). The table allocates nothing until a function or an object with ref content is seen.

Scaling. The cycle of the new test with k objects in it, this PR: k = 2, 4, 8, 16, 32, 64, 128 take 21, 16, 19, 21, 23, 36, 97 ms. Main gives no result in 300 s for k = 2 (7.5 GB then).

Upstream. Before this PR the file was the same as compiler/crates/react_compiler_validation/src/validate_no_ref_access_in_render.rs at the ported commit, except for imports. TypeScript upstream shares nested types by reference, but joinRefAccessTypes and tyEqual still walk them in full, so Babel grows the same way: 1.1 s and 17.7 s above, and JavaScript heap out of memory on Comp of the new test. Babel 1.0.0 does not reach this pass on the first repro, because it throws This value cannot be modified (a hook argument is reassigned) before it. The port records that error and continues, as newer upstream does. With useCallback removed, both report Ref type environment did not converge.

Why the result is the same.

  • An entry is equal to another entry only if every field is equal, ref ids and locations included. So two places that hold the same index held equal trees before.
  • join(a, a) is a in the old code too: each rule returns its left operand when the ids are equal. That is the a == b shortcut.
  • A join calls next_ref_id() only for two refs with different ids, or for a ref and a structure. Every other join is a function of its two operands, so the table can return the earlier result. A join that made an id is computed again each time and makes new ids each time, as before. So the sequence of next_ref_id() calls is the same as before.
  • tyEqual ignores the id of a Ref and of a RefValue. Each entry stores the first entry that is tyEqual to it, found through a key in which nested indexes are replaced by those first entries. Two types are tyEqual if and only if their keys are equal.
  • The diff keeps the upstream text where it can (.clone() on the now Copy types, the two join functions, RefAccessRefType). clippy::clone_on_copy is allowed for this module.

How I checked it.

  • A temporary build ran the old file next to the new one on every function. It compared the errors (Debug output), the return type, the changed flag, and the type of every identifier with ref ids renamed in order of first use. 2,781 functions of react-compiler-fixtures.test.ts (52 with ref errors), 25 of react-compiler.test.ts and 387 of generated components: no difference. The old side is too slow for 12 more generated components (over 120 s each on a debug build).
  • Release builds of main and of this PR give byte-identical output on the 1,807 fixture inputs, on 22 hand-written guard patterns (merged refs, refs in objects and closures, loops, 11 compile and 11 do not), and on 4,556 of 4,600 generated components (one component per file with refs, guards, closures, reassigned locals and the cycle above, 986 memoized). On the other 44 main needs more than 30 s. With a longer limit 30 of them give identical output. For 14 main gave no result within 15 to 25 minutes, or the 32 GB of the machine.

Tests.

  • react-compiler ref validation does not multiply the type of a local that holds a closure over itself: on 1.4.3 the build needs more than 200 s, so the test fails by its timeout (or by the 30 s kill switch of the spawn when the test timeout is longer). With this PR it takes 0.3 s on a debug build.
  • react-compiler/GuardOnARefFromAJoinOfTwoRefs passes on main and with this PR. It covers the rule that a join that makes a ref id is not reused: on a build that reuses every join, TwoRefs compiles and the test fails.

Not fixed here. A join that makes a ref id is not reused, because the old code gives each such join its own ids, and a later guard can tell them apart (the second test). A component that has the cycle above and also merges two different refs inside it still grows, but each step is cheaper than before:

input main this PR
cycle + const w = () => () => [props.c ? r1 : r2] in one object 1.5 s (99 MB) 0.10 s (57 MB)
the same, w one object further from the local 42 s (1,294 MB) 1.7 s (438 MB)
the same with a second reassignment no result in 8 min (14 GB then) 29 s (6.3 GB)
const a0 = [c ? r1 : r2]; const a1 = [a0]; ..., 800 statements (compiles) 6.2 s (80 MB) 0.12 s (77 MB)

None of the 4,600 generated components is in this group. I did not add a budget that ends the pass early. A budget also stops inputs that settle after large joins (the last row compiles on main and here), so it changes results, and the repo rule is to not cap a count to hide growth. The growth comes from the rule that each such join makes new ids, which upstream has too. A table of such joins that is cleared at each pass would make a pass cheap. But two joins of one pair would then share their new ids inside a pass, which main does not do: the errors of a rejected component can differ, and the side by side check no longer holds. That can be a follow-up.

The table. bun_collections::ArrayHashMap<RefAccessType, RefAccessTypeId> keeps its keys in insertion order. The index of a key is the id of the type, and the value is the first tyEqual entry. It uses the global allocator, not the arena of the crate's IndexMap, because an arena keeps every outgrown buffer of a table that grows. An entry costs about 65 bytes. A first version with a Vec plus a HashMap cost about 150 bytes and took more memory than main on the first and last row.

Self-review. Three reviewers read the diff for a change of result, for the rules of DESIGN.md and REVIEW.md, and for the tests. None found a change of result. Raised and addressed: the diff rewrote upstream text without need (.clone(), a loop in destructure, a derive), now restored. RefAccessTypeInterner::next_ref_id had no Not in upstream marker, and a re-port that calls the free next_ref_id() there would silently reuse joins that make ids. No test covered that rule (second test added, checked against a build without the rule). The first test had no kill switch and did not assert signalCode. The random components had no guard on a merged or extracted ref and no while loop (22 hand-written inputs added to the comparison). Numbers for the common case and for inputs that defeat the reuse were missing (tables above). Names and comments (ref_ids_made, ty_equal_first, the test name). Raised and not addressed: report the growth to upstream, whose Rust port and TypeScript have it too.

Related. #42807 ends the pass when a nested fixpoint does not converge, and leaves out the growth inside the 10 passes of the top level. This PR is that case. The two PRs change adjacent hunks of the Rust file and both append tests to the same test file, so the second one to land needs a rebase. #43230 adds a stack check to the first lines of two functions of the file.


[human-review] gate passed · iteration 2 · 2 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (367d939d9)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [824.92ms]
(pass) bundler > react-compiler/ComponentWithHooks [332.42ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [308.67ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [344.01ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [172.70ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [119.11ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [385.16ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [137.69ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [789.76ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [113.13ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [122.74ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [333.84ms]
(pass) bundler > reac
... (truncated)

release without fix: 1 skipped
bun test v1.4.3-canary.1 (f5c2a6906)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [49.44ms]
(pass) bundler > react-compiler/ComponentWithHooks [9.04ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [9.49ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [9.48ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [7.94ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [3.96ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [14.80ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [4.81ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [15.19ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [4.23ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [3.72ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [10.06ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [15.52ms]
(pass) bundler > react-compiler/RequireStringPreservesImportRecord [9.85ms]
(pass) bundler > react-compiler/BranchBooleanFea
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (367d939d9)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [1063.14ms]
(pass) bundler > react-compiler/ComponentWithHooks [353.64ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [330.67ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [336.41ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [207.04ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [156.61ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [457.48ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [152.63ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [895.95ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [141.28ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [138.97ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [383.88ms]
(pass) bundler > rea
... (truncated)

release with fix: 1 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 966ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/7] cargo bun_runtime → libbun_runtime.a
�[1m�[33mwarning�[0m�[1m: binary `bun_shim_impl` should have a kebab-case name�[0m
   �[1m�[94m|�[0m
�[1m�[94m 1�[0m �[1m�[94m|�[0m /workspace/bun/build/release/rust-target/.../bun_shim_impl
   �[1m�[94m|�[0m                                              �[1m�[33m^^^^^^^^^^^^^�[0m
   �[1m�[94m|�[0m
   �[1m�[94m= �[0m�[1mnote�[0m: `cargo::non_kebab_case_bins` is set to `warn` by default
�[1m�[96mhelp�[0m: to change the binary name to `bun-shim-impl`, convert `bin.name`
  �[1m�[94m--> �[0msrc/install/windows-shim/Cargo.toml:41:8
   �[1m�[94m|�[0m
�[1m�[94m41�[0m �[91m- �[0mname = �[91m"bun_shim_impl"�[0m
�[1m�[94m41�[0m �[92m+ �[0mname = �[92m"bun-shim-impl"�[0m
   �[1m�[94m|�[0m
�[1m�[33mwarning�[0m: `bun_shim_impl` (manifest) generated 1 warning
�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   C
... (truncated)
diff hotspot
.../validation/validate_no_ref_access_in_render.rs | 274 ++++++++++++---------
 test/bundler/transpiler/react-compiler.test.ts     | 116 +++++++++
 2 files changed, 276 insertions(+), 114 deletions(-)

gate history · 2 passed · 0 rejected · iteration 2

evidence per changed file
file                                                      reads  edits  tests
…compiler/validation/validate_no_ref_access_in_render.rs      3      4     25
test/bundler/transpiler/react-compiler.test.ts                0      0     25

The pass nested its types by value. When a closure returns a local, and the
local is later assigned a value that holds the closure, the type of the local
holds its last type several times. The type multiplied on each of the 10
passes of the fixpoint and each join cloned it.

The nested types are now entries of a table with one entry per distinct type.
A join of two entries that makes no ref id is computed once. The result of
the pass is the same.
@robobun

robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 6:20 PM PT - Sep 19th, 2026

✅ @robobun, your commit 778aaa124b219b5f1e6ad882682b5e18ba4ef00c passed in Build #118678! 🎉


🧪   To try this PR locally:

bunx bun-pr 43559

That installs a local version of the PR into your bun-43559 executable, so you can run:

bun-43559 --bun

@robobun

robobun commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

How I reproduced it:

  1. Save the 12 line component from the Notes of the PR body as slow.jsx.
  2. Run bun build slow.jsx --react-compiler --target=browser --external '*'.
  3. On 1.4.3 and on main the build takes about 6 s of CPU time. A plain build of the same file takes 10 ms. With this PR the build takes about 20 ms, and the output is the same.

The new test react-compiler ref validation does not multiply the type of a local that holds a closure over itself fails on a build of main (more than 200 s for its component) and passes with this PR.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: oven-sh/bun/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 38c70da5-33ff-49ef-8de5-aa4870b63203

📥 Commits

Reviewing files that changed from the base of the PR and between 47aa413 and 2c088f9.

📒 Files selected for processing (1)
  • src/react_compiler/validation/validate_no_ref_access_in_render.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.


Walkthrough

The validator now uses interned ref-access type IDs for joins, storage, propagation, and validation. Two bundler tests cover ref identity joins and recursive closure type propagation.

Changes

Ref access validation

Layer / File(s) Summary
Interned type model
src/react_compiler/validation/validate_no_ref_access_in_render.rs
Ref-access types now use interned IDs, canonical storage, structural equality keys, generated reference IDs, and cached recursive joins.
Environment and validation integration
src/react_compiler/validation/validate_no_ref_access_in_render.rs
The environment stores the interner. Widening, destructuring, property access, calls, aggregates, assignments, and returns use interned types.
Regression coverage
test/bundler/transpiler/react-compiler.test.ts
Tests cover equivalent and distinct ref joins and recursive closure/object type propagation.

Suggested reviewers: jarred-sumner

Priority: ⬆️ High

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: interning nested types in ValidateNoRefAccessInRender.
Description check ✅ Passed The description explains the problem, fix, performance impact, compatibility validation, and tests. It does not use the template headings exactly, but it provides the required information in equivalen…

Comment @coderabbitai help to get the list of available commands.

@claude claude 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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I also checked the memo's equivalence to the old by-value joins: join(a, a) is an identity in every arm of join_ref_access_types/join_ref_access_ref_types (so the a == b shortcut is sound); the only next_ref_id calls reachable from inside a join go through interner.next_ref_id, so ref_ids_made cannot miss a fresh id; Structure.value is only ever interned as Ref/RefValue/Structure (the None/Guard/Nullable cases set RefAccessType::None instead), so interner.join on two values falls through to join_ref_access_ref_types as before; and ty_equal_key maps tyEqual nested entries to the same ty_equal_ids slot, so Env::set's changed-detection matches the removed custom PartialEq.

Extended reasoning...

The single posted finding is a pre-existing, partly-fixed performance bound (joins that mint a ref id are not memoized, so cycle + merged-refs inputs still grow), which the PR description already acknowledges. To add something concrete rather than restate it, I traced the four properties the correctness of the interner depends on: the self-join shortcut, the ref-id counter covering every id-creating site inside a join, the invariant that Structure.value entries are always ref-shaped so the nested interner.join reduces to the old join_ref_access_ref_types, and the ty_equal_key induction that replaces the old tyEqual PartialEq. None of these showed a behavioral divergence from the base. The change is a non-trivial rework of a fixpoint pass with a derived equality replacing hand-written tyEqual semantics, so a human look remains worthwhile, but I found nothing beyond the inline comment.

Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs
The table held each type twice: in a Vec, and as the key of a HashMap.
An ArrayHashMap keeps its keys in insertion order, so the index of a key
is the id of the type, and its value is the first tyEqual entry.

An input that defeats the reuse of joins (a join that makes a ref id)
now takes 57 MB. It took 111 MB before this commit and 99 MB on main.
Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs Outdated
Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs Outdated
Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs Outdated
Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs Outdated
Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs Outdated

@claude claude 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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I checked the core soundness questions of the interner on this push and found nothing wrong: the joins memo is only written when ref_ids_made did not move, so an id-making join (two different Refs, or Ref vs structure, at any nesting depth) is recomputed and makes fresh ids each pass exactly as before; Structure.value: Some(intern(value)) is equivalent to the old value.to_ref_type().map(Box::new) because the None | Guard | Nullable arm above already excludes the only cases where to_ref_type returns None; and the keys()[id] / values()[id] indexing in get and ty_equal_key cannot go out of range because every nested id is interned before the type that holds it. The timeout: 30_000 on the new spawn test was also examined and left alone since it is a hang guard paired with a signalCode assertion, not a wait.

Extended reasoning...

This run's inline finding is a naming/consistency nit (raw ArrayHashMap instead of the crate's IndexMap wrapper), which is not a reason to hold the PR on its own. The change itself is a non-trivial rewrite of a fixpoint analysis in the React Compiler port (type interning plus join memoization with a ref-id-creation escape hatch), so I did not approve; a human should still read the memo/ty_equal semantics against upstream. The points listed in the body are the concrete concerns I traced through the code and ruled out: memo write gating on ref_ids_made, the Structure.value equivalence given the enclosing match arms, the interner index invariant, and the spawn timeout that the ruled-out list already covered.

Still open from earlier reviews (1):

  • Unresolved: 1 minor or pre-existing.

Comment thread src/react_compiler/validation/validate_no_ref_access_in_render.rs

@claude claude 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.

Code review found no issues

No high-confidence issues detected in this change.

Still open from earlier reviews (2):

  • Unresolved: 2 minor or pre-existing.

@claude claude 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.

Code review found no issues

No high-confidence issues detected in this change.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant