Repository: denoland/deno_core
Pull request #1004 (2024-12-16 21:33:17)

allow static and instance methods with same name
To support e.g.

```rust
#[op2]
impl Foo {
  #[static_method]
  fn foo() {}

  fn foo(&self) {}
}
```

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/denoland/deno_core/pull/1004?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 81.84%. Comparing base [(`0c7f83e`)](https://app.codecov.io/gh/denoland/deno_core/commit/0c7f83ea8daabe8f541fc93ae97529dc446f5f24?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) to head [(`f480cec`)](https://app.codecov.io/gh/denoland/deno_core/commit/f480cec2deb90a066a79e07665f47b99feaa3165?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).
> Report is 198 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1004      +/-   ##
==========================================
+ Coverage   81.43%   81.84%   +0.40%     
==========================================
  Files          97       98       +1     
  Lines       23877    25915    +2038     
==========================================
+ Hits        19445    21210    +1765     
- Misses       4432     4705     +273     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/denoland/deno_core/pull/1004?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).

nathanwhit: > One more ask would be to add a compile test, like `ops/op2/test_cases/...` that shows the full output of constructed object. But we can do that in a follow up

Opened https://github.com/denoland/deno_core/pull/1005
nathanwhit: Merged https://github.com/denoland/deno_core/pull/1005, updated this PR to add to the macro test so you can see the output change

Patch:
diff --git a/core/examples/ts_module_loader.rs b/core/examples/ts_module_loader.rs
index 332963b..338e1c6 100644
--- a/core/examples/ts_module_loader.rs
+++ b/core/examples/ts_module_loader.rs
@@ -4,6 +4,7 @@
 //!
 //! It will only transpile, not typecheck (like Deno's `--no-check` flag).
 
+use std::borrow::Cow;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::rc::Rc;
@@ -124,8 +125,12 @@ impl ModuleLoader for TypescriptModuleLoader {
     ModuleLoadResponse::Sync(load(source_maps, module_specifier))
   }
 
-  fn get_source_map(&self, specifier: &str) -> Option<Vec<u8>> {
-    self.source_maps.borrow().get(specifier).cloned()
+  fn get_source_map(&self, specifier: &str) -> Option<Cow<[u8]>> {
+    self
+      .source_maps
+      .borrow()
+      .get(specifier)
+      .map(|v| v.clone().into())
   }
 }
 
diff --git a/core/modules/loaders.rs b/core/modules/loaders.rs
index f3f53b1..d811db3 100644
--- a/core/modules/loaders.rs
+++ b/core/modules/loaders.rs
@@ -18,6 +18,7 @@ use anyhow::bail;
 use anyhow::Context;
 use anyhow::Error;
 use futures::future::FutureExt;
+use std::borrow::Cow;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::future::Future;
@@ -117,7 +118,7 @@ pub trait ModuleLoader {
   /// Returns a source map for given `file_name`.
   ///
   /// This function will soon be deprecated or renamed.
-  fn get_source_map(&self, _file_name: &str) -> Option<Vec<u8>> {
+  fn get_source_map(&self, _file_name: &str) -> Option<Cow<[u8]>> {
     None
   }
 
diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs
index 1c6c215..74cb0e7 100644
--- a/core/runtime/bindings.rs
+++ b/core/runtime/bindings.rs
@@ -401,8 +401,8 @@ pub(crate) fn initialize_deno_core_ops_bindings<'s>(
 
     for method in op_method_ctx.static_methods.iter() {
       let op_fn = op_ctx_template(scope, method);
-      let method_key = method.decl.name_fast.v8_string(scope).unwrap();
-      tmpl.set(method_key.into(), op_fn.into());
+      let method_key = method.decl.name_fast;
+      tmpl.set(method_key.v8_string(scope).unwrap().into(), op_fn.into());
     }
 
     let op_fn = tmpl.get_function(scope).unwrap();
@@ -526,17 +526,15 @@ fn create_accessor_store(ctx: &OpMethodCtx) -> AccessorStore {
   for method in ctx.methods.iter() {
     // Populate all setters first.
     if method.decl.accessor_type == AccessorType::Setter {
-      let key = method.decl.name_fast.to_string();
-
-      // All setters must start with "__set_".
-      let key = key.strip_prefix("__set_").expect("Invalid setter name");
+      let key = method.decl.name_fast;
 
+      let key_str = key.to_string();
       // There must be a getter for each setter.
       let getter = ctx
         .methods
         .iter()
         .find(|m| {
-          m.decl.name == key && m.decl.accessor_type == AccessorType::Getter
+          m.decl.name == key_str && m.decl.accessor_type == AccessorType::Getter
         })
         .expect("Getter not found for setter");
 
@@ -1018,8 +1016,8 @@ pub fn create_exports_for_ops_virtual_module<'s>(
 
     for method in ctx.static_methods.iter() {
       let op_fn = op_ctx_template(scope, method);
-      let method_key = method.decl.name_fast.v8_string(scope).unwrap();
-      tmpl.set(method_key.into(), op_fn.into());
+      let method_key = method.decl.name_fast;
+      tmpl.set(method_key.v8_string(scope).unwrap().into(), op_fn.into());
     }
 
     let op_fn = tmpl.get_function(scope).unwrap();
diff --git a/core/source_map.rs b/core/source_map.rs
index 316eac7..43945c3 100644
--- a/core/source_map.rs
+++ b/core/source_map.rs
@@ -205,13 +205,13 @@ mod tests {
       unreachable!()
     }
 
-    fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
+    fn get_source_map(&self, file_name: &str) -> Option<Cow<[u8]>> {
       let url = Url::parse(file_name).unwrap();
       let content = self.map.get(&url)?;
       content
         .source_map
         .as_ref()
-        .map(|s| s.to_string().into_bytes())
+        .map(|s| Cow::Borrowed(s.as_bytes()))
     }
 
     fn get_source_mapped_source_line(
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 4f395b1..668a7a5 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -99,6 +99,7 @@ pub(crate) fn generate_op2(
 ) -> Result<TokenStream, Op2Error> {
   // Create a copy of the original function, named "call"
   let call = Ident::new("call", Span::call_site());
+  let orig_name = func.sig.ident.clone();
   let mut op_fn = func.clone();
   // Collect non-special attributes
   let attrs = op_fn
@@ -121,6 +122,8 @@ pub(crate) fn generate_op2(
   if config.setter {
     // Prepend "__set_" to the setter function name.
     func.sig.ident = format_ident!("__set_{}", func.sig.ident);
+  } else if config.static_member {
+    func.sig.ident = format_ident!("__static_{}", func.sig.ident);
   }
   let signature = parse_signature(func.attrs, func.sig.clone())?;
   if let Some(ident) = signature.lifetime.as_ref().map(|s| format_ident!("{s}"))
@@ -203,7 +206,8 @@ pub(crate) fn generate_op2(
 
   let mut slow_generator_state = base_generator_state.clone();
 
-  let name = func.sig.ident;
+  let rust_name = func.sig.ident;
+  let name = orig_name;
 
   let slow_fn = if signature.ret_val.is_async() {
     generate_dispatch_async(&config, &mut slow_generator_state, &signature)?
@@ -290,7 +294,7 @@ pub(crate) fn generate_op2(
     }
   } else {
     quote! {
-      impl <#(#generic : #bound),*> #name <#(#generic),*> {
+      impl <#(#generic : #bound),*> #rust_name <#(#generic),*> {
         #[inline(always)]
         #(#attrs)*
         #op_fn
@@ -308,15 +312,15 @@ pub(crate) fn generate_op2(
 
   Ok(quote! {
     #[allow(non_camel_case_types)]
-    #vis const fn #name <#(#generic : #bound),*> () -> ::deno_core::_ops::OpDecl {
+    #vis const fn #rust_name <#(#generic : #bound),*> () -> ::deno_core::_ops::OpDecl {
       #[allow(non_camel_case_types)]
       #(#attrs)*
-      #vis struct #name <#(#generic),*> {
+      #vis struct #rust_name <#(#generic),*> {
         // We need to mark these type parameters as used, so we use a PhantomData
         _unconstructable: ::std::marker::PhantomData<(#(#generic),*)>
       }
 
-      impl <#(#generic : #bound),*> ::deno_core::_ops::Op for #name <#(#generic),*> {
+      impl <#(#generic : #bound),*> ::deno_core::_ops::Op for #rust_name <#(#generic),*> {
         const NAME: &'static str = stringify!(#name);
         const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
           /*name*/ ::deno_core::__op_name_fast!(#name),
@@ -336,7 +340,7 @@ pub(crate) fn generate_op2(
         );
       }
 
-      impl <#(#generic : #bound),*> #name <#(#generic),*> {
+      impl <#(#generic : #bound),*> #rust_name <#(#generic),*> {
         pub const fn name() -> &'static str {
           <Self as deno_core::_ops::Op>::NAME
         }
@@ -347,7 +351,7 @@ pub(crate) fn generate_op2(
 
       #callable
 
-      <#name <#(#generic),*>  as ::deno_core::_ops::Op>::DECL
+      <#rust_name <#(#generic),*>  as ::deno_core::_ops::Op>::DECL
     }
   })
 }
diff --git a/ops/op2/object_wrap.rs b/ops/op2/object_wrap.rs
index d1f783e..430e339 100644
--- a/ops/op2/object_wrap.rs
+++ b/ops/op2/object_wrap.rs
@@ -116,7 +116,7 @@ pub(crate) fn generate_impl_ops(
 
         constructor = Some(ident);
       } else if config.static_member {
-        static_methods.push(ident);
+        static_methods.push(format_ident!("__static_{}", ident));
       } else {
         if config.setter {
           methods.push(format_ident!("__set_{}", ident));


Test patch:
diff --git a/ops/op2/test_cases/sync/object_wrap.out b/ops/op2/test_cases/sync/object_wrap.out
index 8e7f4f0..d95b86a 100644
--- a/ops/op2/test_cases/sync/object_wrap.out
+++ b/ops/op2/test_cases/sync/object_wrap.out
@@ -7,8 +7,9 @@ impl Foo {
             Foo::zzz(),
             Foo::withVarargs(),
             Foo::with_RENAME(),
+            Foo::doThing(),
         ],
-        static_methods: &[],
+        static_methods: &[Foo::__static_doThing()],
         constructor: Foo::constructor(),
         name: ::deno_core::__op_name_fast!(Foo),
         type_name: || std::any::type_name::<Foo>(),
@@ -260,9 +261,9 @@ impl Foo {
             _unconstructable: ::std::marker::PhantomData<()>,
         }
         impl ::deno_core::_ops::Op for __set_x {
-            const NAME: &'static str = stringify!(__set_x);
+            const NAME: &'static str = stringify!(x);
             const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
-                ::deno_core::__op_name_fast!(__set_x),
+                ::deno_core::__op_name_fast!(x),
                 false,
                 false,
                 1usize as u8,
@@ -929,4 +930,197 @@ impl Foo {
         }
         <with_RENAME as ::deno_core::_ops::Op>::DECL
     }
+    #[allow(non_camel_case_types)]
+    const fn __static_doThing() -> ::deno_core::_ops::OpDecl {
+        #[allow(non_camel_case_types)]
+        struct __static_doThing {
+            _unconstructable: ::std::marker::PhantomData<()>,
+        }
+        impl ::deno_core::_ops::Op for __static_doThing {
+            const NAME: &'static str = stringify!(doThing);
+            const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
+                ::deno_core::__op_name_fast!(doThing),
+                false,
+                false,
+                0usize as u8,
+                false,
+                Self::v8_fn_ptr as _,
+                Self::v8_fn_ptr_metrics as _,
+                ::deno_core::AccessorType::None,
+                None,
+                None,
+                ::deno_core::OpMetadata {
+                    ..::deno_core::OpMetadata::default()
+                },
+            );
+        }
+        impl __static_doThing {
+            pub const fn name() -> &'static str {
+                <Self as deno_core::_ops::Op>::NAME
+            }
+            #[inline(always)]
+            fn slow_function_impl<'s>(
+                info: &'s deno_core::v8::FunctionCallbackInfo,
+            ) -> usize {
+                #[cfg(debug_assertions)]
+                let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                    &<Self as deno_core::_ops::Op>::DECL,
+                );
+                let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(
+                    info,
+                );
+                let result = { Self::call() };
+                deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv);
+                return 0;
+            }
+            extern "C" fn v8_fn_ptr<'s>(
+                info: *const deno_core::v8::FunctionCallbackInfo,
+            ) {
+                let info: &'s _ = unsafe { &*info };
+                Self::slow_function_impl(info);
+            }
+            extern "C" fn v8_fn_ptr_metrics<'s>(
+                info: *const deno_core::v8::FunctionCallbackInfo,
+            ) {
+                let info: &'s _ = unsafe { &*info };
+                let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                    info,
+                );
+                let opctx: &'s _ = unsafe {
+                    &*(deno_core::v8::Local::<
+                        deno_core::v8::External,
+                    >::cast_unchecked(args.data())
+                        .value() as *const deno_core::_ops::OpCtx)
+                };
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Dispatched,
+                );
+                let res = Self::slow_function_impl(info);
+                if res == 0 {
+                    deno_core::_ops::dispatch_metrics_slow(
+                        opctx,
+                        deno_core::_ops::OpMetricsEvent::Completed,
+                    );
+                } else {
+                    deno_core::_ops::dispatch_metrics_slow(
+                        opctx,
+                        deno_core::_ops::OpMetricsEvent::Error,
+                    );
+                }
+            }
+        }
+        impl __static_doThing {
+            #[inline(always)]
+            fn call() {}
+        }
+        <__static_doThing as ::deno_core::_ops::Op>::DECL
+    }
+    #[allow(non_camel_case_types)]
+    const fn doThing() -> ::deno_core::_ops::OpDecl {
+        #[allow(non_camel_case_types)]
+        struct doThing {
+            _unconstructable: ::std::marker::PhantomData<()>,
+        }
+        impl ::deno_core::_ops::Op for doThing {
+            const NAME: &'static str = stringify!(doThing);
+            const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
+                ::deno_core::__op_name_fast!(doThing),
+                false,
+                false,
+                0usize as u8,
+                false,
+                Self::v8_fn_ptr as _,
+                Self::v8_fn_ptr_metrics as _,
+                ::deno_core::AccessorType::None,
+                None,
+                None,
+                ::deno_core::OpMetadata {
+                    ..::deno_core::OpMetadata::default()
+                },
+            );
+        }
+        impl doThing {
+            pub const fn name() -> &'static str {
+                <Self as deno_core::_ops::Op>::NAME
+            }
+            #[inline(always)]
+            fn slow_function_impl<'s>(
+                info: &'s deno_core::v8::FunctionCallbackInfo,
+            ) -> usize {
+                #[cfg(debug_assertions)]
+                let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                    &<Self as deno_core::_ops::Op>::DECL,
+                );
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(info) };
+                let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(
+                    info,
+                );
+                let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                    info,
+                );
+                let Some(self_) = deno_core::_ops::try_unwrap_cppgc_object::<
+                    Foo,
+                >(&mut scope, args.this().into()) else {
+                    let msg = deno_core::v8::String::new_from_one_byte(
+                            &mut scope,
+                            "expected Foo".as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return 1;
+                };
+                let self_ = &*self_;
+                let result = { Foo::call(self_) };
+                deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv);
+                return 0;
+            }
+            extern "C" fn v8_fn_ptr<'s>(
+                info: *const deno_core::v8::FunctionCallbackInfo,
+            ) {
+                let info: &'s _ = unsafe { &*info };
+                Self::slow_function_impl(info);
+            }
+            extern "C" fn v8_fn_ptr_metrics<'s>(
+                info: *const deno_core::v8::FunctionCallbackInfo,
+            ) {
+                let info: &'s _ = unsafe { &*info };
+                let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                    info,
+                );
+                let opctx: &'s _ = unsafe {
+                    &*(deno_core::v8::Local::<
+                        deno_core::v8::External,
+                    >::cast_unchecked(args.data())
+                        .value() as *const deno_core::_ops::OpCtx)
+                };
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Dispatched,
+                );
+                let res = Self::slow_function_impl(info);
+                if res == 0 {
+                    deno_core::_ops::dispatch_metrics_slow(
+                        opctx,
+                        deno_core::_ops::OpMetricsEvent::Completed,
+                    );
+                } else {
+                    deno_core::_ops::dispatch_metrics_slow(
+                        opctx,
+                        deno_core::_ops::OpMetricsEvent::Error,
+                    );
+                }
+            }
+        }
+        trait Callable {
+            fn call(&self);
+        }
+        impl Callable for Foo {
+            #[inline(always)]
+            fn call(&self) {}
+        }
+        <doThing as ::deno_core::_ops::Op>::DECL
+    }
 }
diff --git a/ops/op2/test_cases/sync/object_wrap.rs b/ops/op2/test_cases/sync/object_wrap.rs
index 73b5207..7d19a73 100644
--- a/ops/op2/test_cases/sync/object_wrap.rs
+++ b/ops/op2/test_cases/sync/object_wrap.rs
@@ -50,4 +50,11 @@ impl Foo {
   #[nofast]
   #[rename("with_RENAME")]
   fn with_rename(&self) {}
+
+  #[nofast]
+  #[static_method]
+  fn do_thing() {}
+
+  #[nofast]
+  fn do_thing(&self) {}
 }
diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs
index 095aba4..102bbff 100644
--- a/testing/checkin/runner/ops.rs
+++ b/testing/checkin/runner/ops.rs
@@ -105,6 +105,31 @@ pub struct DOMPoint {
 
 impl GarbageCollected for DOMPoint {}
 
+impl DOMPoint {
+  fn from_point_inner(
+    scope: &mut v8::HandleScope,
+    other: v8::Local<v8::Object>,
+  ) -> Result<DOMPoint, AnyError> {
+    fn get(
+      scope: &mut v8::HandleScope,
+      other: v8::Local<v8::Object>,
+      key: &str,
+    ) -> Option<f64> {
+      let key = v8::String::new(scope, key).unwrap();
+      other
+        .get(scope, key.into())
+        .map(|x| x.to_number(scope).unwrap().value())
+    }
+
+    Ok(DOMPoint {
+      x: get(scope, other, "x").unwrap_or(0.0),
+      y: get(scope, other, "y").unwrap_or(0.0),
+      z: get(scope, other, "z").unwrap_or(0.0),
+      w: get(scope, other, "w").unwrap_or(0.0),
+    })
+  }
+}
+
 #[op2]
 impl DOMPoint {
   #[constructor]
@@ -130,23 +155,17 @@ impl DOMPoint {
     scope: &mut v8::HandleScope,
     other: v8::Local<v8::Object>,
   ) -> Result<DOMPoint, AnyError> {
-    fn get(
-      scope: &mut v8::HandleScope,
-      other: v8::Local<v8::Object>,
-      key: &str,
-    ) -> Option<f64> {
-      let key = v8::String::new(scope, key).unwrap();
-      other
-        .get(scope, key.into())
-        .map(|x| x.to_number(scope).unwrap().value())
-    }
+    DOMPoint::from_point_inner(scope, other)
+  }
 
-    Ok(DOMPoint {
-      x: get(scope, other, "x").unwrap_or(0.0),
-      y: get(scope, other, "y").unwrap_or(0.0),
-      z: get(scope, other, "z").unwrap_or(0.0),
-      w: get(scope, other, "w").unwrap_or(0.0),
-    })
+  #[required(1)]
+  #[cppgc]
+  fn from_point(
+    &self,
+    scope: &mut v8::HandleScope,
+    other: v8::Local<v8::Object>,
+  ) -> Result<DOMPoint, AnyError> {
+    DOMPoint::from_point_inner(scope, other)
   }
 
   #[getter]
@@ -165,6 +184,8 @@ impl DOMPoint {
   fn w(&self) -> f64 {
     self.w
   }
+
+  #[fast]
   #[getter]
   fn z(&self) -> f64 {
     self.z
diff --git a/testing/checkin/runner/ts_module_loader.rs b/testing/checkin/runner/ts_module_loader.rs
index 4d4e53d..0e8930a 100644
--- a/testing/checkin/runner/ts_module_loader.rs
+++ b/testing/checkin/runner/ts_module_loader.rs
@@ -1,4 +1,6 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::borrow::Cow;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fs;
@@ -152,8 +154,12 @@ impl ModuleLoader for TypescriptModuleLoader {
     ))
   }
 
-  fn get_source_map(&self, specifier: &str) -> Option<Vec<u8>> {
-    self.source_maps.borrow().get(specifier).cloned()
+  fn get_source_map(&self, specifier: &str) -> Option<Cow<[u8]>> {
+    self
+      .source_maps
+      .borrow()
+      .get(specifier)
+      .map(|v| v.clone().into())
   }
 }
 
diff --git a/testing/ops.d.ts b/testing/ops.d.ts
index 823cd13..5561fab 100644
--- a/testing/ops.d.ts
+++ b/testing/ops.d.ts
@@ -25,6 +25,9 @@ export class DOMPoint {
   static fromPoint(
     other: { x?: number; y?: number; z?: number; w?: number },
   ): DOMPoint;
+  fromPoint(
+    other: { x?: number; y?: number; z?: number; w?: number },
+  ): DOMPoint;
   get x(): number;
   get y(): number;
   get z(): number;
diff --git a/testing/unit/resource_test.ts b/testing/unit/resource_test.ts
index 8703633..dbda74c 100644
--- a/testing/unit/resource_test.ts
+++ b/testing/unit/resource_test.ts
@@ -69,10 +69,12 @@ test(function testDomPoint() {
   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 });
   assertEquals(p1.x, 100);
   assertEquals(p2.x, 0);
   assertEquals(p3.x, 200);
   assertEquals(p4.x, 0);
+  assertEquals(p5.x, 200);
 
   let caught;
   try {