@@ -75,7 +75,7 @@ pub fn jsg_struct(attr: TokenStream, item: TokenStream) -> TokenStream {
7575 . expect ( "Named fields must have identifiers" )
7676 . to_string ( ) ;
7777 Some ( quote ! {
78- let #field_name = jsg:: v8:: ToLocalValue :: to_local( & self . #field_name, lock) ;
78+ let #field_name = jsg:: v8:: ToLocalValue :: to_local( & this . #field_name, lock) ;
7979 obj. set( lock, #field_name_str, #field_name) ;
8080 } )
8181 } else {
@@ -87,11 +87,13 @@ pub fn jsg_struct(attr: TokenStream, item: TokenStream) -> TokenStream {
8787 #input
8888
8989 impl jsg:: Type for #name {
90+ type This = Self ;
91+
9092 fn class_name( ) -> & ' static str {
9193 #class_name
9294 }
9395
94- fn wrap<' a, ' b>( & self , lock: & ' a mut jsg:: Lock ) -> jsg:: v8:: Local <' b, jsg:: v8:: Value >
96+ fn wrap<' a, ' b>( this : Self :: This , lock: & ' a mut jsg:: Lock ) -> jsg:: v8:: Local <' b, jsg:: v8:: Value >
9597 where
9698 ' b: ' a,
9799 {
@@ -104,6 +106,15 @@ pub fn jsg_struct(attr: TokenStream, item: TokenStream) -> TokenStream {
104106 obj. into( )
105107 }
106108 }
109+
110+ fn is_exact( value: & jsg:: v8:: Local <jsg:: v8:: Value >) -> bool {
111+ value. is_object( )
112+ }
113+
114+ fn unwrap( _isolate: jsg:: v8:: IsolatePtr , _value: jsg:: v8:: Local <jsg:: v8:: Value >) -> Self {
115+ // TODO(soon): Implement proper unwrapping for struct types
116+ unimplemented!( "Struct unwrap is not yet supported" )
117+ }
107118 }
108119
109120 impl jsg:: Struct for #name {
@@ -119,16 +130,47 @@ pub fn jsg_struct(attr: TokenStream, item: TokenStream) -> TokenStream {
119130/// Creates a `{method_name}_callback` extern "C" function that bridges JavaScript and Rust.
120131/// If no name is provided, automatically converts `snake_case` to `camelCase`.
121132///
133+ /// # Supported Parameter Types
134+ ///
135+ /// | Type | `T` | `NonCoercible<T>` |
136+ /// |------------|---------------|-------------------|
137+ /// | `&str` | ✅ Supported | ❌ |
138+ /// | `T: Type` | ❌ | ✅ Supported |
139+ ///
140+ /// Any type implementing [`jsg::Type`] can be used with `NonCoercible<T>`.
141+ /// Built-in types include `String`, `bool`, and `f64`.
142+ ///
143+ /// # `NonCoercible<T>` Parameters
144+ ///
145+ /// Use `NonCoercible<T>` when you want to accept a value only if it's already the expected
146+ /// type, without JavaScript's automatic type coercion:
147+ ///
148+ /// ```ignore
149+ /// #[jsg_method]
150+ /// pub fn strict_string(&self, param: NonCoercible<String>) -> Result<String, Error> {
151+ /// // Only accepts actual strings - passing null/undefined/numbers will throw
152+ /// // Access via Deref: *param, or via AsRef: param.as_ref()
153+ /// Ok(param.as_ref().clone())
154+ /// }
155+ ///
156+ /// #[jsg_method]
157+ /// pub fn strict_bool(&self, param: NonCoercible<bool>) -> Result<bool, Error> {
158+ /// // Only accepts actual booleans
159+ /// Ok(*param)
160+ /// }
161+ /// ```
162+ ///
122163/// # Example
123- /// ```rust
164+ ///
165+ /// ```ignore
124166/// // With explicit name
125- /// #[jsg::method (name = "parseRecord")]
167+ /// #[jsg_method (name = "parseRecord")]
126168/// pub fn parse_record(&self, data: &str) -> Result<Record, Error> {
127169/// // implementation
128170/// }
129171///
130172/// // Without name - automatically becomes "parseRecord"
131- /// #[jsg::method ]
173+ /// #[jsg_method ]
132174/// pub fn parse_record(&self, data: &str) -> Result<Record, Error> {
133175/// // implementation
134176/// }
@@ -210,11 +252,34 @@ fn is_str_reference(ty: &Type) -> bool {
210252 }
211253}
212254
255+ /// Returns true if the type is `NonCoercible<T>`.
256+ fn is_non_coercible ( ty : & Type ) -> bool {
257+ let Type :: Path ( type_path) = ty else {
258+ return false ;
259+ } ;
260+ type_path
261+ . path
262+ . segments
263+ . last ( )
264+ . is_some_and ( |seg| seg. ident == "NonCoercible" )
265+ }
266+
213267fn generate_unwrap_code (
214268 arg_name : & syn:: Ident ,
215269 ty : & Type ,
216270 index : usize ,
217271) -> quote:: __private:: TokenStream {
272+ // Check for NonCoercible<T> types
273+ if is_non_coercible ( ty) {
274+ return quote ! {
275+ let Some ( #arg_name) = ( unsafe {
276+ <#ty>:: unwrap( & mut lock, args. get( #index) )
277+ } ) else {
278+ return ;
279+ } ;
280+ } ;
281+ }
282+
218283 if is_str_reference ( ty) {
219284 quote ! {
220285 let #arg_name = unsafe {
@@ -223,7 +288,7 @@ fn generate_unwrap_code(
223288 }
224289 } else {
225290 quote ! {
226- compile_error!( "Unsupported parameter type for jsg::method. Currently only &str is supported." ) ;
291+ compile_error!( "Unsupported parameter type for jsg::method. Currently only &str and NonCoercible<T> are supported." ) ;
227292 }
228293 }
229294}
@@ -294,16 +359,27 @@ pub fn jsg_resource(attr: TokenStream, item: TokenStream) -> TokenStream {
294359
295360 #[ automatically_derived]
296361 impl jsg:: Type for #name {
362+ type This = jsg:: Ref <Self >;
363+
297364 fn class_name( ) -> & ' static str {
298365 #class_name
299366 }
300367
301- fn wrap<' a, ' b>( & self , lock : & ' a mut jsg:: Lock ) -> jsg:: v8:: Local <' b, jsg:: v8:: Value >
368+ fn wrap<' a, ' b>( _this : Self :: This , _lock : & ' a mut jsg:: Lock ) -> jsg:: v8:: Local <' b, jsg:: v8:: Value >
302369 where
303370 ' b: ' a,
304371 {
305372 todo!( "Implement wrap for jsg::Resource" )
306373 }
374+
375+ fn is_exact( value: & jsg:: v8:: Local <jsg:: v8:: Value >) -> bool {
376+ value. is_object( )
377+ }
378+
379+ fn unwrap( _isolate: jsg:: v8:: IsolatePtr , _value: jsg:: v8:: Local <jsg:: v8:: Value >) -> Self {
380+ // TODO(soon): Implement proper unwrapping for resource types
381+ unimplemented!( "Resource unwrap is not yet supported" )
382+ }
307383 }
308384
309385 #[ automatically_derived]
@@ -353,7 +429,7 @@ fn generate_resource_impl(impl_block: &ItemImpl) -> TokenStream {
353429
354430 method_registrations. push ( quote ! {
355431 jsg:: Member :: Method {
356- name: #js_name,
432+ name: #js_name. to_owned ( ) ,
357433 callback: Self :: #callback_name,
358434 }
359435 } ) ;
0 commit comments