Skip to content

Conversation

@crowlKats
Copy link
Member

@crowlKats crowlKats commented Dec 14, 2025

This PR is split into multiple commits; I would recommend to review them separately.

This PR does the following changes:

  • removes the async keyword on the macro declaration; it is fully inferred from the function definition. The async alternatives (fake, lazy, & deferred) remain unchanged.
  • removes the global attribute for arguments and returns types.
  • adds a FromV8Scopeless trait, which is the default behaviour for arguments now, and requires a scoped attribute on arguments to use the FromV8 trait.
  • return types will default to usage of the ToV8 trait.
  • from_v8 and to_v8 attributes are removed as these are now the default behaviour.
  • Adds some new structs that implement FomV8/ToV8.

There is denoland/deno#31607 ready for updating in CLI.

The v8_slow attribute is kinda not greatly named, so maybe some bikeshedding on that is necessary

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Walkthrough

The PR removes explicit async flags from many #[op2(...)] attributes, replaces several #[global] parameter annotations with #[v8_slow] or changes ops to accept v8::Local and wrap into v8::Global, and deletes some global-based test ops/benchmarks. It adds fast-path traits ToV8Fast/FromV8Fast, new types ByteString and ArrayBufferView, a V8ConvertError, and a new RetVal enum for return parsing. The op2 macro and its dispatch/generator logic were refactored (config, signature, fast/slow dispatch, and generator state).

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: removing the async attribute and making FromV8/ToV8 the default behavior in the op2 macro system.
Description check ✅ Passed The description clearly explains the key changes: removing async keyword inference, removing global attributes, introducing FromV8Fast as default with v8_slow fallback, and removing from_v8/to_v8 attributes.
Docstring Coverage ✅ Passed Docstring coverage is 93.75% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rework-op2

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

# Conflicts:
#	core/ops_builtin_v8.rs
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
ops/op2/dispatch_slow.rs (4)

338-369: Fake-async should likely be treated as async for cppgc rooting.
If generator_state.is_fake_async produces a future boundary, then the non-rooting branch can leave self_ (and similar cppgc refs) unrooted across that boundary.

 pub(crate) fn with_self(
   generator_state: &mut GeneratorState,
   ret_val: &RetVal,
 ) -> TokenStream {
@@
-  if ret_val.is_async() {
+  let is_async = ret_val.is_async() || generator_state.is_fake_async;
+  if is_async {
     let tokens = gs_quote!(generator_state(self_ty, fn_args, scope, try_unwrap_cppgc) => {
       let Some(mut self_) = deno_core::_ops::#try_unwrap_cppgc::<#self_ty>(&mut #scope, #fn_args.this().into()) else {
         #throw_exception;
       };
       self_.root();
     });

786-857: Same fake-async rooting concern for cppgc args.
CppGcResource / OptionCppGcResource should likely root when ret_val.is_async() || generator_state.is_fake_async.

-      if ret_val.is_async() {
+      if ret_val.is_async() || generator_state.is_fake_async {
         let tokens = quote! {
           let Some(mut #arg_ident) = deno_core::_ops::#try_unwrap_cppgc::<#ty>(&mut #scope, #from_ident) else {
             #throw_exception;
           };
           #arg_ident.root();
         };
@@
-      if ret_val.is_async() {
+      if ret_val.is_async() || generator_state.is_fake_async {
         let tokens = quote! {
           let #arg_ident = if #arg_ident.is_null_or_undefined() {
             None
           } else if let Some(mut #arg_ident) = deno_core::_ops::#try_unwrap_cppgc::<#ty>(&mut #scope, #arg_ident) {
             #arg_ident.root();
             Some(#arg_ident)
           } else {
             #throw_exception;
           };
         };

1015-1051: call() should handle moves for fake-async too (if you root in fake-async).
Once you root in fake-async branches, you’ll want those moves to execute inside the future as well.

-  if ret_val.is_async() && !generator_state.moves.is_empty() {
+  if (ret_val.is_async() || generator_state.is_fake_async) && !generator_state.moves.is_empty() {
     let mut moves = TokenStream::new();
     for m in &generator_state.moves {
       moves.extend(m.clone());
     }
-    quote!(async move {
+    let maybe_await = if ret_val.is_async() { quote!(.await) } else { quote!() };
+    quote!(async move {
       #moves
-      #call.await
+      #call #maybe_await
     })
   } else if generator_state.is_fake_async {
     quote!(std::future::ready(#call))
   } else {
     call
   }

659-693: Remove unused FromV8Fast import and use UFCS instead.

In the Arg::FromV8(ty, true) branch, use deno_core::FromV8Fast; is never used and will trigger warnings under #![deny(warnings)]. Use UFCS syntax to avoid the unused import:

     Arg::FromV8(ty, true) => {
       *needs_scope = true;
       let ty =
         syn::parse_str::<syn::Type>(ty).expect("Failed to reparse state type");
       let scope = scope.clone();
       let err = format_ident!("{}_err", arg_ident);
       let throw_exception = throw_type_error_string(generator_state, &err);
       quote! {
-        let #arg_ident = {
-          use deno_core::FromV8;
-          use deno_core::FromV8Fast;
-
-          match <#ty>::from_v8(&mut #scope, #arg_ident) {
+        let #arg_ident = match <#ty as deno_core::FromV8>::from_v8(&mut #scope, #arg_ident) {
             Ok(t) => t,
             Err(#err) => {
               #throw_exception;
             }
-          }
-        };
+        };
       };
     }

The semantics are correct: true represents the slow path (requiring scope and FromV8 trait), while false represents the fast path (FromV8Fast without scope).

core/convert.rs (1)

675-714: Fix null pointer dereference in abview_to_vec before calling add().

The data() pointer can be null for zero-length arrays (V8 fastcalls behavior), but add() is called unconditionally on line 703. This is undefined behavior. Use NonNull::new() to safely handle the null case before any pointer arithmetic, similar to the pattern already used in core/runtime/ops.rs line 460. Apply the fix to both the explicit Uint8Array impl and all macro-generated typed array implementations.

🧹 Nitpick comments (3)
ops/op2/dispatch_async.rs (1)

30-51: Consider improving the error message.

The error string "an async return" at line 50 is incomplete. Looking at the error handling at lines 82-89, this string becomes part of V8SignatureMappingError::NoRetValMapping. The current message doesn't clearly indicate what was expected.

Consider making it more descriptive:

   } else {
-    return Err("an async return");
+    return Err("a Future return type for async dispatch");
   };
ops/op2/signature.rs (2)

650-658: Consider boxing the Type in CUnknown variant.

The clippy warning suppression indicates Type can be large. Boxing it (Box<Type>) would reduce the overall enum size, though the current approach works.


1598-1598: Extract repeated V8Slow check into helper method.

The pattern matches!(attrs.primary, Some(AttributeModifier::V8Slow)) appears multiple times (lines 1387, 1409, 1429, 1439, 1759, 1763). Consider adding a helper method like attrs.is_v8_slow() for clarity.

+impl Attributes {
+  fn is_v8_slow(&self) -> bool {
+    matches!(self.primary, Some(AttributeModifier::V8Slow))
+  }
+}

Also applies to: 1626-1626, 1689-1690, 1756-1765

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2898ad9 and 93e7071.

⛔ Files ignored due to path filters (3)
  • ops/op2/test_cases/async/async_v8_global.out is excluded by !**/*.out
  • ops/op2/test_cases/sync/op_state_ref.out is excluded by !**/*.out
  • ops/op2/test_cases/sync/v8_global.out is excluded by !**/*.out
📒 Files selected for processing (50)
  • core/benches/ops/async.rs (1 hunks)
  • core/benches/ops/sync.rs (0 hunks)
  • core/convert.rs (22 hunks)
  • core/examples/op2.rs (1 hunks)
  • core/lib.rs (1 hunks)
  • core/ops_builtin.rs (9 hunks)
  • core/ops_builtin_v8.rs (9 hunks)
  • core/runtime/ops.rs (14 hunks)
  • core/runtime/tests/jsrealm.rs (1 hunks)
  • core/runtime/tests/misc.rs (6 hunks)
  • core/runtime/tests/mod.rs (1 hunks)
  • core/runtime/tests/ops.rs (6 hunks)
  • ops/op2/README.md (0 hunks)
  • ops/op2/config.rs (8 hunks)
  • ops/op2/dispatch_async.rs (3 hunks)
  • ops/op2/dispatch_fast.rs (3 hunks)
  • ops/op2/dispatch_shared.rs (0 hunks)
  • ops/op2/dispatch_slow.rs (9 hunks)
  • ops/op2/generator_state.rs (1 hunks)
  • ops/op2/mod.rs (9 hunks)
  • ops/op2/object_wrap.rs (1 hunks)
  • ops/op2/signature.rs (34 hunks)
  • ops/op2/signature_retval.rs (2 hunks)
  • ops/op2/test_cases/async/async_arg_return.rs (1 hunks)
  • ops/op2/test_cases/async/async_arg_return_result.rs (1 hunks)
  • ops/op2/test_cases/async/async_cppgc.rs (1 hunks)
  • ops/op2/test_cases/async/async_jsbuffer.rs (1 hunks)
  • ops/op2/test_cases/async/async_op_metadata.rs (1 hunks)
  • ops/op2/test_cases/async/async_opstate.rs (1 hunks)
  • ops/op2/test_cases/async/async_precise_capture.rs (1 hunks)
  • ops/op2/test_cases/async/async_result.rs (1 hunks)
  • ops/op2/test_cases/async/async_result_impl.rs (1 hunks)
  • ops/op2/test_cases/async/async_result_smi.rs (1 hunks)
  • ops/op2/test_cases/async/async_stack_trace.rs (1 hunks)
  • ops/op2/test_cases/async/async_v8_global.rs (0 hunks)
  • ops/op2/test_cases/async/async_void.rs (1 hunks)
  • ops/op2/test_cases/compiler_pass/async.rs (1 hunks)
  • ops/op2/test_cases/compiler_pass/sync.rs (0 hunks)
  • ops/op2/test_cases/sync/from_v8.rs (1 hunks)
  • ops/op2/test_cases/sync/op_state_ref.rs (0 hunks)
  • ops/op2/test_cases/sync/to_v8.rs (0 hunks)
  • ops/op2/test_cases/sync/v8_global.rs (0 hunks)
  • ops/op2/valid_args.md (0 hunks)
  • ops/op2/valid_retvals.md (1 hunks)
  • testing/checkin/runner/ops.rs (0 hunks)
  • testing/checkin/runner/ops_async.rs (6 hunks)
  • testing/checkin/runner/ops_error.rs (1 hunks)
  • testing/checkin/runner/ops_io.rs (1 hunks)
  • testing/checkin/runner/ops_worker.rs (2 hunks)
  • testing/checkin/runner/testing.rs (1 hunks)
💤 Files with no reviewable changes (10)
  • ops/op2/test_cases/async/async_v8_global.rs
  • ops/op2/dispatch_shared.rs
  • ops/op2/valid_args.md
  • ops/op2/test_cases/sync/op_state_ref.rs
  • ops/op2/test_cases/sync/v8_global.rs
  • ops/op2/test_cases/compiler_pass/sync.rs
  • testing/checkin/runner/ops.rs
  • ops/op2/test_cases/sync/to_v8.rs
  • core/benches/ops/sync.rs
  • ops/op2/README.md
🧰 Additional context used
🧬 Code graph analysis (27)
ops/op2/test_cases/async/async_void.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_precise_capture.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_opstate.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_result_smi.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_jsbuffer.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
core/benches/ops/async.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/lib.rs (1)
  • op2 (16-18)
core/runtime/ops.rs (1)
  • op_async_void (2098-2098)
testing/checkin/runner/ops_error.rs (2)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/lib.rs (1)
  • op2 (16-18)
core/runtime/tests/misc.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/compile_test_runner/lib.rs (1)
  • op2 (20-25)
ops/lib.rs (1)
  • op2 (16-18)
ops/op2/test_cases/async/async_stack_trace.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_arg_return_result.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_op_metadata.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/signature_retval.rs (1)
ops/op2/signature.rs (1)
  • parse_type (1590-1865)
ops/op2/test_cases/compiler_pass/async.rs (2)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_opstate.rs (2)
  • op_async_opstate (11-15)
  • state (14-14)
ops/op2/test_cases/async/async_result_impl.rs (2)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/lib.rs (1)
  • op2 (16-18)
testing/checkin/runner/ops_io.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/compile_test_runner/lib.rs (1)
  • op2 (20-25)
ops/lib.rs (1)
  • op2 (16-18)
testing/checkin/runner/ops_worker.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/compile_test_runner/lib.rs (1)
  • op2 (20-25)
ops/lib.rs (1)
  • op2 (16-18)
core/runtime/tests/mod.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/compile_test_runner/lib.rs (1)
  • op2 (20-25)
ops/lib.rs (1)
  • op2 (16-18)
core/runtime/tests/jsrealm.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/compile_test_runner/lib.rs (1)
  • op2 (20-25)
ops/lib.rs (1)
  • op2 (16-18)
ops/op2/test_cases/async/async_cppgc.rs (3)
ops/op2/mod.rs (1)
  • op2 (76-96)
core/webidl.rs (1)
  • cppgc (871-871)
ops/op2/test_cases_fail/lifetimes.rs (1)
  • op_use_cppgc_object (18-18)
ops/op2/test_cases/async/async_arg_return.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/test_cases/async/async_result.rs (1)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/op2/mod.rs (1)
ops/op2/signature.rs (3)
  • parse_signature (1008-1069)
  • name (805-823)
  • sig (1936-1936)
core/runtime/ops.rs (2)
core/core.d.ts (1)
  • Uint8Array (1275-1275)
core/convert.rs (2)
  • value (355-356)
  • value (690-690)
ops/op2/config.rs (1)
ops/webidl/mod.rs (1)
  • list (80-80)
ops/op2/signature.rs (2)
ops/op2/signature_retval.rs (2)
  • arg (122-128)
  • try_parse (132-161)
ops/op2/mod.rs (4)
  • signature (279-283)
  • signature (284-288)
  • signature (290-290)
  • signature (291-291)
core/ops_builtin.rs (2)
ops/op2/mod.rs (1)
  • op2 (76-96)
ops/lib.rs (1)
  • op2 (16-18)
core/ops_builtin_v8.rs (2)
core/core.d.ts (2)
  • Uint8Array (1275-1275)
  • Function (924-924)
core/error.rs (1)
  • f (1807-1807)
🔇 Additional comments (82)
ops/op2/test_cases/async/async_op_metadata.rs (1)

6-20: LGTM! Async inference from function signature.

The removal of explicit async from the macro attributes is correct—the op2 macro now infers async behavior from the async fn signatures. The metadata attributes are preserved as expected.

ops/op2/test_cases/sync/from_v8.rs (1)

20-22: LGTM!

The removal of the #[from_v8] attribute aligns with the PR objective where FromV8 behavior is now the default for arguments. The Foo type correctly implements FromV8Trait, so the conversion will work implicitly.

ops/op2/valid_retvals.md (1)

33-35: LGTM!

The documentation correctly indicates that #[buffer] V8Slice<u32> is supported while Vec<u32> and Box<[u32]> variants are not (missing the "X" in the Supported column). This provides clear guidance on which buffer types can be used as return values.

testing/checkin/runner/ops_error.rs (1)

6-9: LGTM!

The removal of async from the attribute is correct per the PR objectives. The async behavior will now be inferred from the async fn signature. The deferred and lazy variants on other ops correctly remain explicit as they specify non-default async behavior.

ops/op2/test_cases/async/async_opstate.rs (1)

10-14: LGTM!

Correctly updated test case - async behavior is now inferred from the async fn signature rather than the macro attribute.

testing/checkin/runner/testing.rs (1)

44-54: LGTM!

The #[global]#[v8_slow] change correctly aligns with the PR objectives. The v8::Global<v8::Function> type requires the slow-path FromV8 conversion, which is now explicitly requested via #[v8_slow].

testing/checkin/runner/ops_io.rs (1)

103-126: LGTM!

The removal of async from the attribute follows the PR pattern. The async behavior is correctly inferred from the async fn signature.

testing/checkin/runner/ops_worker.rs (2)

221-236: Same as above; macro-only change, function remains async.


194-198: Async inference confirmed; pattern matches the rest of the codebase.

The op2 macro correctly infers async from the async fn signature. This is already the established pattern across the codebase—multiple async operations in ops/op2/test_cases/ use #[op2] without an explicit async parameter on async functions. No lingering #[op2(async)] remains in the file. Changes are good to go.

ops/op2/dispatch_slow.rs (4)

29-29: RetVal import is fine.


1053-1066: RetVal matching change looks consistent with the refactor.
Just ensure non-Value/Result variants are handled elsewhere (the todo!() remains a footgun if reachable).


1123-1125: Mapping ToV8Fast like ToV8 in slow path is sensible.


1206-1208: Same: ToV8Fast treated as ToV8 for v8::Value production is fine.

core/lib.rs (1)

71-75: Public re-exports look good; validate intended public API + docs.

ops/op2/test_cases/async/async_void.rs (1)

6-7: Good migration test for async inference; should keep catching regressions.

core/runtime/tests/mod.rs (1)

34-64: Looks correct; relies on op2 async inference—worth double-checking the test still exercises async dispatch paths.

ops/op2/test_cases/async/async_precise_capture.rs (1)

8-13: LGTM - async inference working as intended.

The removal of the explicit async flag aligns with the PR objective. The macro now infers async behavior from the impl Future return type.

ops/op2/generator_state.rs (1)

13-13: LGTM - new field for fake-async tracking.

The is_fake_async field addition supports the async behavior refactoring mentioned in the PR objectives.

core/runtime/tests/jsrealm.rs (1)

85-88: LGTM - async inference from function signature.

The macro correctly infers async behavior from the async fn declaration.

core/runtime/tests/misc.rs (6)

90-96: LGTM - async inference working correctly.

All test ops in this file have been updated consistently to remove explicit async flags while maintaining async function signatures.


714-719: LGTM - consistent with other async op updates.


1036-1040: LGTM - async behavior correctly inferred.


1084-1087: LGTM - consistent async op conversion.


1190-1193: LGTM - async op attribute updated correctly.


1328-1332: LGTM - final async op conversion in this file.

ops/op2/test_cases/async/async_result.rs (1)

6-9: LGTM - async inference from async fn.

ops/op2/test_cases/async/async_jsbuffer.rs (1)

8-12: LGTM - async inference with buffer attribute.

The #[buffer] attribute is correctly retained while removing the explicit async flag.

ops/op2/test_cases/async/async_result_impl.rs (1)

9-14: LGTM - async inference from Future return type.

The macro correctly identifies async behavior from the impl Future return type, even when wrapped in a Result.

ops/op2/test_cases/async/async_arg_return.rs (1)

6-9: LGTM - async inference from async fn.

ops/op2/test_cases/async/async_result_smi.rs (1)

8-11: LGTM!

The removal of the explicit async flag from #[op2] is correct—async behavior is now inferred from the async fn signature, aligning with the PR objectives.

ops/op2/test_cases/async/async_arg_return_result.rs (1)

6-9: LGTM!

Attribute change is consistent with the PR's approach of inferring async behavior from the function signature.

core/benches/ops/async.rs (1)

54-60: LGTM!

Both op_async_void and op_async_yield correctly updated to use #[op2] without the explicit async flag. The async(lazy) and async(deferred) variants are appropriately preserved on other ops, consistent with the PR description stating those alternatives remain unchanged.

ops/op2/test_cases/async/async_cppgc.rs (1)

18-28: LGTM!

All three CPPGC-related ops correctly updated to use #[op2] without the explicit async flag while preserving the #[cppgc] attributes for garbage-collected object handling.

ops/op2/test_cases/async/async_stack_trace.rs (1)

6-7: LGTM!

The async flag is correctly removed while preserving the stack_trace attribute. This demonstrates proper attribute composition under the new inference model.

core/examples/op2.rs (1)

7-14: LGTM!

The migration from #[global] to #[v8_slow] is correct per the PR objectives. Since v8::Global<v8::Function> requires the slow conversion path (not the new fast-path default), the #[v8_slow] attribute appropriately signals this requirement.

ops/op2/test_cases/compiler_pass/async.rs (1)

15-62: LGTM!

All nine async ops correctly updated. Notable coverage includes:

  • Standard async fn variants
  • Result<impl Future<Output = T>> patterns (op_async4, op_async5) which should be correctly inferred as async
  • Preservation of auxiliary attributes (#[buffer], #[string])
ops/op2/dispatch_async.rs (2)

21-21: LGTM!

Import updated to reflect RetVal relocation to signature_retval module.


98-111: LGTM!

The added !generator_state.is_fake_async condition correctly prevents eager error unwrapping for fake-async ops. In fake-async scenarios, the Result is part of the future's output type rather than an outer wrapper, so attempting eager unwrap would be incorrect.

ops/op2/dispatch_fast.rs (3)

401-407: Verify the operator precedence in this condition.

The condition signature.ret_val.is_async() && !config.async_lazy && !config.async_deferred || config.fake_async has ambiguous precedence. Due to && binding tighter than ||, this evaluates as:

(is_async && !async_lazy && !async_deferred) || fake_async

If fake_async is true, the function returns Ok(None), meaning no fast path is generated. However, the AI summary states "fake_async enables fast path." Please verify this matches the intended behavior.

If you want fake_async ops to skip the fast path, this is correct. If you want them to have a fast path, parentheses are needed.


912-914: Pattern updated to match new FromV8 arity.

The Arg::FromV8(_, _) now accepts two parameters, consistent with the broader refactor adding type information. This correctly returns Ok(None) to indicate no fast-call support for FromV8 arguments.


964-965: Pattern updated to match new ToV8 arity.

The Arg::ToV8(_, _) pattern correctly excludes these return types from fast-call support, returning Ok(None).

ops/op2/signature_retval.rs (3)

85-129: Clean design for nested return value representation.

The RetVal enum with Value, Result, and Future variants cleanly models the nested return type structures. The recursive helper methods (is_async, get_future, unwrap_result, arg) correctly traverse the structure.


131-161: Recursive parsing with async wrapping looks correct.

The handle_type inner function recursively unwraps Result and Future (via impl Future<Output = ...>) types, and try_parse wraps the final result in Future when the function is declared async. This correctly handles both explicit impl Future returns and async fn syntax.


174-199: Test coverage for return value parsing.

Tests cover the key combinations: plain values, Result<_>, impl Future<Output = _>, and nested combinations like Result<impl Future<...>> and Future<Result<...>>. Good coverage for the new structure.

core/runtime/tests/ops.rs (3)

23-24: Async inference from function signature.

The async keyword removal from #[op2] is correct—async behavior is now inferred from the async fn signature. This aligns with the PR's goal of simplifying op declarations.


104-105: Consistent migration to inferred async.

All these async ops correctly have #[op2] without the explicit async flag, while retaining their async fn signatures. The macro will infer async behavior automatically.

Also applies to: 336-338, 485-486, 540-543, 545-551, 553-557, 559-564, 566-570, 597-600


572-580: Async variants (deferred, lazy) retained correctly.

The async(deferred) and async(lazy) attributes remain on op_async_deferred and op_async_lazy since these are explicit scheduling behaviors that cannot be inferred from the function signature alone.

core/ops_builtin.rs (4)

168-171: Async ops simplified.

op_add_async, op_void_async, and op_error_async correctly have the async flag removed from #[op2] while keeping their async fn signatures.


281-282: Consistent async removal.

op_wasm_streaming_stream_feed follows the same pattern—async inferred from signature.


341-342: Promise ID ops retain that attribute.

op_read, op_read_all, and op_write correctly keep #[op2(promise_id)] since promise_id is a distinct configuration from async. The async portion is inferred from the function signature.

Also applies to: 354-356, 390-391


433-434: Remaining async ops simplified.

op_write_all, op_write_type_error, and op_shutdown all correctly use #[op2] with async inferred from their signatures.

Also applies to: 449-450, 464-465

ops/op2/mod.rs (7)

30-30: Import path updated for module restructure.

RetVal is now imported from signature_retval module, reflecting the extraction of return value parsing into its own module.


130-138: Simplified parse_signature call.

The parse_signature function no longer requires an explicit is_async flag—it now reads asyncness directly from the Signature. Using ident.clone() preserves span information better than format_ident!.


168-172: Self type handling improved.

Using ty.clone() instead of string formatting preserves the original token's span, which improves error message locations.


211-212: is_fake_async propagated to generator state.

This flag enables the dispatch generators to know when fake async mode is active, affecting code generation paths.


222-228: Dispatch path selection includes fake_async.

When config.fake_async is true, the async dispatch path is used even if the function isn't actually async. The Ident::new with orig_name.span() preserves error location accuracy.


246-250: fake_async excluded from fast-path enforcement.

Fake async ops use the async dispatch path, so they shouldn't trigger the ShouldBeFast error even if they're technically fast-compatible.


512-513: Test helper calls updated.

Test invocations of parse_signature updated to pass function.attrs and function.sig.clone() matching the new API signature.

Also applies to: 578-579, 594-596

testing/checkin/runner/ops_async.rs (3)

16-26: #[v8_slow] on v8::Global<v8::Function> looks correct (forces slow conversion path).
This matches the new “fast conversion by default” direction while still letting you store the callback beyond the current scope.


28-31: Async inference changes look consistent (async fn / impl Future drive async-ness now).
No functional concerns in these ops given the new macro behavior.

Also applies to: 43-54, 56-65, 79-96, 103-106


43-54: No action needed. The repo uses Rust 1.91.1 with edition = "2024" configured, which exceeds the minimum requirement (Rust 1.85) for impl Trait + use<> syntax. This code will compile without issues.

core/runtime/ops.rs (6)

543-544: Uint8Array import is the right follow-up for the updated buffer return types.


1948-1955: #[op2] + async fn for op_test_get_cppgc_resource matches the new async inference model.


2097-2190: Bulk #[op2(async)]#[op2] conversions look consistent with “async inferred from signature”.

Also applies to: 2249-2266, 2288-2296, 2353-2380


2314-2319: op_async_buffer_vec returning Uint8Array is a good tightening of the JS-facing type.
output.into() keeps the implementation straightforward.


2394-2416: Dropping explicit #[from_v8] / #[to_v8] on Smi / Number matches the new defaults.


2471-2474: #[v8_slow] on Bool is appropriate unless/until Bool: FromV8Fast exists.

ops/op2/config.rs (2)

454-501: Tests updated to format_ident! correctly reflect the new Ident storage.


248-268: Async flag parsing is stricter now (requires async(lazy|fake|deferred)), which matches the PR intent.
Just make sure you’re happy with the error UX for plain async (it’ll be a parse error now).

In syn/proc-macro attribute parsing, what is the best practice for producing friendly errors vs proc-macro panics when parsing keywords like `async`?
core/ops_builtin_v8.rs (2)

623-718: op_serialize returning Uint8Array is a good API improvement; dummy return on thrown exception is fine.
Minor: the dummy Ok(vec![].into()) could be Ok(Uint8Array(Vec::new())) for clarity, but current code is fine.


28-40: Local-in, Global-stored callback pattern is correct and safe.
Accepting v8::Local<v8::Function> at the boundary and wrapping into v8::Global for storage follows rusty_v8 best practices. Creating the Global from the Local while an active PinScope is present satisfies the key requirement that persistent handles must be created within a valid HandleScope. Storing the Global in module state for later use is the intended pattern—no issues with scope or lifetime.

Also applies to: 42-50, 128-177, 1050-1092, 1236-1249

core/convert.rs (1)

84-88: Fast-trait delegation pattern for Smi/Number/bool looks good.
This keeps the slow signature stable while letting op2 use fast conversions by default.

Also applies to: 136-140, 191-213, 270-291, 341-360

ops/op2/signature.rs (10)

404-407: Position-aware conversion looks correct.

The use of position to determine FromV8 vs ToV8 is appropriate.


564-565: Marker logic correctly implements fast/slow path distinction.


670-671: Attribute requirements correctly enforce fast-by-default behavior.


727-727: Good improvement using Ident instead of String.

Preserves span information and improves type safety.


1261-1261: V8Slow attribute parsing integrated correctly.


1014-1030: Explicit name extraction improves error reporting.

The refactored code is clearer about how argument names are extracted for diagnostics.


1383-1391: Vec handling logic supports both buffer and trait-based conversion paths.

The conditional logic correctly distinguishes between buffer usage and generic FromV8/ToV8 conversion based on attributes.


1409-1409: v8::Global now handled through generic conversion traits.

Consistent with the PR objective of removing special Global handling in favor of trait-based conversion.


1568-1578: Updated validation correctly handles new CUnknown variant.


1917-1919: Test updates consistently reflect new signature format.

All tests properly updated for the removal of is_fake_async parameter and new return value structure.

Also applies to: 1971-1971, 1980-2141

@crowlKats crowlKats changed the title Rework op2 feat(op2): remove async attribute and make FromV8/ToV8 the default behaviour Dec 14, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
core/convert.rs (1)

394-505: Fix ByteString::from(&str) – current conversion does not compile

impl From<&str> for ByteString still uses let v: Vec<u8> = s.into();, but &str does not implement Into<Vec<u8>> in the standard library, so this will fail to compile here (unlike in serde_v8 where additional context may exist).

USIZE2X is now correctly using std::mem::size_of, but this From<&str> needs to be adjusted.

Suggested fix reusing the existing From<&[u8]> impl:

 impl From<&str> for ByteString {
   fn from(s: &str) -> Self {
-    let v: Vec<u8> = s.into();
-    ByteString::from(v)
+    ByteString::from(s.as_bytes())
   }
 }
🧹 Nitpick comments (1)
core/convert.rs (1)

559-570: Option / OptionNull / OptionUndefined fast-paths match existing semantics

The new FromV8Fast impls for:

  • OptionNull<T> (mapping nullNone, else delegate to T::from_v8),
  • OptionUndefined<T> (mapping undefinedNone, else delegate), and
  • plain Option<T> (same undefined semantics),

mirror the existing FromV8 behavior and correctly reuse the underlying FromV8Fast of T.

If you want to avoid future drift between slow/fast paths, you could optionally implement the FromV8 versions in terms of the new fast variants (as done for numbers), but the current duplication is correct and clear.

Also applies to: 624-635, 1068-1079

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12049ca and 4ae07b9.

📒 Files selected for processing (2)
  • core/convert.rs (23 hunks)
  • ops/op2/config.rs (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
core/convert.rs (3)
core/runtime/ops_rust_to_v8.rs (9)
  • to_v8 (37-40)
  • to_v8 (46-46)
  • to_v8 (77-85)
  • to_v8 (449-456)
  • to_v8 (463-470)
  • to_v8 (478-483)
  • to_v8 (575-580)
  • to_v8 (592-594)
  • from (149-151)
ops/op2/test_cases/sync/from_v8.rs (1)
  • from_v8 (12-17)
core/error.rs (6)
  • v (1663-1663)
  • v (1666-1666)
  • v (1669-1669)
  • error (298-311)
  • error (1801-1801)
  • from (244-246)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build-linux-arm / linux-arm test
  • GitHub Check: build-windows / windows lint
  • GitHub Check: build-linux-arm / linux-arm test-miri
  • GitHub Check: build-windows / windows test
  • GitHub Check: build-linux / linux coverage
  • GitHub Check: build-linux / linux test
  • GitHub Check: build-linux / linux test-miri
  • GitHub Check: build-macos / macos lint
  • GitHub Check: build-linux / linux lint-deps
  • GitHub Check: build-macos / macos test
  • GitHub Check: build-linux / linux test-ops
🔇 Additional comments (9)
ops/op2/config.rs (5)

35-37: Field types improved to use Ident.

Changing from Option<String> to Option<Ident> is a good improvement for type safety. Note that these fields aren't populated from attribute parsing anymore—they're likely derived from the function signature elsewhere.


101-105: Async mode handling looks correct.

The change from Async(Option<AsyncMode>) to Async(AsyncMode) is consistent with the updated parsing, and the match arms properly set the corresponding config flags.


234-254: Async parsing requires explicit mode specification.

The parsing now requires arguments via require_list(), meaning #[op2(async)] alone will error—you must specify the mode like #[op2(async(lazy))]. This aligns with the PR's goal of inferring async from the function signature, making explicit async attributes mode-specific only.


442-447: Test case looks good.

The promise_id parsing test is straightforward and correct.


223-230: fast(...) parsing converts Type to string intentionally, with roundtrip validation.

The pattern is tested and intentional—it stores generic types like op_generic::<T> as strings for later use as alternative op references. The string is reparsed back to Type in dispatch_fast.rs line 392 with .expect(), meaning invalid alternatives will panic at compile time. This is already documented with a TODO comment at dispatch_fast.rs line 389 noting that alternatives are not validated. No changes needed here.

core/convert.rs (4)

84-88: Fast-path traits + v8::Local/v8::Global conversions look coherent

The introduction of ToV8Fast/FromV8Fast and their use in the new V8ConvertError + v8::Local/v8::Global impls is consistent:

  • V8ConvertError cleanly wraps both Infallible and DataError.
  • The TryInto-based impls for v8::Local<T> and v8::Global<T> correctly bridge between specific V8 handle types and v8::Value without extra scope requirements.
  • ToV8 delegating to ToV8Fast and FromV8 delegating to FromV8Fast for these handles avoids duplication and matches the intended “fast-path” design.

I don't see correctness issues or surprising behavior in this layering.

Also applies to: 136-140, 1081-1179


191-213: Numeric/bool FromV8 refactor to fast paths is fine; confirm Error = DataError impact

The refactor of Smi<T>, Number<T>, primitive numeric types, and bool so that:

  • FromV8 forwards to FromV8Fast, and
  • their Error associated type is DataError

is internally consistent and simplifies the implementation. Fast paths now own the actual conversion logic, with FromV8 just acting as a thin wrapper.

However, using DataError as the Error type for these public FromV8 impls is a behavioral API change compared to using a boxed error type (e.g., JsErrorBox) and can affect downstream crates that mention <T as FromV8>::Error in bounds or pattern-match on it. Please double-check that this change is acceptable from a semver/API-compat perspective for deno_core.

Also applies to: 270-291, 293-329, 341-360


675-699: Typed array and ArrayBufferView conversions look correct and efficient

The additions around typed arrays and ArrayBufferView look solid:

  • Uint8Array and the macro-generated {Int16,Uint16,Int32,Uint32,BigInt64,BigUint64}Array now have FromV8 delegating to FromV8Fast, with fast paths gated by the appropriate is_*_array checks and clear BadType errors.
  • abview_to_vec plus maybe_uninit_vec/transmute_vec is used in a standard way for zero-copy-ish extraction from ArrayBufferView, with size/alignment assertions guarding the unsafe transmute.
  • ArrayBufferView’s FromV8Fast dispatches cleanly across all supported typed-array kinds and returns a precise BadType error when the value is not an ArrayBufferView.

Overall, this gives a good, fast typed-array story without obvious safety or semantic issues.

Also applies to: 716-785, 788-794, 795-872


1294-1302: Tests exercise the new option/typed-array conversions appropriately

The updated tests:

  • For OptionUndefined/OptionNull, verify both the JS→Rust (FromV8) and Rust→JS (ToV8) directions for undefined/null and concrete numeric payloads.
  • For all typed array wrappers and BigInt* variants, assert round-tripping through FromV8 and ToV8, including empty arrays and the DataError shape in failure cases.

This coverage is well aligned with the new conversion paths and should catch regressions in the option and typed-array handling.

Also applies to: 1329-1335, 1489-1497, 1514-1522, 1539-1547, 1564-1572, 1592-1594, 1614-1616, 1622-1624

@ry
Copy link
Member

ry commented Dec 15, 2025

what's the purpose behind this?

@crowlKats
Copy link
Member Author

This is for things we had discussed during the offsite

Copy link
Member

@nathanwhit nathanwhit left a comment

Choose a reason for hiding this comment

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

Looks good overall, just a bit confused on the FromV8Fast and ToV8Fast traits

core/convert.rs Outdated
) -> Result<Self, Self::Error>;
}

pub trait FromV8Fast<'a>: Sized + FromV8<'a> {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment on this trait and why it exists? And like what requirement is there to impl FromV8Fast. Likewise for ToV8Fast

I'm guessing it's like "available in fast api" but it's not super clear. There's also nothing preventing anyone from implementing FromV8Fast for a random type, though not sure if that would be problematic in practice

Copy link
Member Author

Choose a reason for hiding this comment

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

so it doesnt actually have much to do with v8 fast api, so maybe needs to be renamed.
We have ops which do not need to create a scope, FromV8Fast is what plays into that since FromV8 requires a scope. so maybe FromV8Fast should be named to FromV8Scopeless or something similar.

ToV8Fast is in a similar situation, however it is only implemented for Local and also is not handled by op2 in this PR, so maybe not worth including in this PR?

Copy link
Member

@littledivy littledivy left a comment

Choose a reason for hiding this comment

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

Looks good. Let's land in Deno and run benchy to measure perf

@crowlKats crowlKats merged commit df40fcb into main Jan 10, 2026
18 checks passed
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.

5 participants