Skip to content

fix(native): cut circular variable resolution instead of blowing the stack - #422

Open
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/variable-resolution-cycle
Open

YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/variable-resolution-cycle

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

#451 cuts a custom-property cycle at runtime and in the compile-time inliner, which is what this PR fixed when I opened it. The code change is gone. What stays is coverage main does not have, because its cycle tests declare every cycle on one element:

  • src/__tests__/native/variable-cycles.test.tsx:
    • A variable reaches a descendant unresolved, so a cycle can also close on the element that reads it. The cases are a variable that is itself, one reached again through a fallback, two that name each other, and one reached from two branches of one value.
    • Each case drops the declaration that reads the cycle, as Chromium 153, Firefox 155 and WebKit 26.6 do, while an unrelated variable still resolves.
    • Two cases pin what must not read as a cycle: an inherited variable read twice in one declaration, and a long inherited chain.
    • The cycle is also read through useUnstableNativeVariable.
  • src/__tests__/compiler/variable-cycles.test.ts: the inliner leaves a cycle for the runtime and still folds a chain.

Tests

All 24 pass on main. Removing the inliner's guard, the resolver's guard, the pop of a resolved name, or the marking of a cycle each turns them red (4, 10, 2 and 1 cases).

Verification

On Windows with Node 26:

  • yarn lint clean
  • yarn typecheck clean
  • yarn test --coverage: every failure also fails on main on this machine (the four babel cases)
  • yarn build clean
  • yarn example expo export --platform web exported

Base

Re-written on main (a5002c5) as tests only. Two expectations changed to what browsers compute:

  • A cycle reached from two branches of one value drops the whole declaration; it does not keep a partial value.
  • A * declaration outranks an inherited value. That case is about precedence rather than cycles, so it moved to test(native): cover custom-property precedence #443.

@YevheniiKotyrlo

YevheniiKotyrlo commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor Author

No device frame

This PR adds tests only. main already cuts a variable cycle (#451), so a device renders the same with and without it.

…er's guard

nativewind#451 cuts a custom-property cycle at runtime and in the compile-time
inliner, and tests cycles declared on one element. A variable reaches a
descendant unresolved, so a cycle can also close on the element that reads
it: a variable that is itself, one reached again through a fallback, two that
name each other, and one reached from two branches of one value. Each drops
the declaration that reads it, as browsers do, while an unrelated variable
still resolves.

The other cases pin what must not read as a cycle: a variable read twice in
one declaration, and a long inherited chain. The compiler cases pin that the
inliner leaves a cycle for the runtime and still folds a chain.
@YevheniiKotyrlo
YevheniiKotyrlo force-pushed the fix/variable-resolution-cycle branch from 3ab7d96 to f5913ae Compare October 11, 2026 12:35

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant