Repository: denoland/deno_core
Pull request #1035 (2025-01-07 23:50:16)

perf: dynamically dispatch context function in `WebIDLConverter`
Avoids re-instantiating the converters with different generic arguments, saving on binary size.

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/denoland/deno_core/pull/1035?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) Report
Attention: Patch coverage is `82.35294%` with `27 lines` in your changes missing coverage. Please review.
> Project coverage is 81.72%. 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 [(`109ec40`)](https://app.codecov.io/gh/denoland/deno_core/commit/109ec4022046ed8b1e3573d5a4cd881d5b490973?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).
> Report is 223 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/denoland/deno_core/pull/1035?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) | Patch % | Lines |
|---|---|---|
| [core/webidl.rs](https://app.codecov.io/gh/denoland/deno_core/pull/1035?src=pr&el=tree&filepath=core%2Fwebidl.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS93ZWJpZGwucnM=) | 81.25% | [27 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/1035?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |

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


```diff
@@            Coverage Diff             @@
##             main    #1035      +/-   ##
==========================================
+ Coverage   81.43%   81.72%   +0.28%     
==========================================
  Files          97      103       +6     
  Lines       23877    27864    +3987     
==========================================
+ Hits        19445    22771    +3326     
- Misses       4432     5093     +661     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/denoland/deno_core/pull/1035?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).


Patch:
diff --git a/core/extension_set.rs b/core/extension_set.rs
index efeb683..7fca10e 100644
--- a/core/extension_set.rs
+++ b/core/extension_set.rs
@@ -166,10 +166,13 @@ pub fn create_op_ctxs(
   };
 
   for (index, decl) in op_method_decls.iter_mut().enumerate() {
-    decl.constructor.name = decl.name.0;
-    decl.constructor.name_fast = decl.name.1;
+    if let Some(mut constructor) = decl.constructor {
+      constructor.name = decl.name.0;
+      constructor.name_fast = decl.name.1;
+
+      op_ctxs.push(create_ctx(index, constructor));
+    }
 
-    op_ctxs.push(create_ctx(index, decl.constructor));
     for method in decl.methods {
       op_ctxs.push(create_ctx(index, *method));
     }
diff --git a/core/extensions.rs b/core/extensions.rs
index e2864d9..be959c2 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -182,7 +182,7 @@ const NOOP_FN: CFunction = CFunction::new(
 pub struct OpMethodDecl {
   pub type_name: fn() -> &'static str,
   pub name: (&'static str, FastStaticString),
-  pub constructor: OpDecl,
+  pub constructor: Option<OpDecl>,
   pub methods: &'static [OpDecl],
   pub static_methods: &'static [OpDecl],
 }
diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs
index 37798b7..df91140 100644
--- a/core/runtime/bindings.rs
+++ b/core/runtime/bindings.rs
@@ -375,14 +375,22 @@ pub(crate) fn initialize_deno_core_ops_bindings<'s>(
     if index == methods_ctx_offset {
       break;
     }
-    let constructor_ctx = &op_ctxs[index];
 
-    let tmpl = op_ctx_template(scope, constructor_ctx);
-    let prototype = tmpl.prototype_template(scope);
-    let key = decl.constructor.name_fast.v8_string(scope).unwrap();
+    let tmpl = if decl.constructor.is_some() {
+      let constructor_ctx = &op_ctxs[index];
 
-    index += 1;
+      let tmpl = op_ctx_template(scope, constructor_ctx);
+
+      index += 1;
+
+      tmpl
+    } else {
+      crate::cppgc::make_cppgc_template(scope)
+    };
+
+    let key = decl.name.1.v8_string(scope).unwrap();
 
+    let prototype = tmpl.prototype_template(scope);
     let method_ctxs = &op_ctxs[index..index + decl.methods.len()];
 
     let accessor_store = create_accessor_store(method_ctxs);
@@ -1030,12 +1038,16 @@ pub fn create_exports_for_ops_virtual_module<'s>(
     if index == methods_ctx_offset {
       break;
     }
-    let constructor_ctx = &op_ctxs[index];
 
-    let op_fn = get(scope, ops_obj, constructor_ctx.decl.name_fast, "op");
-    exports.push((constructor_ctx.decl.name_fast, op_fn));
+    if decl.constructor.is_some() {
+      index += 1;
+    }
+
+    let name = decl.name.1;
+    let op_fn = get(scope, ops_obj, name, "op");
+    exports.push((name, op_fn));
 
-    index += 1 + decl.methods.len() + decl.static_methods.len();
+    index += decl.methods.len() + decl.static_methods.len();
   }
 
   let op_ctxs = &op_ctxs[index..];
diff --git a/core/webidl.rs b/core/webidl.rs
index 5fe3cca..f0cdd37 100644
--- a/core/webidl.rs
+++ b/core/webidl.rs
@@ -13,22 +13,72 @@ pub struct WebIdlError {
   pub kind: WebIdlErrorKind,
 }
 
+type DynContextFn<'a> = dyn Fn() -> Cow<'static, str> + 'a;
+
+enum ContextFnInner<'a> {
+  Borrowed(&'a DynContextFn<'a>),
+  Owned(Box<DynContextFn<'a>>),
+}
+
+/// A function that returns a context string for an error.
+///
+/// When possible, prefer to use `ContextFn::new_borrowed` when creating a new context function
+/// to avoid unnecessary allocations.
+///
+/// To pass a borrow of the context function, use `ContextFn::borrowed`.
+pub struct ContextFn<'a>(ContextFnInner<'a>);
+
+impl<'a, T> From<T> for ContextFn<'a>
+where
+  T: Fn() -> Cow<'static, str> + 'a,
+{
+  fn from(f: T) -> Self {
+    Self(ContextFnInner::Owned(Box::new(f)))
+  }
+}
+
+impl<'a> ContextFn<'a> {
+  pub fn call(&self) -> Cow<'static, str> {
+    match &self.0 {
+      ContextFnInner::Borrowed(b) => b(),
+      ContextFnInner::Owned(b) => b(),
+    }
+  }
+
+  pub fn new(f: impl Fn() -> Cow<'static, str> + 'a) -> Self {
+    Self(ContextFnInner::Owned(Box::new(f)))
+  }
+
+  pub fn new_borrowed(b: &'a DynContextFn<'a>) -> Self {
+    Self(ContextFnInner::Borrowed(b))
+  }
+}
+
+impl<'a> ContextFn<'a> {
+  pub fn borrowed(&'a self) -> ContextFn<'a> {
+    match self {
+      Self(ContextFnInner::Borrowed(b)) => Self(ContextFnInner::Borrowed(*b)),
+      Self(ContextFnInner::Owned(b)) => Self(ContextFnInner::Borrowed(&**b)),
+    }
+  }
+}
+
 impl WebIdlError {
   pub fn new(
     prefix: Cow<'static, str>,
-    context: &impl Fn() -> Cow<'static, str>,
+    context: ContextFn<'_>,
     kind: WebIdlErrorKind,
   ) -> Self {
     Self {
       prefix,
-      context: context(),
+      context: context.call(),
       kind,
     }
   }
 
   pub fn other<T: std::error::Error + Send + Sync + 'static>(
     prefix: Cow<'static, str>,
-    context: &impl Fn() -> Cow<'static, str>,
+    context: ContextFn<'_>,
     other: T,
   ) -> Self {
     Self::new(prefix, context, WebIdlErrorKind::Other(Box::new(other)))
@@ -117,15 +167,15 @@ pub fn type_of<'a>(
 pub trait WebIdlConverter<'a>: Sized {
   type Options: Default;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>;
+  ) -> Result<Self, WebIdlError>;
+  // where
+  // C: Fn() -> Cow<'static, str>;
 }
 
 // Option's None is treated as undefined. this behaviour differs from a nullable
@@ -133,16 +183,13 @@ pub trait WebIdlConverter<'a>: Sized {
 impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Option<T> {
   type Options = T::Options;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     if value.is_undefined() {
       Ok(None)
     } else {
@@ -157,16 +204,13 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Option<T> {
 impl<'a> WebIdlConverter<'a> for Local<'a, Value> {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     _scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     _prefix: Cow<'static, str>,
-    _context: C,
+    _context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     Ok(value)
   }
 }
@@ -188,16 +232,13 @@ impl<T> Nullable<T> {
 impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Nullable<T> {
   type Options = T::Options;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     if value.is_null_or_undefined() {
       Ok(Self::Null)
     } else {
@@ -224,20 +265,17 @@ thread_local! {
 impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
   type Options = T::Options;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Some(obj) = value.to_object(scope) else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("sequence"),
       ));
     };
@@ -251,7 +289,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
     else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("sequence"),
       ));
     };
@@ -263,9 +301,9 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
         if let Some(key) = eternal.get(scope) {
           Ok(key)
         } else {
-          let key = NEXT
-            .v8_string(scope)
-            .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?;
+          let key = NEXT.v8_string(scope).map_err(|e| {
+            WebIdlError::other(prefix.clone(), context.borrowed(), e)
+          })?;
           eternal.set(scope, key);
           Ok(key)
         }
@@ -277,9 +315,9 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
         if let Some(key) = eternal.get(scope) {
           Ok(key)
         } else {
-          let key = DONE
-            .v8_string(scope)
-            .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?;
+          let key = DONE.v8_string(scope).map_err(|e| {
+            WebIdlError::other(prefix.clone(), context.borrowed(), e)
+          })?;
           eternal.set(scope, key);
           Ok(key)
         }
@@ -291,9 +329,9 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
         if let Some(key) = eternal.get(scope) {
           Ok(key)
         } else {
-          let key = VALUE
-            .v8_string(scope)
-            .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?;
+          let key = VALUE.v8_string(scope).map_err(|e| {
+            WebIdlError::other(prefix.clone(), context.borrowed(), e)
+          })?;
           eternal.set(scope, key);
           Ok(key)
         }
@@ -309,7 +347,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
       else {
         return Err(WebIdlError::new(
           prefix,
-          &context,
+          context.borrowed(),
           WebIdlErrorKind::ConvertToConverterType("sequence"),
         ));
       };
@@ -321,7 +359,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
       let Some(iter_val) = res.get(scope, value_key) else {
         return Err(WebIdlError::new(
           prefix,
-          &context,
+          context.borrowed(),
           WebIdlErrorKind::ConvertToConverterType("sequence"),
         ));
       };
@@ -330,7 +368,9 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec<T> {
         scope,
         iter_val,
         prefix.clone(),
-        || format!("{}, index {}", context(), out.len()).into(),
+        ContextFn::new_borrowed(&|| {
+          format!("{}, index {}", context.call(), out.len()).into()
+        }),
         options,
       )?);
     }
@@ -349,20 +389,17 @@ impl<
 {
   type Options = V::Options;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Ok(obj) = value.try_cast::<v8::Object>() else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("record"),
       ));
     };
@@ -399,14 +436,14 @@ impl<
         scope,
         key,
         prefix.clone(),
-        &context,
+        context.borrowed(),
         &Default::default(),
       )?;
       let value = WebIdlConverter::convert(
         scope,
         value,
         prefix.clone(),
-        &context,
+        context.borrowed(),
         options,
       )?;
 
@@ -431,25 +468,23 @@ macro_rules! impl_ints {
         type Options = IntOptions;
 
         #[allow(clippy::manual_range_contains)]
-        fn convert<C>(
+        fn convert<'b>(
           scope: &mut HandleScope<'a>,
           value: Local<'a, Value>,
           prefix: Cow<'static, str>,
-          context: C,
+          context: ContextFn<'b>,
           options: &Self::Options,
         ) -> Result<Self, WebIdlError>
-        where
-          C: Fn() -> Cow<'static, str>,
         {
           const MIN: f64 = $min as f64;
           const MAX: f64 = $max as f64;
 
           if value.is_big_int() {
-            return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name)));
+            return Err(WebIdlError::new(prefix, context.borrowed(), WebIdlErrorKind::ConvertToConverterType($name)));
           }
 
           let Some(mut n) = value.number_value(scope) else {
-            return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name)));
+            return Err(WebIdlError::new(prefix, context.borrowed(), WebIdlErrorKind::ConvertToConverterType($name)));
           };
           if n == -0.0 {
             n = 0.0;
@@ -457,7 +492,7 @@ macro_rules! impl_ints {
 
           if options.enforce_range {
             if !n.is_finite() {
-              return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::NotFinite));
+              return Err(WebIdlError::new(prefix, context.borrowed(), WebIdlErrorKind::NotFinite));
             }
 
             n = n.trunc();
@@ -466,7 +501,7 @@ macro_rules! impl_ints {
             }
 
             if n < MIN || n > MAX {
-              return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntRange {
+              return Err(WebIdlError::new(prefix, context.borrowed(), WebIdlErrorKind::IntRange {
                 lower_bound: MIN,
                 upper_bound: MAX,
               }));
@@ -539,20 +574,17 @@ impl_ints!(
 impl<'a> WebIdlConverter<'a> for f32 {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Some(n) = value.number_value(scope) else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("float"),
       ));
     };
@@ -560,7 +592,7 @@ impl<'a> WebIdlConverter<'a> for f32 {
     if !n.is_finite() {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::NotFinite,
       ));
     }
@@ -570,7 +602,7 @@ impl<'a> WebIdlConverter<'a> for f32 {
     if !n.is_finite() {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::Precision,
       ));
     }
@@ -591,20 +623,17 @@ impl std::ops::Deref for UnrestrictedFloat {
 impl<'a> WebIdlConverter<'a> for UnrestrictedFloat {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Some(n) = value.number_value(scope) else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("float"),
       ));
     };
@@ -617,20 +646,17 @@ impl<'a> WebIdlConverter<'a> for UnrestrictedFloat {
 impl<'a> WebIdlConverter<'a> for f64 {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Some(n) = value.number_value(scope) else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("float"),
       ));
     };
@@ -638,7 +664,7 @@ impl<'a> WebIdlConverter<'a> for f64 {
     if !n.is_finite() {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::NotFinite,
       ));
     }
@@ -659,20 +685,17 @@ impl std::ops::Deref for UnrestrictedDouble {
 impl<'a> WebIdlConverter<'a> for UnrestrictedDouble {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Some(n) = value.number_value(scope) else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("float"),
       ));
     };
@@ -690,20 +713,17 @@ pub struct BigInt {
 impl<'a> WebIdlConverter<'a> for BigInt {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let Some(bigint) = value.to_big_int(scope) else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("bigint"),
       ));
     };
@@ -717,16 +737,13 @@ impl<'a> WebIdlConverter<'a> for BigInt {
 impl<'a> WebIdlConverter<'a> for bool {
   type Options = ();
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     _prefix: Cow<'static, str>,
-    _context: C,
+    _context: ContextFn<'b>,
     _options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     Ok(value.to_boolean(scope).is_true())
   }
 }
@@ -740,16 +757,13 @@ pub struct StringOptions {
 impl<'a> WebIdlConverter<'a> for String {
   type Options = StringOptions;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let str = if value.is_string() {
       value.try_cast::<v8::String>().unwrap()
     } else if value.is_null() && options.treat_null_as_empty_string {
@@ -757,7 +771,7 @@ impl<'a> WebIdlConverter<'a> for String {
     } else if value.is_symbol() {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("string"),
       ));
     } else if let Some(str) = value.to_string(scope) {
@@ -765,7 +779,7 @@ impl<'a> WebIdlConverter<'a> for String {
     } else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("string"),
       ));
     };
@@ -785,16 +799,13 @@ impl std::ops::Deref for ByteString {
 impl<'a> WebIdlConverter<'a> for ByteString {
   type Options = StringOptions;
 
-  fn convert<C>(
+  fn convert<'b>(
     scope: &mut HandleScope<'a>,
     value: Local<'a, Value>,
     prefix: Cow<'static, str>,
-    context: C,
+    context: ContextFn<'b>,
     options: &Self::Options,
-  ) -> Result<Self, WebIdlError>
-  where
-    C: Fn() -> Cow<'static, str>,
-  {
+  ) -> Result<Self, WebIdlError> {
     let str = if value.is_string() {
       value.try_cast::<v8::String>().unwrap()
     } else if value.is_null() && options.treat_null_as_empty_string {
@@ -802,7 +813,7 @@ impl<'a> WebIdlConverter<'a> for ByteString {
     } else if value.is_symbol() {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("string"),
       ));
     } else if let Some(str) = value.to_string(scope) {
@@ -810,7 +821,7 @@ impl<'a> WebIdlConverter<'a> for ByteString {
     } else {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::ConvertToConverterType("string"),
       ));
     };
@@ -818,7 +829,7 @@ impl<'a> WebIdlConverter<'a> for ByteString {
     if !str.contains_only_onebyte() {
       return Err(WebIdlError::new(
         prefix,
-        &context,
+        context.borrowed(),
         WebIdlErrorKind::InvalidByteString,
       ));
     }
@@ -852,7 +863,7 @@ mod tests {
             scope,
             val.into(),
             "prefix".into(),
-            || "context".into(),
+            ContextFn::from(|| "context".into()),
             &test_integer!(@opts $($opts)?),
           );
           assert_eq!(converted.unwrap(), $expected);
@@ -866,7 +877,7 @@ mod tests {
             scope,
             val.into(),
             "prefix".into(),
-            || "context".into(),
+            ContextFn::from(|| "context".into()),
             &test_integer!(@opts $($opts)?),
           );
           assert!(converted.is_err());
@@ -912,7 +923,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), 3);
@@ -922,7 +933,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), 0);
@@ -932,7 +943,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -942,7 +953,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -952,7 +963,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), 0);
@@ -968,7 +979,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), 3.0);
@@ -978,7 +989,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -988,7 +999,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -1004,7 +1015,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), 3.0);
@@ -1014,7 +1025,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), f32::INFINITY);
@@ -1024,7 +1035,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
 
@@ -1035,7 +1046,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.unwrap().is_infinite());
@@ -1051,7 +1062,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), 3.0);
@@ -1061,7 +1072,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -1071,7 +1082,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), f64::MAX);
@@ -1087,7 +1098,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), 3.0);
@@ -1097,7 +1108,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), f64::INFINITY);
@@ -1107,7 +1118,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
 
@@ -1118,7 +1129,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), f64::MAX);
@@ -1134,7 +1145,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), "foo");
@@ -1144,7 +1155,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), "1");
@@ -1154,7 +1165,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -1164,7 +1175,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), "null");
@@ -1174,7 +1185,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &StringOptions {
         treat_null_as_empty_string: true,
       },
@@ -1186,7 +1197,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &StringOptions {
         treat_null_as_empty_string: true,
       },
@@ -1198,7 +1209,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), "生");
@@ -1214,7 +1225,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), "foo");
@@ -1224,7 +1235,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), "1");
@@ -1234,7 +1245,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -1244,7 +1255,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(*converted.unwrap(), "null");
@@ -1254,7 +1265,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &StringOptions {
         treat_null_as_empty_string: true,
       },
@@ -1266,7 +1277,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &StringOptions {
         treat_null_as_empty_string: true,
       },
@@ -1278,7 +1289,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
@@ -1294,7 +1305,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.unwrap().is_object());
@@ -1312,7 +1323,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), vec![1, 2]);
@@ -1328,7 +1339,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), Nullable::Null);
@@ -1338,7 +1349,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), Nullable::Value(1));
@@ -1361,7 +1372,7 @@ mod tests {
       scope,
       obj.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     )
     .unwrap();
@@ -1400,7 +1411,7 @@ mod tests {
       scope,
       val,
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
 
@@ -1436,7 +1447,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), Enumeration::FooBar);
@@ -1446,7 +1457,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), Enumeration::Baz);
@@ -1456,7 +1467,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert_eq!(converted.unwrap(), Enumeration::World);
@@ -1466,7 +1477,7 @@ mod tests {
       scope,
       val.into(),
       "prefix".into(),
-      || "context".into(),
+      (|| "context".into()).into(),
       &Default::default(),
     );
     assert!(converted.is_err());
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 7aa86d7..d87a5ce 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -711,7 +711,7 @@ pub fn from_arg(
           &mut #scope,
           #arg_ident,
           #prefix.into(),
-          || std::borrow::Cow::Borrowed(#context),
+          deno_core::webidl::ContextFn::new_borrowed(&|| std::borrow::Cow::Borrowed(#context)),
           &#options,
         ) {
           Ok(t) => t,
diff --git a/ops/op2/object_wrap.rs b/ops/op2/object_wrap.rs
index 7f13b22..2d27c06 100644
--- a/ops/op2/object_wrap.rs
+++ b/ops/op2/object_wrap.rs
@@ -134,6 +134,12 @@ pub(crate) fn generate_impl_ops(
     }
   }
 
+  let constructor = if let Some(constructor) = constructor {
+    quote! { Some(#self_ty::#constructor()) }
+  } else {
+    quote! { None }
+  };
+
   let res = quote! {
       impl #self_ty {
         pub const DECL: deno_core::_ops::OpMethodDecl = deno_core::_ops::OpMethodDecl {
@@ -147,7 +153,7 @@ pub(crate) fn generate_impl_ops(
               #self_ty::#static_methods(),
             )*
           ],
-          constructor: #self_ty::#constructor(),
+          constructor: #constructor,
           name: ::deno_core::__op_name_fast!(#self_ty),
           type_name: || std::any::type_name::<#self_ty>(),
         };
diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs
index 1f2c787..db0a9b5 100644
--- a/ops/webidl/dictionary.rs
+++ b/ops/webidl/dictionary.rs
@@ -119,7 +119,7 @@ pub fn get_body(
       quote! {
         return Err(::deno_core::webidl::WebIdlError::new(
           __prefix,
-          &__context,
+          __context.borrowed(),
           ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey {
             converter: #ident_string,
             key: #string_name,
@@ -139,7 +139,7 @@ pub fn get_body(
             } else {
               let __key = #v8_static_name
                 .v8_string(__scope)
-                .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))?;
+                .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), __context.borrowed(), e))?;
               __eternal.set(__scope, __key);
               Ok(__key)
             }
@@ -151,7 +151,7 @@ pub fn get_body(
             __scope,
             __value,
             __prefix.clone(),
-            || format!("{} ({})", #new_context, __context()).into(),
+            ::deno_core::webidl::ContextFn::new_borrowed(&|| format!("{} ({})", #new_context, __context.call()).into()),
             &#options,
           )?
         } else {
@@ -178,7 +178,7 @@ pub fn get_body(
       } else {
         return Err(::deno_core::webidl::WebIdlError::new(
           __prefix,
-          &__context,
+          __context.borrowed(),
           ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary")
         ));
       }
diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs
index 056c547..2a86712 100644
--- a/ops/webidl/enum.rs
+++ b/ops/webidl/enum.rs
@@ -33,14 +33,14 @@ pub fn get_body(
     let Ok(str) = __value.try_cast::<::deno_core::v8::String>() else {
       return Err(::deno_core::webidl::WebIdlError::new(
         __prefix,
-        &__context,
+        __context,
         ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"),
       ));
     };
 
     match str.to_rust_string_lossy(__scope).as_str() {
       #(#variants),*,
-      s => Err(::deno_core::webidl::WebIdlError::new(__prefix, &__context, ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { converter: #ident_string, variant: s.to_string() }))
+      s => Err(::deno_core::webidl::WebIdlError::new(__prefix, __context, ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { converter: #ident_string, variant: s.to_string() }))
     }
   })
 }
diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs
index ea74021..0fa0bc5 100644
--- a/ops/webidl/mod.rs
+++ b/ops/webidl/mod.rs
@@ -98,15 +98,13 @@ fn create_impl(ident: Ident, body: TokenStream) -> TokenStream {
     impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident {
       type Options = ();
 
-      fn convert<C>(
+      fn convert<'b>(
         __scope: &mut ::deno_core::v8::HandleScope<'a>,
         __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>,
         __prefix: std::borrow::Cow<'static, str>,
-        __context: C,
+        __context: ::deno_core::webidl::ContextFn<'b>,
         __options: &Self::Options,
       ) -> Result<Self, ::deno_core::webidl::WebIdlError>
-      where
-        C: Fn() -> std::borrow::Cow<'static, str>,
       {
         #body
       }


Test patch:
diff --git a/ops/op2/test_cases/sync/object_wrap.out b/ops/op2/test_cases/sync/object_wrap.out
index a57f02e..1eaa1d7 100644
--- a/ops/op2/test_cases/sync/object_wrap.out
+++ b/ops/op2/test_cases/sync/object_wrap.out
@@ -10,7 +10,7 @@ impl Foo {
             Foo::doThing(),
         ],
         static_methods: &[Foo::__static_doThing()],
-        constructor: Foo::constructor(),
+        constructor: Some(Foo::constructor()),
         name: ::deno_core::__op_name_fast!(Foo),
         type_name: || std::any::type_name::<Foo>(),
     };
diff --git a/ops/op2/test_cases/sync/webidl.out b/ops/op2/test_cases/sync/webidl.out
index 805fca9..824a097 100644
--- a/ops/op2/test_cases/sync/webidl.out
+++ b/ops/op2/test_cases/sync/webidl.out
@@ -45,7 +45,9 @@ const fn op_webidl() -> ::deno_core::_ops::OpDecl {
                     &mut scope,
                     arg0,
                     "Failed to execute 'UNINIT.call'".into(),
-                    || std::borrow::Cow::Borrowed("Argument 1"),
+                    deno_core::webidl::ContextFn::new_borrowed(
+                        &|| std::borrow::Cow::Borrowed("Argument 1"),
+                    ),
                     &Default::default(),
                 ) {
                     Ok(t) => t,
@@ -59,7 +61,9 @@ const fn op_webidl() -> ::deno_core::_ops::OpDecl {
                     &mut scope,
                     arg1,
                     "Failed to execute 'UNINIT.call'".into(),
-                    || std::borrow::Cow::Borrowed("Argument 2"),
+                    deno_core::webidl::ContextFn::new_borrowed(
+                        &|| std::borrow::Cow::Borrowed("Argument 2"),
+                    ),
                     &Default::default(),
                 ) {
                     Ok(t) => t,
diff --git a/ops/webidl/test_cases/dict.out b/ops/webidl/test_cases/dict.out
index 68119d0..6aa0ec4 100644
--- a/ops/webidl/test_cases/dict.out
+++ b/ops/webidl/test_cases/dict.out
@@ -1,15 +1,12 @@
 impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
     type Options = ();
-    fn convert<C>(
+    fn convert<'b>(
         __scope: &mut ::deno_core::v8::HandleScope<'a>,
         __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>,
         __prefix: std::borrow::Cow<'static, str>,
-        __context: C,
+        __context: ::deno_core::webidl::ContextFn<'b>,
         __options: &Self::Options,
-    ) -> Result<Self, ::deno_core::webidl::WebIdlError>
-    where
-        C: Fn() -> std::borrow::Cow<'static, str>,
-    {
+    ) -> Result<Self, ::deno_core::webidl::WebIdlError> {
         ::deno_core::v8_static_strings! {
             __v8_static_a = "a", __v8_static_b = "b", __v8_static_c = "c", __v8_static_e
             = "e", __v8_static_f = "f"
@@ -32,7 +29,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                 return Err(
                     ::deno_core::webidl::WebIdlError::new(
                         __prefix,
-                        &__context,
+                        __context.borrowed(),
                         ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType(
                             "dictionary",
                         ),
@@ -50,7 +47,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                             .v8_string(__scope)
                             .map_err(|e| ::deno_core::webidl::WebIdlError::other(
                                 __prefix.clone(),
-                                &__context,
+                                __context.borrowed(),
                                 e,
                             ))?;
                         __eternal.set(__scope, __key);
@@ -66,14 +63,16 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                     __scope,
                     __value,
                     __prefix.clone(),
-                    || format!("{} ({})", "'a' of 'Dict'", __context()).into(),
+                    ::deno_core::webidl::ContextFn::new_borrowed(
+                        &|| format!("{} ({})", "'a' of 'Dict'", __context.call()).into(),
+                    ),
                     &Default::default(),
                 )?
             } else {
                 return Err(
                     ::deno_core::webidl::WebIdlError::new(
                         __prefix,
-                        &__context,
+                        __context.borrowed(),
                         ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey {
                             converter: "Dict",
                             key: "a",
@@ -92,7 +91,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                             .v8_string(__scope)
                             .map_err(|e| ::deno_core::webidl::WebIdlError::other(
                                 __prefix.clone(),
-                                &__context,
+                                __context.borrowed(),
                                 e,
                             ))?;
                         __eternal.set(__scope, __key);
@@ -108,7 +107,9 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                     __scope,
                     __value,
                     __prefix.clone(),
-                    || format!("{} ({})", "'b' of 'Dict'", __context()).into(),
+                    ::deno_core::webidl::ContextFn::new_borrowed(
+                        &|| format!("{} ({})", "'b' of 'Dict'", __context.call()).into(),
+                    ),
                     &{
                         type Alias<'a> = <Vec<
                             u16,
@@ -123,7 +124,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                 return Err(
                     ::deno_core::webidl::WebIdlError::new(
                         __prefix,
-                        &__context,
+                        __context.borrowed(),
                         ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey {
                             converter: "Dict",
                             key: "b",
@@ -142,7 +143,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                             .v8_string(__scope)
                             .map_err(|e| ::deno_core::webidl::WebIdlError::other(
                                 __prefix.clone(),
-                                &__context,
+                                __context.borrowed(),
                                 e,
                             ))?;
                         __eternal.set(__scope, __key);
@@ -161,7 +162,9 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                     __scope,
                     __value,
                     __prefix.clone(),
-                    || format!("{} ({})", "'c' of 'Dict'", __context()).into(),
+                    ::deno_core::webidl::ContextFn::new_borrowed(
+                        &|| format!("{} ({})", "'c' of 'Dict'", __context.call()).into(),
+                    ),
                     &Default::default(),
                 )?
             } else {
@@ -178,7 +181,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                             .v8_string(__scope)
                             .map_err(|e| ::deno_core::webidl::WebIdlError::other(
                                 __prefix.clone(),
-                                &__context,
+                                __context.borrowed(),
                                 e,
                             ))?;
                         __eternal.set(__scope, __key);
@@ -194,14 +197,16 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                     __scope,
                     __value,
                     __prefix.clone(),
-                    || format!("{} ({})", "'e' of 'Dict'", __context()).into(),
+                    ::deno_core::webidl::ContextFn::new_borrowed(
+                        &|| format!("{} ({})", "'e' of 'Dict'", __context.call()).into(),
+                    ),
                     &Default::default(),
                 )?
             } else {
                 return Err(
                     ::deno_core::webidl::WebIdlError::new(
                         __prefix,
-                        &__context,
+                        __context.borrowed(),
                         ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey {
                             converter: "Dict",
                             key: "e",
@@ -220,7 +225,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                             .v8_string(__scope)
                             .map_err(|e| ::deno_core::webidl::WebIdlError::other(
                                 __prefix.clone(),
-                                &__context,
+                                __context.borrowed(),
                                 e,
                             ))?;
                         __eternal.set(__scope, __key);
@@ -236,14 +241,16 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict {
                     __scope,
                     __value,
                     __prefix.clone(),
-                    || format!("{} ({})", "'f' of 'Dict'", __context()).into(),
+                    ::deno_core::webidl::ContextFn::new_borrowed(
+                        &|| format!("{} ({})", "'f' of 'Dict'", __context.call()).into(),
+                    ),
                     &Default::default(),
                 )?
             } else {
                 return Err(
                     ::deno_core::webidl::WebIdlError::new(
                         __prefix,
-                        &__context,
+                        __context.borrowed(),
                         ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey {
                             converter: "Dict",
                             key: "f",
diff --git a/ops/webidl/test_cases/enum.out b/ops/webidl/test_cases/enum.out
index dc09959..91077e0 100644
--- a/ops/webidl/test_cases/enum.out
+++ b/ops/webidl/test_cases/enum.out
@@ -1,20 +1,17 @@
 impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration {
     type Options = ();
-    fn convert<C>(
+    fn convert<'b>(
         __scope: &mut ::deno_core::v8::HandleScope<'a>,
         __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>,
         __prefix: std::borrow::Cow<'static, str>,
-        __context: C,
+        __context: ::deno_core::webidl::ContextFn<'b>,
         __options: &Self::Options,
-    ) -> Result<Self, ::deno_core::webidl::WebIdlError>
-    where
-        C: Fn() -> std::borrow::Cow<'static, str>,
-    {
+    ) -> Result<Self, ::deno_core::webidl::WebIdlError> {
         let Ok(str) = __value.try_cast::<::deno_core::v8::String>() else {
             return Err(
                 ::deno_core::webidl::WebIdlError::new(
                     __prefix,
-                    &__context,
+                    __context,
                     ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"),
                 ),
             );
@@ -27,7 +24,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration {
                 Err(
                     ::deno_core::webidl::WebIdlError::new(
                         __prefix,
-                        &__context,
+                        __context,
                         ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant {
                             converter: "Enumeration",
                             variant: s.to_string(),
diff --git a/testing/checkin/runner/extensions.rs b/testing/checkin/runner/extensions.rs
index 94094b0..42c0c75 100644
--- a/testing/checkin/runner/extensions.rs
+++ b/testing/checkin/runner/extensions.rs
@@ -52,7 +52,8 @@ deno_core::extension!(
   ],
   objects = [
     ops::DOMPoint,
-    ops::TestObjectWrap
+    ops::TestObjectWrap,
+    ops::TestEnumWrap
   ],
   esm_entry_point = "ext:checkin_runtime/__init.js",
   esm = [
diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs
index 6c4d521..4a9ac72 100644
--- a/testing/checkin/runner/ops.rs
+++ b/testing/checkin/runner/ops.rs
@@ -214,6 +214,23 @@ impl DOMPoint {
   fn with_symbol(&self) {}
 }
 
+#[repr(u8)]
+#[derive(Clone, Copy)]
+pub enum TestEnumWrap {
+  #[allow(dead_code)]
+  A,
+}
+
+impl GarbageCollected for TestEnumWrap {}
+
+#[op2]
+impl TestEnumWrap {
+  #[getter]
+  fn as_int(&self) -> u8 {
+    *self as u8
+  }
+}
+
 #[op2(fast)]
 pub fn op_nop_generic<T: SomeType + 'static>(state: &mut OpState) {
   state.take::<T>();
diff --git a/testing/checkin/runtime/object.ts b/testing/checkin/runtime/object.ts
index a4cd826..bc1782c 100644
--- a/testing/checkin/runtime/object.ts
+++ b/testing/checkin/runtime/object.ts
@@ -1,5 +1,5 @@
 // Copyright 2018-2025 the Deno authors. MIT license.
 
-import { DOMPoint, TestObjectWrap } from "ext:core/ops";
+import { DOMPoint, TestEnumWrap, TestObjectWrap } from "ext:core/ops";
 
-export { DOMPoint, TestObjectWrap };
+export { DOMPoint, TestEnumWrap, TestObjectWrap };
diff --git a/testing/ops.d.ts b/testing/ops.d.ts
index 63f9475..74a109a 100644
--- a/testing/ops.d.ts
+++ b/testing/ops.d.ts
@@ -41,3 +41,5 @@ export class TestObjectWrap {
   with_RENAME(): void;
   withAsyncFn(ms: number): Promise<void>;
 }
+
+export class TestEnumWrap {}
diff --git a/testing/unit/resource_test.ts b/testing/unit/resource_test.ts
index de9d2a1..3819239 100644
--- a/testing/unit/resource_test.ts
+++ b/testing/unit/resource_test.ts
@@ -1,6 +1,6 @@
 // Copyright 2018-2025 the Deno authors. MIT license.
 import { assert, assertArrayEquals, assertEquals, test } from "checkin:testing";
-import { DOMPoint, TestObjectWrap } from "checkin:object";
+import { DOMPoint, TestEnumWrap, TestObjectWrap } from "checkin:object";
 
 const {
   op_pipe_create,
@@ -110,4 +110,6 @@ test(async function testDomPoint() {
   assert(promise instanceof Promise);
 
   await promise;
+
+  new TestEnumWrap();
 });