fix(Effect): handle transpiled generator bodies in Effect.fn#6254
Open
markgdawson wants to merge 1 commit into
Open
fix(Effect): handle transpiled generator bodies in Effect.fn#6254markgdawson wants to merge 1 commit into
markgdawson wants to merge 1 commit into
Conversation
Effect.fn(name)(body) crashes at runtime with
"RuntimeException: Not a valid effect: {}" when body is a generator
function that has been lowered by a bundler/transpiler into a plain
function returning an iterator IIFE (e.g. babel-preset-expo on React
Native / Hermes). isGeneratorFunction returns false for such bodies,
so the iterator was passed through as if it were an Effect.
Detect the iterator-shape post-apply and re-wrap it with
core.fromIterator. The first iterator is consumed immediately; subsequent
invocations re-apply body to produce fresh iterators, preserving
Effect.fn's reusable-wrapper contract.
fnUntraced is unaffected: it always wraps with fromIterator already.
🦋 Changeset detectedLatest commit: 36266c6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
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.
Summary
Effect.fn(name)(body)crashes at runtime withRuntimeException: Not a valid effect: {}whenbodyis a generator function that has been lowered by a bundler/transpiler into a non-generator function returning an iterator. For example,babel-preset-expoon React Native / Hermes rewrites destructured-param generators like this:The lowered wrapper is no longer a
GeneratorFunction, soisGeneratorFunction(body)infnApplyreturns false. The else branch then takesbody.apply(...)'s return value — anIterator, not anEffect— and passes it towithSpan, which hands it to the fiber runLoop. The loop dies at the first instruction because the value has no_op.Repro
Root cause
isGeneratorFunctioninUtils.tschecksu.constructor === function*(){}.constructor. That works for native generators and for transpilers that preservefunction*syntax in their output, but fails for any transformer that lowersfunction*into a plain function — including Babel's@babel/plugin-transform-parameters, whichbabel-preset-expoships unconditionally on Hermes-native.Effect.fnhas had this hazard since it was introduced in 3.11.0. It is not a regression — it has always crashed under transformers that erasefunction*syntax.Fix
In
fnApply's else branch, after callingbody.apply(...), duck-type the result. If it looks like an iterator (has.nextandSymbol.iterator) and isn't already anEffect, re-wrap it withcore.fromIterator. The first iterator is consumed immediately; subsequent invocations re-applybodyto produce fresh iterators (preservingEffect.fn's reusable-wrapper contract).fnUntraceddoes not need this change: it unconditionally wraps withfromIterator, so a transpiler-lowered body's returned iterator flows through correctly already.Test
Added a regression test in
packages/effect/test/Effect/fn.test.tsconstructing the exact shape the transpiler produces — a plain function returning a generator IIFE. Verified red → green againstmain.Related
Effect.fn(this PR resolves one observable manifestation; the broader hardening is still useful).