Skip to content

fix(types): correct FormData iterator return types to include File - #27195

Closed
robobun wants to merge 2 commits into
mainfrom
claude/fix-formdata-iterator-types
Closed

robobun wants to merge 2 commits into
mainfrom
claude/fix-formdata-iterator-types

Conversation

@robobun

@robobun robobun commented Feb 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fix FormData.values() return type from IterableIterator<string> to IterableIterator<Bun.FormDataEntryValue>
  • Fix FormData.entries() return type from IterableIterator<[string, string]> to IterableIterator<[string, Bun.FormDataEntryValue]>
  • Add regression test validating FormData iterators return File objects

These iterator types were inconsistent with get(), getAll(), and forEach() in the same interface, which already correctly used Bun.FormDataEntryValue (string | File).

Closes #27194

Test plan

  • bun bd test test/regression/issue/27194.test.ts passes — validates that values() and entries() return File instances at runtime

🤖 Generated with Claude Code

`FormData.values()` and `FormData.entries()` incorrectly typed their
return values as `string` instead of `FormDataEntryValue` (string | File),
inconsistent with `get()`, `getAll()`, and `forEach()` in the same interface.

Closes #27194

Co-Authored-By: Claude <noreply@anthropic.com>
@robobun
robobun requested a review from alii as a code owner February 19, 2026 15:17
@robobun

robobun commented Feb 19, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 9:49 PM PT - Apr 23rd, 2026

❌ @alii, your commit 190312e has 3 failures in Build #47628 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 27195

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

bun-27195 --bun

@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉


Walkthrough

Updates FormData interface type declarations in the globals definitions to return Bun.FormDataEntryValue instead of string for the values() and entries() iteration methods. Adds regression test to verify FormData iteration behavior with mixed field types.

Changes

Cohort / File(s) Summary
FormData type declarations
packages/bun-types/globals.d.ts
Updated values() to return IterableIterator<Bun.FormDataEntryValue> and entries() to return IterableIterator<[string, Bun.FormDataEntryValue]>, aligning with DOM standards for handling mixed field types (text and File objects).
Regression test
test/regression/issue/27194.test.ts
Added tests validating FormData iteration behavior with mixed field types, ensuring values() and entries() correctly return File objects for file fields and strings for text fields.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically summarizes the main change: correcting FormData iterator return types to include File.
Description check ✅ Passed The PR description covers both required template sections with clear explanations of changes, rationale, and test validation.
Linked Issues check ✅ Passed Changes fully address issue #27194 requirements: values() and entries() now return Bun.FormDataEntryValue (string | File) with regression test validating runtime behavior.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing FormData iterator types and adding related regression tests; no unrelated modifications present.

✏️ 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.

@alii

alii commented Feb 21, 2026

Copy link
Copy Markdown
Member

I'm not confident this doesn't cause conflicts. Will revisit.

@alii alii left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Revisiting

@robobun

robobun commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator Author

@alii I investigated potential conflicts — there shouldn't be any:

  1. No lib.dom conflict: Bun's tsconfig uses "lib": ["ESNext"] (no lib.dom). The FormData constructor declaration already uses Bun.__internal.UseLibDomIfAvailable, which defers to DOM types when lib.dom is loaded. That mechanism applies to the constructor (declare var FormData), not the interface FormData body — but when lib.dom is loaded, its FormData interface (which already uses FormDataEntryValue for these iterators) takes precedence via declaration merging.

  2. Internal consistency: This change makes values() and entries() consistent with get(), getAll(), and forEach() in the same interface, which already use Bun.FormDataEntryValue.

  3. Matches the C++ runtime: The C++ binding (DOMFormData.h) defines FormDataEntryValue = std::variant<String, RefPtr<Blob>>, confirming the runtime can return either type.

  4. No other FormData interface definitions: There's only one interface FormData in bun-types, and only one FormDataEntryValue type definition — no merging or augmentation issues.

Happy to address any specific conflict scenario you have in mind.

Comment on lines +1 to +13
import { expect, test } from "bun:test";

test("FormData.values() returns File objects, not just strings", () => {
const fd = new FormData();
const file = new File(["content"], "test.txt", { type: "text/plain" });
fd.append("textField", "hello");
fd.append("fileField", file);

const values = [...fd.values()];
expect(values).toHaveLength(2);
expect(values[0]).toBe("hello");
expect(values[1]).toBeInstanceOf(File);
});

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.

🟡 This runtime test doesn't guard the actual change in this PR — the PR only edits .d.ts declarations, and FormData.values()/entries() already returned File at runtime, so these assertions pass on system Bun before the fix (violating the USE_SYSTEM_BUN=1 validity rule in CLAUDE.md). Per CLAUDE.md, .d.ts changes should be covered by a type-level assertion in test/integration/bun-types/fixture/globals.ts (which already exercises FormData), and since #27194 was never correct in any release it isn't a true regression — so this file shouldn't live under test/regression/issue/ either.

Extended reasoning...

What's wrong

This PR's only functional change is to two type annotations in packages/bun-types/globals.d.ts. The accompanying test at test/regression/issue/27194.test.ts is a runtime test (expect(values[1]).toBeInstanceOf(File)), but the runtime behavior it asserts was already correct before this PR — only the .d.ts types were wrong. Consequently the test does not regression-guard the change: if someone reverted values(): IterableIterator<Bun.FormDataEntryValue> back to IterableIterator<string>, this test would still pass.

Why existing guidance flags this

Two repo conventions apply directly:

  1. Test validity — CLAUDE.md:130 states: "CRITICAL: Verify your test fails with USE_SYSTEM_BUN=1 bun test <file> and passes with bun bd test <file>. Your test is NOT VALID if it passes with USE_SYSTEM_BUN=1." This test passes on system Bun (the C++ DOMFormData has always yielded File for blob entries), so it provides zero protection for the diff.
  2. Where .d.ts tests belong — CLAUDE.md:37-43 says edits under packages/bun-types/**/*.d.ts should be tested via bun test test/integration/bun-types/bun-types.test.ts, which packs the .d.ts files and runs tsc against fixtures. That's the only test that would actually fail if the type declarations regressed.

Separately, CLAUDE.md:66 and test/CLAUDE.md:153 reserve test/regression/issue/${issueNumber}.test.ts for true regressions (worked in a prior release, then broke). #27194's types were never correct, so even if a runtime test were kept it would belong in test/js/web/html/FormData.test.ts, not a new regression file.

Step-by-step proof

  1. Before this PR, globals.d.ts declares values(): IterableIterator<string>, but JSDOMFormData's iterator yields the underlying FormDataEntryValue variant (String | Blob).
  2. Run USE_SYSTEM_BUN=1 bun test test/regression/issue/27194.test.ts against a released Bun → values[1] is a File, toBeInstanceOf(File) passes. ✅ Test green without the fix.
  3. Now apply this PR, then revert just the two .d.ts lines → bun bd test test/regression/issue/27194.test.ts still passes. ✅ Test green with the fix reverted.
  4. Conclusion: the test cannot detect the absence of the fix in either direction.

Suggested fix

Replace the runtime test with a type-level assertion in test/integration/bun-types/fixture/globals.ts (lines ~221-235 already exercise FormData), e.g.:

import { expectType } from "./utilities";
// ...
const fd = new FormData();
for (const v of fd.values()) expectType<Bun.FormDataEntryValue>(v);
for (const [k, v] of fd.entries()) {
  expectType<string>(k);
  expectType<Bun.FormDataEntryValue>(v);
}

This will fail tsc if the iterator types ever regress to string. If runtime coverage is also desired, add it to the existing test/js/web/html/FormData.test.ts rather than a new test/regression/issue/ file.

Comment on lines +1683 to +1684
values(): IterableIterator<Bun.FormDataEntryValue>;
entries(): IterableIterator<[string, Bun.FormDataEntryValue]>;

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.

🟣 While you're fixing the iterator return types here, consider also adding [Symbol.iterator](): IterableIterator<[string, Bun.FormDataEntryValue]>; — the runtime aliases Symbol.iterator to entries() (JSDOMFormData.cpp), but bun-types doesn't declare it, so without lib.dom for (const [k, v] of fd) fails to type-check. This is a pre-existing gap, not something this PR introduced, but it's a one-line addition adjacent to the lines you're already touching and falls under the same "FormData iterator return types" concern.

Extended reasoning...

What's missing

The interface FormData at packages/bun-types/globals.d.ts:1664-1685 declares keys(), values(), and entries(), but has no [Symbol.iterator]() declaration. Per the WHATWG spec, FormData is iterable and its default iterator yields the same [string, FormDataEntryValue] pairs as entries(). Bun's runtime implements exactly that — JSDOMFormData::finishCreation does putDirect(vm, vm.propertyNames->iteratorSymbol, getDirect(vm, builtinNames.entriesPublicName())), aliasing @@iterator to the entries function.

How it manifests

Without lib.dom loaded (the default for Bun projects, which use "lib": ["ESNext"]), TypeScript sees only the bun-types FormData interface. Since that interface has no [Symbol.iterator](), code like:

const fd = new FormData();
for (const [k, v] of fd) { /* ... */ }

fails with TS2488 ("Type 'FormData' must have a 'Symbol.iterator' method that returns an iterator"), and [...fd] likewise fails. The runtime supports this perfectly fine — only the types are missing.

Why nothing else covers it

A grep across packages/bun-types shows this is the only interface FormData declaration, and Symbol.iterator is declared for CookieMap and sqlite.Statement but not FormData. bun-types depends only on @types/node (which provides no global FormData) and imports undici-types only for EventSource, so there's no other source of a [Symbol.iterator] for FormData when lib.dom is absent. The existing type fixture at test/integration/bun-types/fixture/globals.ts tests entries()/keys()/values() but not for...of, which is why this gap hasn't been caught.

Step-by-step proof

  1. Project uses @types/bun with "lib": ["ESNext"] (no lib.dom) — the recommended Bun setup.
  2. TypeScript resolves FormData to packages/bun-types/globals.d.ts:1664-1685.
  3. That interface has entries(): IterableIterator<[string, Bun.FormDataEntryValue]> (after this PR) but no [Symbol.iterator]().
  4. User writes for (const [name, value] of fd) { ... }.
  5. tsc emits TS2488 because FormData is not declared iterable.
  6. At runtime the same code works, since the C++ prototype installs @@iterator = entries.

Relevance to this PR

This is pre-existing — the PR didn't introduce or worsen it. But the PR's stated goal is to "correct FormData iterator return types to include File", and the default iterator is the most common way people iterate FormData. Fixing values()/entries() while leaving for...of un-typed is an incomplete fix for the same conceptual issue, and the addition is one line right next to the lines being changed.

Suggested fix

entries(): IterableIterator<[string, Bun.FormDataEntryValue]>;
[Symbol.iterator](): IterableIterator<[string, Bun.FormDataEntryValue]>;

This matches how lib.dom.iterable.d.ts declares it, so when lib.dom is loaded the merged interface remains consistent (both declare the same signature shape). Not a blocker — just a suggestion for completeness while you're in this exact spot.

@robobun

robobun commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #36505, which batches this fix with six other bun-types corrections and also adds the missing [Symbol.iterator]() signature.

@robobun robobun closed this Jul 31, 2026
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Correction: #36505 has since been cut down to the SharedArrayBuffer.grow() fix only and no longer carries the FormData change. The FormData iterator fix for #27194 is #34264, which also adds the missing [Symbol.iterator]() signature; this PR stays closed in favor of that one.

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.

Incorrect Iterable FormData types in bun-types

2 participants