From bb74ca4d156c56e78719f8d9350297e4b3f48da5 Mon Sep 17 00:00:00 2001 From: Willem Nordhus Date: Mon, 25 May 2026 11:09:16 -0400 Subject: [PATCH 1/6] Add PhantomData support for the GodotConvert derive macro on newtype structs --- .../src/derive/data_models/godot_convert.rs | 56 ++++---- .../src/derive/data_models/newtype.rs | 121 +++++++++++++++--- godot-macros/src/derive/derive_from_godot.rs | 22 +++- .../src/derive/derive_godot_convert.rs | 44 ++++++- godot-macros/src/derive/derive_to_godot.rs | 17 ++- .../derive_godotconvert_test.rs | 21 +++ 6 files changed, 232 insertions(+), 49 deletions(-) diff --git a/godot-macros/src/derive/data_models/godot_convert.rs b/godot-macros/src/derive/data_models/godot_convert.rs index 8de589e9e..71f72b5bd 100644 --- a/godot-macros/src/derive/data_models/godot_convert.rs +++ b/godot-macros/src/derive/data_models/godot_convert.rs @@ -7,6 +7,7 @@ use proc_macro2::{Ident, TokenStream}; use quote::ToTokens; +use venial::{GenericParamList, WhereClause}; use super::c_style_enum::CStyleEnum; use super::godot_attribute::{GodotAttribute, ViaType}; @@ -18,6 +19,8 @@ use crate::util::bail; pub struct GodotConvert { /// The name of the type we're deriving for. pub ty_name: Ident, + pub where_clause: Option, + pub generic_params: Option, /// The data from the type and `godot` attribute. pub convert_type: ConvertType, } @@ -30,11 +33,34 @@ impl GodotConvert { &struct_.where_clause, &struct_.generic_params, ), - venial::Item::Enum(enum_) => ( - enum_.name.clone(), - &enum_.where_clause, - &enum_.generic_params, - ), + venial::Item::Enum(enum_) => { + // These checks aren't really necessary anyways, because we're guaranteed to have + // only c-style enums anywas, and so rust would complain that the generics aren't + // being used. These error messages are a bit more clear with our intentions, + // though. + + if let Some(generic_params) = &enum_.generic_params { + return bail!( + generic_params, + "#[derive(GodotConvert)] does not support lifetimes or generic parameters on enums" + ); + } + + // Is this check even necessary? What's the use case of where clauses without generics? + // For traits, one can imagine `Self: SomeBound`, but for structs/enums? + if let Some(where_clause) = &enum_.where_clause { + return bail!( + where_clause, + "#[derive(GodotConvert)] does not support where clauses" + ); + } + + ( + enum_.name.clone(), + &enum_.where_clause, + &enum_.generic_params, + ) + } other => { return bail!( other, @@ -43,26 +69,12 @@ impl GodotConvert { } }; - if let Some(generic_params) = generic_params { - return bail!( - generic_params, - "#[derive(GodotConvert)] does not support lifetimes or generic parameters" - ); - } - - // Is this check even necessary? What's the use case of where clauses without generics? - // For traits, one can imagine `Self: SomeBound`, but for structs/enums? - if let Some(where_clause) = where_clause { - return bail!( - where_clause, - "#[derive(GodotConvert)] does not support where clauses" - ); - } - - let data = ConvertType::parse_declaration(item)?; + let data = ConvertType::parse_declaration(item.clone())?; Ok(Self { ty_name: name, + where_clause: where_clause.clone(), + generic_params: generic_params.clone(), convert_type: data, }) } diff --git a/godot-macros/src/derive/data_models/newtype.rs b/godot-macros/src/derive/data_models/newtype.rs index 9ed13cab5..f00ed073d 100644 --- a/godot-macros/src/derive/data_models/newtype.rs +++ b/godot-macros/src/derive/data_models/newtype.rs @@ -5,18 +5,32 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::quote; +use venial::{NamedField, TupleField}; use crate::ParseResult; use crate::util::bail; +pub enum FieldType { + Tuple(Literal), + Named(Ident), +} + +pub enum FieldsType { + Tuple(Vec), + Named(Vec), +} + /// Stores info from the field of a newtype struct for use in deriving `GodotConvert` and other related traits. pub struct NewtypeStruct { /// The name of the field. /// /// If `None`, then this represents a tuple-struct with one field. - pub name: Option, + pub name: FieldType, + + /// The names of the phantom fields. + pub phantom_names: FieldsType, /// The type of the field. pub ty: venial::TypeExpr, @@ -33,34 +47,99 @@ impl NewtypeStruct { "GodotConvert expects a struct with a single field, unit structs are currently not supported" ), venial::Fields::Tuple(fields) => { - if fields.fields.len() != 1 { + fn phantom_predicate(field: &TupleField) -> bool { + // Some types we don't care about are not paths, like references + if let Some(path) = field.ty.as_path() { + // This unwrap only fails if the field had no type specified, which isn't valid code anyways. + return path.segments.last().unwrap().ident + == Ident::new("PhantomData", Span::mixed_site()); + } + false + } + + let mut non_phantom_fields = fields + .fields + .items() + .enumerate() + .filter(|(_, field)| !phantom_predicate(field)); + + let maybe_field = non_phantom_fields.next(); + + let total_count = if maybe_field.is_none() { + 0 + } else { + non_phantom_fields.count() + 1 + }; + + if total_count != 1 { return bail!( &fields.fields, - "GodotConvert expects a struct with a single field, not {} fields", - fields.fields.len() + "GodotConvert expects a struct with a single non-PhantomData field, not {} fields", + total_count ); } - let (field, _) = fields.fields[0].clone(); + let (field_num, field) = maybe_field.unwrap(); + + let phantom_nums = (0..field_num) + .chain(field_num + 1..fields.fields.len()) + .map(Literal::usize_unsuffixed) + .collect(); Ok(NewtypeStruct { - name: None, - ty: field.ty, + name: FieldType::Tuple(Literal::usize_unsuffixed(field_num)), + phantom_names: FieldsType::Tuple(phantom_nums), + ty: field.ty.clone(), }) } venial::Fields::Named(fields) => { - if fields.fields.len() != 1 { + fn phantom_predicate(field: &NamedField) -> bool { + // Some types we don't care about are not paths, like references + if let Some(path) = field.ty.as_path() { + // This unwrap only fails if the field had no type specified, which isn't valid code anyways. + return path.segments.last().unwrap().ident + == Ident::new("PhantomData", Span::mixed_site()); + } + false + } + + let mut non_phantom_fields = fields + .fields + .items() + .filter(|field| !phantom_predicate(field)); + + let maybe_field = non_phantom_fields.next(); + + let total_count = if maybe_field.is_none() { + 0 + } else { + non_phantom_fields.count() + 1 + }; + + if total_count != 1 { return bail!( &fields.fields, - "GodotConvert expects a struct with a single field, not {} fields", - fields.fields.len() + "GodotConvert expects a struct with a single non-PhantomData field, not {} fields", + total_count ); } - let (field, _) = fields.fields[0].clone(); + let field = maybe_field.unwrap().clone(); + + let phantom_names = fields + .fields + .items() + .filter_map(|field| { + if phantom_predicate(field) { + return Some(field.name.clone()); + } + None + }) + .collect(); Ok(NewtypeStruct { - name: Some(field.name), + name: FieldType::Named(field.name), + phantom_names: FieldsType::Named(phantom_names), ty: field.ty, }) } @@ -69,7 +148,7 @@ impl NewtypeStruct { /// Gets the field name. /// - /// If this represents a tuple-struct, then it will return `0`. This can be used just like it was a named field with the name `0`. + /// If this represents a tuple-struct, then it will return a number. This can be used just like it was a named field. /// For instance: /// ``` /// struct Foo(i64); @@ -80,8 +159,18 @@ impl NewtypeStruct { /// ``` pub fn field_name(&self) -> TokenStream { match &self.name { - Some(name) => quote! { #name }, - None => quote! { 0 }, + FieldType::Named(name) => quote! { #name }, + FieldType::Tuple(num) => quote! { #num }, + } + } + + /// Gets the phantom field names. + /// + /// If this represents a tuple-struct, then it will return numbers. See `Self::field_name` + pub fn phantom_field_names(&self) -> Vec { + match &self.phantom_names { + FieldsType::Named(vec) => vec.iter().map(|ident| quote! {#ident}).collect(), + FieldsType::Tuple(vec) => vec.iter().map(|ident| quote! {#ident}).collect(), } } } diff --git a/godot-macros/src/derive/derive_from_godot.rs b/godot-macros/src/derive/derive_from_godot.rs index 53d8184f9..cbc5bd9ba 100644 --- a/godot-macros/src/derive/derive_from_godot.rs +++ b/godot-macros/src/derive/derive_from_godot.rs @@ -19,10 +19,11 @@ pub fn make_fromgodot(convert: &GodotConvert, cache: &mut EnumeratorExprCache) - let GodotConvert { ty_name: name, convert_type: data, + .. } = convert; match data { - ConvertType::NewType { field } => make_fromgodot_for_newtype_struct(name, field), + ConvertType::NewType { field } => make_fromgodot_for_newtype_struct(convert, field), ConvertType::Enum { variants, @@ -37,16 +38,29 @@ pub fn make_fromgodot(convert: &GodotConvert, cache: &mut EnumeratorExprCache) - } /// Derives `FromGodot` for newtype structs. -fn make_fromgodot_for_newtype_struct(name: &Ident, field: &NewtypeStruct) -> TokenStream { +fn make_fromgodot_for_newtype_struct(convert: &GodotConvert, field: &NewtypeStruct) -> TokenStream { // For tuple structs this ends up using the alternate tuple-struct constructor syntax of // TupleStruct { 0: value } + let GodotConvert { + ty_name: name, + generic_params, + where_clause, + .. + } = convert; + let generic_args = generic_params + .as_ref() + .map(|params| params.as_inline_args()); let field_name = field.field_name(); + let phantom_field_names = field.phantom_field_names(); let via_type = &field.ty; quote! { - impl ::godot::meta::FromGodot for #name { + impl #generic_params ::godot::meta::FromGodot for #name #generic_args #where_clause { fn try_from_godot(via: #via_type) -> ::std::result::Result { - Ok(Self { #field_name: via }) + Ok(Self { + #field_name: via, + #(#phantom_field_names: ::std::marker::PhantomData),* + }) } } } diff --git a/godot-macros/src/derive/derive_godot_convert.rs b/godot-macros/src/derive/derive_godot_convert.rs index 1db83751b..cd0a883cf 100644 --- a/godot-macros/src/derive/derive_godot_convert.rs +++ b/godot-macros/src/derive/derive_godot_convert.rs @@ -7,8 +7,9 @@ use std::collections::HashMap; -use proc_macro2::{Ident, TokenStream, TokenTree}; +use proc_macro2::{Ident, Punct, Spacing, TokenStream, TokenTree}; use quote::quote; +use venial::GenericBound; use crate::ParseResult; use crate::derive::data_models::{ConvertType, GodotConvert, ViaType}; @@ -20,7 +21,42 @@ use crate::derive::{make_fromgodot, make_togodot}; pub fn derive_godot_convert(item: venial::Item) -> ParseResult { let convert = GodotConvert::parse_declaration(item)?; - let name = &convert.ty_name; + let GodotConvert { + ty_name: name, + generic_params, + where_clause, + .. + } = &convert; + + let generic_args = generic_params + .as_ref() + .map(|params| params.as_inline_args()); + let element_generic_params = { + let mut maybe_params = generic_params.clone(); + + if let Some(params) = &mut maybe_params { + for (param, _) in params.params.iter_mut() { + if param.is_ty() || param.is_lifetime() { + if let Some(bound) = &mut param.bound { + // If we're in here we have at least 1 bound, and rust doesn't error if the + // bound is already 'static, i.e. `T: 'static + 'static` works. It's a + // little hacky, but there's not really a reason to inspect all of the + // bound's tokens if we really don't care what it is. + bound + .tokens + .append(&mut quote! {+ 'static}.into_iter().collect()); + } else { + param.bound = Some(GenericBound { + tk_colon: Punct::new(':', Spacing::Alone), + tokens: quote! {'static}.into_iter().collect(), + }); + } + } + } + } + maybe_params + }; + let via_type = convert.convert_type.via_type(); let mut cache = EnumeratorExprCache::default(); @@ -30,7 +66,7 @@ pub fn derive_godot_convert(item: venial::Item) -> ParseResult { let shape_override = make_shape_override(&convert.convert_type, &mut cache); Ok(quote! { - impl ::godot::meta::GodotConvert for #name { + impl #generic_params ::godot::meta::GodotConvert for #name #generic_args #where_clause { type Via = #via_type; #shape_override } @@ -39,7 +75,7 @@ pub fn derive_godot_convert(item: venial::Item) -> ParseResult { #from_godot_impl // Marker impl: defaults derive element metadata from shape(). - impl ::godot::meta::Element for #name {} + impl #element_generic_params ::godot::meta::Element for #name #generic_args #where_clause {} }) } diff --git a/godot-macros/src/derive/derive_to_godot.rs b/godot-macros/src/derive/derive_to_godot.rs index 616287f96..36b3d7d5a 100644 --- a/godot-macros/src/derive/derive_to_godot.rs +++ b/godot-macros/src/derive/derive_to_godot.rs @@ -18,10 +18,11 @@ pub fn make_togodot(convert: &GodotConvert, cache: &mut EnumeratorExprCache) -> let GodotConvert { ty_name: name, convert_type: data, + .. } = convert; match data { - ConvertType::NewType { field } => make_togodot_for_newtype_struct(name, field), + ConvertType::NewType { field } => make_togodot_for_newtype_struct(convert, field), ConvertType::Enum { variants, @@ -36,12 +37,22 @@ pub fn make_togodot(convert: &GodotConvert, cache: &mut EnumeratorExprCache) -> } /// Derives `ToGodot` for newtype structs. -fn make_togodot_for_newtype_struct(name: &Ident, field: &NewtypeStruct) -> TokenStream { +fn make_togodot_for_newtype_struct(convert: &GodotConvert, field: &NewtypeStruct) -> TokenStream { + let GodotConvert { + ty_name: name, + generic_params, + where_clause, + .. + } = convert; + + let generic_args = generic_params + .as_ref() + .map(|params| params.as_inline_args()); let field_name = field.field_name(); let via_type = &field.ty; quote! { - impl ::godot::meta::ToGodot for #name { + impl #generic_params ::godot::meta::ToGodot for #name #generic_args #where_clause { type Pass = <#via_type as ::godot::meta::ToGodot>::Pass; fn to_godot(&self) -> ::godot::meta::ToArg<'_, Self::Via, Self::Pass> { diff --git a/itest/rust/src/register_tests/derive_godotconvert_test.rs b/itest/rust/src/register_tests/derive_godotconvert_test.rs index be81c2db5..44eafb321 100644 --- a/itest/rust/src/register_tests/derive_godotconvert_test.rs +++ b/itest/rust/src/register_tests/derive_godotconvert_test.rs @@ -6,6 +6,7 @@ */ use std::fmt::Debug; +use std::marker::PhantomData; use godot::builtin::{GString, Vector2, array, dict}; use godot::meta::{GodotConvert, ToGodot}; @@ -16,16 +17,31 @@ use crate::framework::itest; // ---------------------------------------------------------------------------------------------------------------------------------------------- // General FromGodot/ToGodot derive tests +// #[derive(PartialEq, Debug)] #[derive(GodotConvert, PartialEq, Debug)] #[godot(transparent)] struct TupleNewtype(GString); +#[derive(GodotConvert, PartialEq, Debug)] +#[godot(transparent)] +struct TuplePhantomNewtype( + PhantomData, + godot::prelude::Array, +); + #[derive(GodotConvert, PartialEq, Debug)] #[godot(transparent)] struct NamedNewtype { field1: Vector2, } +#[derive(GodotConvert, PartialEq, Debug)] +#[godot(transparent)] +struct NamedPhantomNewtype { + field1: Vector2, + _marker: std::marker::PhantomData, +} + #[derive(GodotConvert, Clone, PartialEq, Debug)] #[godot(via = GString)] enum EnumStringy { @@ -58,6 +74,7 @@ enum EnumIntyWithExprs { #[itest] fn newtype_tuple_struct() { roundtrip(TupleNewtype(GString::from("hello!"))); + roundtrip(TuplePhantomNewtype::(PhantomData, array![])) } #[itest] @@ -65,6 +82,10 @@ fn newtype_named_struct() { roundtrip(NamedNewtype { field1: Vector2::new(10.0, 25.0), }); + roundtrip(NamedPhantomNewtype:: { + field1: Vector2::new(10.0, 25.0), + _marker: PhantomData, + }); } #[itest] From 3703c12e6c30a0dc6acc0b739817b4591090750a Mon Sep 17 00:00:00 2001 From: Willem Nordhus Date: Mon, 25 May 2026 13:42:56 -0400 Subject: [PATCH 2/6] Update GodotConvert derive macro docs to match new PhantomData functionality --- godot-macros/src/lib.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 0d9ef6a2e..7c3b36f13 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -1366,15 +1366,25 @@ pub fn godot_dyn(_meta: TokenStream, input: TokenStream) -> TokenStream { /// assert_eq!(obj.to_godot(), &GString::from("hello!")); /// ``` /// -/// However, it will not work for structs with more than one field, even if that field is zero sized: +/// It will not work for structs with more than one field, unless the extra fields are `PhantomData` /// ```compile_fail /// use godot::prelude::*; +/// use std::marker::PhantomData; /// +/// // This will not compile /// #[derive(GodotConvert)] /// #[godot(transparent)] -/// struct SomeNewtype { +/// struct SomeNewtype1 { /// int: i64, -/// zst: (), +/// bool: bool, +/// } +/// +/// // This will compile +/// #[derive(GodotConvert)] +/// #[godot(transparent)] +/// struct SomeNewtype2 { +/// int: i64, +/// _marker: PhantomData, /// } /// ``` /// From 557ae08def6497e9e72fb6297dc287de37bb29e8 Mon Sep 17 00:00:00 2001 From: Willem Nordhus Date: Mon, 25 May 2026 18:06:39 -0400 Subject: [PATCH 3/6] Refactor to remove unnecessary clones --- .../src/derive/data_models/godot_convert.rs | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/godot-macros/src/derive/data_models/godot_convert.rs b/godot-macros/src/derive/data_models/godot_convert.rs index 71f72b5bd..b6e2a2076 100644 --- a/godot-macros/src/derive/data_models/godot_convert.rs +++ b/godot-macros/src/derive/data_models/godot_convert.rs @@ -27,18 +27,15 @@ pub struct GodotConvert { impl GodotConvert { pub fn parse_declaration(item: venial::Item) -> ParseResult { - let (name, where_clause, generic_params) = match &item { - venial::Item::Struct(struct_) => ( - struct_.name.clone(), - &struct_.where_clause, - &struct_.generic_params, - ), - venial::Item::Enum(enum_) => { - // These checks aren't really necessary anyways, because we're guaranteed to have - // only c-style enums anywas, and so rust would complain that the generics aren't - // being used. These error messages are a bit more clear with our intentions, - // though. + let data = ConvertType::parse_declaration(&item)?; + let (name, where_clause, generic_params) = match item { + venial::Item::Struct(struct_) => { + (struct_.name, struct_.where_clause, struct_.generic_params) + } + venial::Item::Enum(enum_) => { + // We only have C-style enums, so Rust would already complain that generics are unused. + // This provides clearer error messages though. if let Some(generic_params) = &enum_.generic_params { return bail!( generic_params, @@ -55,11 +52,7 @@ impl GodotConvert { ); } - ( - enum_.name.clone(), - &enum_.where_clause, - &enum_.generic_params, - ) + (enum_.name, enum_.where_clause, enum_.generic_params) } other => { return bail!( @@ -69,12 +62,10 @@ impl GodotConvert { } }; - let data = ConvertType::parse_declaration(item.clone())?; - Ok(Self { ty_name: name, - where_clause: where_clause.clone(), - generic_params: generic_params.clone(), + where_clause: where_clause, + generic_params: generic_params, convert_type: data, }) } @@ -89,10 +80,10 @@ pub enum ConvertType { } impl ConvertType { - pub fn parse_declaration(item: venial::Item) -> ParseResult { - let attribute = GodotAttribute::parse_attribute(&item)?; + pub fn parse_declaration(item: &venial::Item) -> ParseResult { + let attribute = GodotAttribute::parse_attribute(item)?; - match &item { + match item { venial::Item::Struct(struct_) => { let GodotAttribute::Transparent { .. } = attribute else { return bail!( From 1d6debe7a360f54280bc7bc8cb9d8b554d0a3475 Mon Sep 17 00:00:00 2001 From: Willem Nordhus Date: Mon, 25 May 2026 18:07:50 -0400 Subject: [PATCH 4/6] Add comment claryifying NewtypeStruct can have multiple fields --- godot-macros/src/derive/data_models/newtype.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/godot-macros/src/derive/data_models/newtype.rs b/godot-macros/src/derive/data_models/newtype.rs index f00ed073d..e12061a0e 100644 --- a/godot-macros/src/derive/data_models/newtype.rs +++ b/godot-macros/src/derive/data_models/newtype.rs @@ -23,6 +23,8 @@ pub enum FieldsType { } /// Stores info from the field of a newtype struct for use in deriving `GodotConvert` and other related traits. +/// +/// Here, a newtype struct must have exactly 1 non-ZST field, and can have an arbitrary amount of ZST fields. pub struct NewtypeStruct { /// The name of the field. /// From 5ef1953d71ec74e7c1facccec49639818aaf194a Mon Sep 17 00:00:00 2001 From: Willem Nordhus Date: Mon, 25 May 2026 18:08:50 -0400 Subject: [PATCH 5/6] Reword and correctly format comment describing `T: 'static + 'static` hack --- godot-macros/src/derive/derive_godot_convert.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/godot-macros/src/derive/derive_godot_convert.rs b/godot-macros/src/derive/derive_godot_convert.rs index cd0a883cf..570f91dce 100644 --- a/godot-macros/src/derive/derive_godot_convert.rs +++ b/godot-macros/src/derive/derive_godot_convert.rs @@ -38,10 +38,10 @@ pub fn derive_godot_convert(item: venial::Item) -> ParseResult { for (param, _) in params.params.iter_mut() { if param.is_ty() || param.is_lifetime() { if let Some(bound) = &mut param.bound { - // If we're in here we have at least 1 bound, and rust doesn't error if the - // bound is already 'static, i.e. `T: 'static + 'static` works. It's a - // little hacky, but there's not really a reason to inspect all of the - // bound's tokens if we really don't care what it is. + // We have at least 1 bound, and rust doesn't error if the bound is already 'static, i.e. `T: 'static + 'static` works. + // If it were to error, we would have to inspect the bounds to make sure the tokens `+ 'static` are valid. It feels a + // little hacky, but there's not really a reason to inspect all of the bound's tokens if rust doesn't really care what + // they are. bound .tokens .append(&mut quote! {+ 'static}.into_iter().collect()); From 60d1a3e2b2cc12cfbf48d954a800eeffe2f424f5 Mon Sep 17 00:00:00 2001 From: Willem Nordhus Date: Mon, 25 May 2026 18:36:32 -0400 Subject: [PATCH 6/6] Add example to GodotConvert derive macro docs --- godot-macros/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 7c3b36f13..d273a76d4 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -1388,6 +1388,17 @@ pub fn godot_dyn(_meta: TokenStream, input: TokenStream) -> TokenStream { /// } /// ``` /// +/// This is useful for cases where you want to have generics in Rust, but you still want to use that struct from Godot. For example, you have a +/// key `Key` to a registry `Registry` that contains `T`. +/// ```rs +/// #[derive(GodotConvert)] +/// #[godot(transparent)] +/// struct Key { +/// id: u32, +/// _marker: PhantomData, +/// } +/// ``` +/// /// You can also not use `transparent` with enums: /// ```compile_fail /// use godot::prelude::*;