Repository: denoland/deno_core
Pull request #1013 (2024-12-27 13:22:26)

Support async methods in object wrap
```rust
impl TestObjectWrap {
  #[async_method]
  async fn with_async_fn(&self)  {
    // ...
  }
}
```


Patch:
diff --git a/core/00_infra.js b/core/00_infra.js
index 13c8269..b57358f 100644
--- a/core/00_infra.js
+++ b/core/00_infra.js
@@ -190,8 +190,9 @@
     };
   }
 
-  function setUpAsyncStub(opName, originalOp) {
+  function setUpAsyncStub(opName, originalOp, maybeProto) {
     let fn;
+
     // The body of this switch statement can be generated using the script above.
     switch (originalOp.length - 1) {
       /* BEGIN TEMPLATE setUpAsyncStub */
@@ -200,7 +201,8 @@
         fn = function async_op_0() {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -219,7 +221,8 @@
         fn = function async_op_1(a) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -238,7 +241,8 @@
         fn = function async_op_2(a, b) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -257,7 +261,8 @@
         fn = function async_op_3(a, b, c) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -276,7 +281,8 @@
         fn = function async_op_4(a, b, c, d) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c, d);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c, d);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -295,7 +301,8 @@
         fn = function async_op_5(a, b, c, d, e) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c, d, e);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c, d, e);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -314,7 +321,8 @@
         fn = function async_op_6(a, b, c, d, e, f) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c, d, e, f);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c, d, e, f);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -333,7 +341,8 @@
         fn = function async_op_7(a, b, c, d, e, f, g) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c, d, e, f, g);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c, d, e, f, g);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -352,7 +361,8 @@
         fn = function async_op_8(a, b, c, d, e, f, g, h) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c, d, e, f, g, h);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c, d, e, f, g, h);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -371,7 +381,8 @@
         fn = function async_op_9(a, b, c, d, e, f, g, h, i) {
           const id = nextPromiseId;
           try {
-            const maybeResult = originalOp(id, a, b, c, d, e, f, g, h, i);
+            // deno-fmt-ignore
+            const maybeResult = originalOp.call(this, id, a, b, c, d, e, f, g, h, i);
             if (maybeResult !== undefined) {
               return PromiseResolve(maybeResult);
             }
@@ -400,6 +411,16 @@
       configurable: false,
       writable: false,
     });
+
+    if (maybeProto) {
+      ObjectDefineProperty(fn, "prototype", {
+        value: maybeProto.prototype,
+        configurable: false,
+        writable: false,
+      });
+      maybeProto.prototype[opName] = fn;
+    }
+
     return fn;
   }
 
diff --git a/core/rebuild_async_stubs.js b/core/rebuild_async_stubs.js
index 7fbfdcb..de60f2e 100755
--- a/core/rebuild_async_stubs.js
+++ b/core/rebuild_async_stubs.js
@@ -8,7 +8,8 @@ const doNotModify =
 function __TEMPLATE__(__ARGS_PARAM__) {
   const id = nextPromiseId;
   try {
-    const maybeResult = __OP__(__ARGS__);
+    // deno-fmt-ignore
+    const maybeResult = __OP__.call(this, __ARGS__);
     if (maybeResult !== undefined) {
       return PromiseResolve(maybeResult);
     }
diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs
index 53ee8ee..d24fe10 100644
--- a/core/runtime/bindings.rs
+++ b/core/runtime/bindings.rs
@@ -374,7 +374,14 @@ pub(crate) fn initialize_deno_core_ops_bindings<'s>(
     let accessor_store = create_accessor_store(method_ctxs);
 
     for method in method_ctxs.iter() {
-      op_ctx_template_or_accessor(&accessor_store, scope, prototype, method);
+      op_ctx_template_or_accessor(
+        &accessor_store,
+        set_up_async_stub_fn,
+        scope,
+        prototype,
+        tmpl,
+        method,
+      );
     }
 
     index += decl.methods.len();
@@ -419,13 +426,31 @@ pub(crate) fn initialize_deno_core_ops_bindings<'s>(
 
 fn op_ctx_template_or_accessor<'s>(
   accessor_store: &AccessorStore,
+  set_up_async_stub_fn: v8::Local<v8::Function>,
   scope: &mut v8::HandleScope<'s>,
   tmpl: v8::Local<'s, v8::ObjectTemplate>,
+  constructor: v8::Local<'s, v8::FunctionTemplate>,
   op_ctx: &OpCtx,
 ) {
   if !op_ctx.decl.is_accessor() {
     let op_fn = op_ctx_template(scope, op_ctx);
     let method_key = op_ctx.decl.name_fast.v8_string(scope).unwrap();
+    if op_ctx.decl.is_async {
+      let undefined = v8::undefined(scope);
+      let op_fn = op_fn.get_function(scope).unwrap();
+
+      let tmpl_fn = constructor.get_function(scope).unwrap();
+
+      let _result = set_up_async_stub_fn
+        .call(
+          scope,
+          undefined.into(),
+          &[method_key.into(), op_fn.into(), tmpl_fn.into()],
+        )
+        .unwrap();
+
+      return;
+    }
 
     tmpl.set(method_key.into(), op_fn.into());
 
diff --git a/ops/op2/config.rs b/ops/op2/config.rs
index 526eb3d..bbb0339 100644
--- a/ops/op2/config.rs
+++ b/ops/op2/config.rs
@@ -109,7 +109,7 @@ impl MacroConfig {
         .collect::<Vec<_>>();
       } else if flag == "nofast" {
         config.nofast = true;
-      } else if flag == "async" {
+      } else if flag == "async" || flag == "async_method" {
         config.r#async = true;
       } else if flag == "async(lazy)" {
         config.r#async = true;
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 51caeb5..a2261f3 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -1259,6 +1259,8 @@ fn parse_attribute(
       (#[getter]) => Some(AttributeModifier::Ignore),
       (#[setter]) => Some(AttributeModifier::Ignore),
       (#[fast]) => Some(AttributeModifier::Ignore),
+      // async is a keyword and does not work as #[async] so we use #[async_method] instead
+      (#[async_method]) => Some(AttributeModifier::Ignore),
       (#[static_method]) => Some(AttributeModifier::Ignore),
       (#[constructor]) => Some(AttributeModifier::Ignore),
       (#[allow ($_rule:path)]) => None,


Test patch:
diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs
index 102bbff..f20a357 100644
--- a/testing/checkin/runner/ops.rs
+++ b/testing/checkin/runner/ops.rs
@@ -94,6 +94,12 @@ impl TestObjectWrap {
   #[fast]
   #[rename("with_RENAME")]
   fn with_rename(&self) {}
+
+  #[async_method]
+  async fn with_async_fn(&self, #[smi] ms: u32) -> Result<(), AnyError> {
+    tokio::time::sleep(std::time::Duration::from_millis(ms as u64)).await;
+    Ok(())
+  }
 }
 
 pub struct DOMPoint {
diff --git a/testing/ops.d.ts b/testing/ops.d.ts
index 5561fab..a3d6fda 100644
--- a/testing/ops.d.ts
+++ b/testing/ops.d.ts
@@ -39,4 +39,5 @@ export class TestObjectWrap {
   constructor();
   withVarargs(...args: any[]): number;
   with_RENAME(): void;
+  withAsyncFn(ms: number): Promise<void>;
 }
diff --git a/testing/unit/resource_test.ts b/testing/unit/resource_test.ts
index 8c9c77a..8971079 100644
--- a/testing/unit/resource_test.ts
+++ b/testing/unit/resource_test.ts
@@ -64,9 +64,9 @@ test(async function testCppgcAsync() {
   assertEquals(await op_async_get_cppgc_resource(resource), 42);
 });
 
-test(function testDomPoint() {
-  const p2 = new DOMPoint();
+test(async function testDomPoint() {
   const p1 = new DOMPoint(100, 100);
+  const p2 = new DOMPoint();
   const p3 = DOMPoint.fromPoint({ x: 200 });
   const p4 = DOMPoint.fromPoint({ x: 0, y: 100, z: 99.9, w: 100 });
   const p5 = p1.fromPoint({ x: 200 });
@@ -102,4 +102,9 @@ test(function testDomPoint() {
   assertEquals(wrap.withVarargs(undefined), 1);
 
   wrap.with_RENAME();
+
+  const promise = wrap.withAsyncFn(10);
+  assert(promise instanceof Promise);
+
+  await promise;
 });