Skip to content

Fix infinite loop when using @variant inside @custom-variant that points to another @custom-variant#19633

Merged
RobinMalfait merged 5 commits intomainfrom
fix/issue-19618
Feb 2, 2026
Merged

Fix infinite loop when using @variant inside @custom-variant that points to another @custom-variant#19633
RobinMalfait merged 5 commits intomainfrom
fix/issue-19618

Conversation

@RobinMalfait
Copy link
Member

This PR fixes an infinite loop when you use a @variant inside of a @custom-variant, where the @variant used is another @custom-variant.

The issue stems from the fact that a @custom-variant can use a @slot that we have to replace with the proper AST nodes. However in this setup, the AST nodes will include a @slot node as well, which causes us to replace the @slot again, and so on, causing an infinite loop.

@custom-variant a { @slot; } @custom-variant b { @variant a { @slot; } }

The solution here is to replace the @slot nodes and then skip walking the nodes that were just inserted. This does mean that we end up with a @slot node in the final AST but that's not a real issue because that will get replaced later when handling the next @custom-variant.

Test plan

  1. Existing tests still pass
  2. Added a regression test to ensure that the infinite loop does not happen anymore
  3. Added additional tests to ensure that the behavior is correct

Thanks @wongjn for your initial debugging help and providing a test case as well!

Fixes: #19618

With a bit more info by introducing an additional selector
When handling `@custom-variants` we substitute the `@slot` with the incoming nodes. The issue is that the `nodes` might contain a `@slot` as well, but we don't want to keep replacing it with itself. The `@slot` that gets introduced is coming from _another_ `@custom-variant` so once we replace a `@slot`, we can stop traversing the new nodes.
@RobinMalfait RobinMalfait requested a review from a team as a code owner February 2, 2026 12:15
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

Walkthrough

This pull request addresses an infinite loop issue occurring when using @variant inside @custom-variant. The changes include documenting the bug in the changelog, adding three new test cases to validate complex interactions between @custom-variant and @variant directives including nested variants and slot handling, and modifying the variant walking logic in variants.ts to use WalkAction.ReplaceSkip(nodes) instead of WalkAction.Replace(nodes) when handling @slot during AST traversal.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main fix: preventing an infinite loop when using @variant inside @custom-variant that points to another @custom-variant, which matches the core change in variants.ts.
Description check ✅ Passed The description is directly related to the changeset, explaining the infinite loop issue, the root cause involving @slot replacement, the solution implemented, and the test plan.
Linked Issues check ✅ Passed The pull request successfully addresses the primary objective from issue #19618: fixing the infinite loop/crash when using @variant inside @custom-variant by changing @slot handling from Replace to ReplaceSkip in variants.ts, with comprehensive test coverage added.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the infinite loop issue: CHANGELOG.md documents the fix, variants.ts implements the core fix, and index.test.ts adds comprehensive test coverage for the specific issue and related scenarios.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@RobinMalfait RobinMalfait merged commit df96ea5 into main Feb 2, 2026
7 checks passed
@RobinMalfait RobinMalfait deleted the fix/issue-19618 branch February 2, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant