Skip to content

feat: implement V8 Value type checking APIs#22462

Merged
Jarred-Sumner merged 7 commits into
mainfrom
claude/add-v8-type-check-apis
Dec 16, 2025
Merged

feat: implement V8 Value type checking APIs#22462
Jarred-Sumner merged 7 commits into
mainfrom
claude/add-v8-type-check-apis

Conversation

@robobun

@robobun robobun commented Sep 7, 2025

Copy link
Copy Markdown
Collaborator

Summary

This PR implements four V8 C++ API methods for type checking that are commonly used by native Node.js modules:

  • v8::Value::IsMap() - checks if value is a Map
  • v8::Value::IsArray() - checks if value is an Array
  • v8::Value::IsInt32() - checks if value is a 32-bit integer
  • v8::Value::IsBigInt() - checks if value is a BigInt

Implementation Details

The implementation maps V8's type checking APIs to JavaScriptCore's equivalent functionality:

  • IsMap() uses JSC's inherits<JSC::JSMap>() check
  • IsArray() uses JSC's isArray() function with the global object
  • IsInt32() uses JSC's isInt32() method
  • IsBigInt() uses JSC's isBigInt() method

Changes

  • Added method declarations to V8Value.h
  • Implemented the methods in V8Value.cpp
  • Added symbol exports to napi.zig (both Unix and Windows mangled names)
  • Added symbols to symbols.txt and symbols.dyn
  • Added comprehensive tests in v8-module/main.cpp and v8.test.ts

Testing

The implementation has been verified to:

  • Compile successfully without errors
  • Export the correct symbols in the binary
  • Follow established patterns in the V8 compatibility layer

Tests cover various value types including empty and populated Maps/Arrays, different numeric ranges, BigInts, and other JavaScript types.

🤖 Generated with Claude Code

Adds V8 C++ API compatibility for:
- v8::Value::IsMap() - checks if value is a Map
- v8::Value::IsArray() - checks if value is an Array
- v8::Value::IsInt32() - checks if value is a 32-bit integer
- v8::Value::IsBigInt() - checks if value is a BigInt

These APIs are commonly used by native Node.js modules and improve
compatibility with the V8 API surface.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the claude label Sep 7, 2025
@robobun

robobun commented Sep 7, 2025

Copy link
Copy Markdown
Collaborator Author
Updated 6:54 PM PT - Dec 15th, 2025

Your commit f10f62c is building: #33384

@coderabbitai

coderabbitai Bot commented Sep 7, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds four new v8::Value type-check methods (IsMap, IsArray, IsInt32, IsBigInt), exports their symbols via Zig and symbol lists (with platform-specific manglings), and adds native and TypeScript tests that exercise these predicates.

Changes

Cohort / File(s) Change Summary
V8 Value type-check methods
src/bun.js/bindings/v8/V8Value.cpp, src/bun.js/bindings/v8/V8Value.h
Declares and implements Value::IsMap, IsArray, IsInt32, IsBigInt. Adds includes for JSMap/JSArray/JSBigInt. IsMap checks cell inheritance from JSC::JSMap; IsArray uses JSC::isArray; IsInt32/IsBigInt use corresponding JSC predicates.
N-API externs and exported symbols
src/napi/napi.zig, src/symbols.dyn, src/symbols.txt
Adds V8 predicate externs to napi.zig (Itanium manglings for non-Windows and MSVC manglings for Windows). Appends dynamic/public symbol entries: __ZNK2v85Value5IsMapEv, __ZNK2v85Value7IsArrayEv, __ZNK2v85Value7IsInt32Ev, __ZNK2v85Value8IsBigIntEv.
Tests
test/v8/v8-module/main.cpp, test/v8/v8.test.ts
Adds test_v8_value_type_checks C++ function that prints multiple predicate results and exports it via NODE_SET_METHOD. Adds a TS test that calls the helper with representative values (Map, Array, Int32, BigInt, etc.) to verify outputs.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/add-v8-type-check-apis

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

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/bun.js/bindings/v8/V8Value.cpp (1)

73-81: IsArray uses the correct cross-realm check.

JSC::isArray(defaultGlobalObject(), value) is the right call; the pre-check is fine but redundant as isArray would return false for non-objects. Optional micro-simplification below.

-    if (!value.isObject()) {
-        return false;
-    }
-    return JSC::isArray(defaultGlobalObject(), value);
+    return JSC::isArray(defaultGlobalObject(), value);
test/v8/v8.test.ts (1)

137-183: Reduce repetition with data-driven loops.

Same assertion pattern repeated many times; switch to arrays/loops to cut boilerplate and speed up edits.

-  describe("Value type checks", () => {
-    it("correctly identifies Maps", async () => {
-      await checkSameOutput("test_v8_value_type_checks", [new Map()]);
-      await checkSameOutput("test_v8_value_type_checks", [
-        new Map([
-          [1, "a"],
-          [2, "b"],
-        ]),
-      ]);
-    });
-    it("correctly identifies Arrays", async () => {
-      await checkSameOutput("test_v8_value_type_checks", [[]]);
-      await checkSameOutput("test_v8_value_type_checks", [[1, 2, 3]]);
-    });
-    it("correctly identifies Int32 values", async () => {
-      await checkSameOutput("test_v8_value_type_checks", [0]);
-      await checkSameOutput("test_v8_value_type_checks", [42]);
-      await checkSameOutput("test_v8_value_type_checks", [-42]);
-      await checkSameOutput("test_v8_value_type_checks", [2147483647]); // INT32_MAX
-      await checkSameOutput("test_v8_value_type_checks", [-2147483648]); // INT32_MIN
-    });
-    it("correctly identifies BigInt values", async () => {
-      await checkSameOutput("test_v8_value_type_checks", [0n]);
-      await checkSameOutput("test_v8_value_type_checks", [123n]);
-      await checkSameOutput("test_v8_value_type_checks", [-123n]);
-      await checkSameOutput("test_v8_value_type_checks", [99999999999999999999999999999999n]);
-    });
-    it("correctly identifies non-Int32 numbers", async () => {
-      await checkSameOutput("test_v8_value_type_checks", [3.14]);
-      await checkSameOutput("test_v8_value_type_checks", [2147483648]); // INT32_MAX + 1
-      await checkSameOutput("test_v8_value_type_checks", [Number.MAX_SAFE_INTEGER]);
-      await checkSameOutput("test_v8_value_type_checks", [Infinity]);
-      await checkSameOutput("test_v8_value_type_checks", [NaN]);
-    });
-    it("correctly identifies other types", async () => {
-      await checkSameOutput("test_v8_value_type_checks", ["string"]);
-      await checkSameOutput("test_v8_value_type_checks", [true]);
-      await checkSameOutput("test_v8_value_type_checks", [false]);
-      await checkSameOutput("test_v8_value_type_checks", [null]);
-      await checkSameOutput("test_v8_value_type_checks", [undefined]);
-      await checkSameOutput("test_v8_value_type_checks", [{}]);
-      await checkSameOutput("test_v8_value_type_checks", [new Date()]);
-      await checkSameOutput("test_v8_value_type_checks", [/regex/]);
-      await checkSameOutput("test_v8_value_type_checks", [new Set()]);
-      await checkSameOutput("test_v8_value_type_checks", [() => {}]);
-    });
-  });
+  describe("Value type checks", () => {
+    const cases = {
+      maps: [new Map(), new Map([[1, "a"], [2, "b"]])],
+      arrays: [[], [1, 2, 3]],
+      int32s: [0, 42, -42, 2147483647, -2147483648],
+      bigints: [0n, 123n, -123n, 99999999999999999999999999999999n],
+      notInt32Numbers: [3.14, 2147483648, Number.MAX_SAFE_INTEGER, Infinity, NaN],
+      others: ["string", true, false, null, undefined, {}, new Date(), /regex/, new Set(), () => {}],
+    };
+    for (const [name, values] of Object.entries(cases)) {
+      it(`correctly identifies ${name}`, async () => {
+        for (const v of values) {
+          await checkSameOutput("test_v8_value_type_checks", [v]);
+        }
+      });
+    }
+  });
test/v8/v8-module/main.cpp (1)

1117-1142: Silence unused local warning.

Local context is currently unused; either remove or cast-to-void to avoid warnings in stricter builds.

-  Local<Context> context = isolate->GetCurrentContext();
+  Local<Context> context = isolate->GetCurrentContext();
+  (void)context;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 73f0594 and 6a14fd0.

📒 Files selected for processing (7)
  • src/bun.js/bindings/v8/V8Value.cpp (2 hunks)
  • src/bun.js/bindings/v8/V8Value.h (1 hunks)
  • src/napi/napi.zig (2 hunks)
  • src/symbols.dyn (1 hunks)
  • src/symbols.txt (1 hunks)
  • test/v8/v8-module/main.cpp (2 hunks)
  • test/v8/v8.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/v8/V8Value.h
  • src/bun.js/bindings/v8/V8Value.cpp
  • test/v8/v8-module/main.cpp
**/*.{cpp,hpp,h,cc,cxx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run clang-format to format C/C++ files (bun run clang-format)

Files:

  • src/bun.js/bindings/v8/V8Value.h
  • src/bun.js/bindings/v8/V8Value.cpp
  • test/v8/v8-module/main.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/v8/V8Value.cpp
  • test/v8/v8-module/main.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When implementing a JS class with a public constructor in C++ bindings, create Foo, FooPrototype, and FooConstructor classes
Define JS-visible properties using HashTableValue arrays
Add iso subspaces for classes with C++ fields
Cache class structures in ZigGlobalObject for bindings

Files:

  • src/bun.js/bindings/v8/V8Value.cpp
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/v8/v8-module/main.cpp
  • test/v8/v8.test.ts
test/v8/**/*

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place V8 C++ API tests under test/v8/

Files:

  • test/v8/v8-module/main.cpp
  • test/v8/v8.test.ts
test/v8/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place V8 C++ API compatibility tests under test/v8/

Files:

  • test/v8/v8-module/main.cpp
  • test/v8/v8.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/v8/v8.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Tests must be named with the suffix .test.ts or .test.tsx and live under the test/ directory
In tests, always use port: 0 and never hardcode ports or use custom random-port utilities
Prefer snapshot tests using normalizeBunSnapshot(...) over direct string equality on stdout/stderr
Do not write tests that assert absence of 'panic', 'uncaught exception', or similar strings in output
Avoid shelling out to tools like find or grep in tests; use Bun.Glob and built-in utilities

Files:

  • test/v8/v8.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Run Prettier to format JS/TS files (bun run prettier)

Files:

  • test/v8/v8.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/v8/v8.test.ts
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup

Files:

  • src/napi/napi.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Run zig-format to format Zig files (bun run zig-format)

Files:

  • src/napi/napi.zig
🧠 Learnings (8)
📚 Learning: 2025-09-03T17:09:28.113Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-03T17:09:28.113Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Define JS-visible properties using HashTableValue arrays

Applied to files:

  • src/bun.js/bindings/v8/V8Value.h
  • src/bun.js/bindings/v8/V8Value.cpp
📚 Learning: 2025-09-03T17:09:28.113Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-03T17:09:28.113Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for classes with C++ fields

Applied to files:

  • src/bun.js/bindings/v8/V8Value.h
📚 Learning: 2025-09-05T18:44:43.223Z
Learnt from: markovejnovic
PR: oven-sh/bun#21728
File: src/bun.js/bindings/bindings.cpp:6429-6432
Timestamp: 2025-09-05T18:44:43.223Z
Learning: The JSC::JSMap::size() method in JavaScriptCore returns a value that can be represented as uint32_t, and the binding functions in src/bun.js/bindings/bindings.cpp correctly use uint32_t as the return type for JSC__JSMap__size. JavaScript Maps are practically limited to 2^32 - 1 elements.

Applied to files:

  • src/bun.js/bindings/v8/V8Value.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros

Applied to files:

  • src/bun.js/bindings/v8/V8Value.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Provide an extern "C" Bun__<Type>__toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Applied to files:

  • src/bun.js/bindings/v8/V8Value.cpp
📚 Learning: 2025-09-03T17:09:28.113Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-03T17:09:28.113Z
Learning: Applies to test/v8/** : Place V8 C++ API compatibility tests under test/v8/

Applied to files:

  • test/v8/v8-module/main.cpp
  • test/v8/v8.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/v8/**/* : Place V8 C++ API tests under test/v8/

Applied to files:

  • test/v8/v8.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/node/**/*.{js,ts} : Place Node.js module compatibility tests under test/js/node/, separated by module (e.g., test/js/node/assert/)

Applied to files:

  • test/v8/v8.test.ts
🔇 Additional comments (11)
src/bun.js/bindings/v8/V8Value.h (1)

17-20: API surface addition looks correct.

Signatures and placement match existing predicates. No header ordering or visibility issues spotted.

src/bun.js/bindings/v8/V8Value.cpp (4)

4-6: Needed JSC includes added.

These headers are appropriate for inherits(), isArray, and isBigInt.


67-71: IsMap mapping LGTM.

Using isCell() && inherits() matches V8::Value::IsMap semantics.


82-86: IsInt32 mapping LGTM.

localToJSValue().isInt32() matches V8 expectations for 32-bit integer numbers.


87-91: IsBigInt mapping LGTM.

Direct isBigInt() is correct.

src/symbols.dyn (1)

53-56: Symbol exports updated in the right spot.

New predicates are listed immediately after IsFunction, matching the layout used elsewhere.

src/symbols.txt (1)

52-55: Static symbol list aligned with dyn exports.

Entries for IsMap/IsArray/IsInt32/IsBigInt added consistently.

test/v8/v8-module/main.cpp (1)

1195-1196: Export wiring LGTM.

Binding is correctly added to exports.

src/napi/napi.zig (3)

1875-1878: POSIX V8API DCE anchors added.

New externs match mangled names and will satisfy link/DCE. No runtime call sites required.


1960-1963: Windows V8API DCE anchors added.

MSVC manglings look correct and are covered by fixDeadCodeElimination().


1800-1882: Quick consistency check across symbol surfaces.

Ensure the four new symbols appear in both src/symbols.{dyn,txt} and in both V8API branches here (POSIX/Windows).

Run this script from repo root:

#!/usr/bin/env bash
set -euo pipefail
need=(__ZNK2v85Value5IsMapEv __ZNK2v85Value7IsArrayEv __ZNK2v85Value7IsInt32Ev __ZNK2v85Value8IsBigIntEv \
'?IsMap@Value@v8@@QEBA_NXZ' '?IsArray@Value@v8@@QEBA_NXZ' '?IsInt32@Value@v8@@QEBA_NXZ' '?IsBigInt@Value@v8@@QEBA_NXZ')
files=(src/symbols.dyn src/symbols.txt src/napi/napi.zig)
for sym in "${need[@]}"; do
  echo "Checking ${sym}"
  rg -n --fixed-strings "${sym}" "${files[@]}"
done
echo "OK if every symbol emitted at least one line above."

Also applies to: 1883-1966

The tests were calling checkSameOutput() too many times per test case,
causing 30+ subprocess spawns which led to timeouts. Consolidated to
test representative values only, matching the pattern of other tests
in the file.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/v8/v8.test.ts (2)

137-149: Reduce duplication with a small table of cases; add key int32 boundaries.

This keeps intent clear, cuts repetition, and exercises edge values for IsInt32 without being noisy.

-  describe("Value type checks", () => {
-    it("correctly identifies value types with IsMap, IsArray, IsInt32, and IsBigInt", async () => {
-      // Test a representative sample of each type, not every possible value
-      // The C++ test function will be called once with each value
-      await checkSameOutput("test_v8_value_type_checks", [new Map()]);
-      await checkSameOutput("test_v8_value_type_checks", [[]]);
-      await checkSameOutput("test_v8_value_type_checks", [42]);
-      await checkSameOutput("test_v8_value_type_checks", [123n]);
-      await checkSameOutput("test_v8_value_type_checks", [3.14]);
-      await checkSameOutput("test_v8_value_type_checks", ["string"]);
-      await checkSameOutput("test_v8_value_type_checks", [{}]);
-    });
-  });
+  describe("Value type checks", () => {
+    it("matches Node for IsMap/IsArray/IsInt32/IsBigInt on representative values", async () => {
+      // Each entry is an argument list; we wrap each value so the addon receives it as info[0].
+      const cases: any[][] = [
+        [new Map()],
+        [[]],
+        [42],
+        [2147483647],   // INT32_MAX
+        [2147483648],   // INT32_MAX + 1 (should not be Int32)
+        [-2147483648],  // INT32_MIN
+        [-2147483649],  // INT32_MIN - 1 (should not be Int32)
+        [123n],
+        [3.14],
+        ["string"],
+        [{}],
+      ];
+      for (const args of cases) {
+        await checkSameOutput("test_v8_value_type_checks", args);
+      }
+    });
+  });

137-149: Scope the assertion to the four new predicates to reduce brittleness.

Right now we diff the whole stdout, which can fail due to unrelated predicates the native helper also prints. Consider parsing the returned nodeResult from checkSameOutput(...) and asserting equality only for lines starting with IsMap/IsArray/IsInt32/IsBigInt.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6a14fd0 and c7d1db5.

📒 Files selected for processing (1)
  • test/v8/v8.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/v8/v8.test.ts
test/v8/**/*

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place V8 C++ API tests under test/v8/

Files:

  • test/v8/v8.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/v8/v8.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Tests must be named with the suffix .test.ts or .test.tsx and live under the test/ directory
In tests, always use port: 0 and never hardcode ports or use custom random-port utilities
Prefer snapshot tests using normalizeBunSnapshot(...) over direct string equality on stdout/stderr
Do not write tests that assert absence of 'panic', 'uncaught exception', or similar strings in output
Avoid shelling out to tools like find or grep in tests; use Bun.Glob and built-in utilities

Files:

  • test/v8/v8.test.ts
test/v8/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place V8 C++ API compatibility tests under test/v8/

Files:

  • test/v8/v8.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Run Prettier to format JS/TS files (bun run prettier)

Files:

  • test/v8/v8.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/v8/v8.test.ts
🧠 Learnings (2)
📚 Learning: 2025-09-03T17:09:28.113Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-03T17:09:28.113Z
Learning: Applies to test/v8/** : Place V8 C++ API compatibility tests under test/v8/

Applied to files:

  • test/v8/v8.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/v8/**/* : Place V8 C++ API tests under test/v8/

Applied to files:

  • test/v8/v8.test.ts
🔇 Additional comments (1)
test/v8/v8.test.ts (1)

137-149: Can the native helper accept multiple args? Consolidate calls if yes.

If test_v8_value_type_checks(...) iterates info.Length() you can pass all values at once to cut process spawns.

Run to confirm helper semantics:

#!/bin/bash
# Inspect the native helper to see if it loops over all args or only uses info[0].
fd -t f -g 'main.cpp' test | xargs -I{} rg -n -C3 'test_v8_value_type_checks|FunctionCallbackInfo<\s*Value\s*>' {}

If it supports multiple args, replace the loop with a single call:

await checkSameOutput("test_v8_value_type_checks", [new Map(), [], 42, 2147483647, 2147483648, -2147483648, -2147483649, 123n, 3.14, "string", {}]);

Claude Bot and others added 4 commits December 16, 2025 01:57
The test was failing because JSON.stringify cannot serialize BigInt
values. Added custom serialization/deserialization that encodes BigInt
values as objects with a __bigint__ marker.

Also removed unused variable in main.cpp to clean up compiler warnings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Use a table of test cases to reduce duplication
- Add INT32_MAX and INT32_MIN boundary tests
- Test values just outside int32 range to verify edge cases

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@Jarred-Sumner Jarred-Sumner merged commit dd04c57 into main Dec 16, 2025
54 of 55 checks passed
@Jarred-Sumner Jarred-Sumner deleted the claude/add-v8-type-check-apis branch December 16, 2025 03:50
Jarred-Sumner pushed a commit that referenced this pull request Dec 17, 2025
### What does this PR do?

- fixes both functions returning false for double-encoded values (even
if the numeric value is a valid int32/uint32)
- fixes IsUint32() returning false for values that don't fit in int32
- fixes the test from #22462 not testing anything (the native functions
were being passed a callback to run garbage collection as the first
argument, so it was only ever testing what the type check APIs returned
for that function)
- extends the test to cover the first edge case above

### How did you verify your code works?

The new tests fail without these fixes.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.

2 participants