Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generic types in glib::object_subclass #1286

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 25 additions & 18 deletions glib-macros/src/object_interface_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ pub fn impl_object_interface(input: &mut syn::ItemImpl) -> TokenStream {
..
} = input;

let self_ty_as_ident = match &**self_ty {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look too close but I think this is currently unsound. There's only a single type registered for every T, so you can currently safely cast between different T. Not sure how that can be solved nicely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, single type. Similar to generics in Java with type erasure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike in Java this is unsound here though :) In the simplest case, you could have a MyObj<T> that stores a T, and then simply casting that between T: String to T: u32 and the subclass couldn't know about that happening and accesses the value with the wrong type then.

syn::Type::Path(syn::TypePath { path, .. }) => path.require_ident(),
_ => Err(syn::Error::new(
let Some(self_ty_ident) = (match &**self_ty {
syn::Type::Path(syn::TypePath { path, .. }) => path.segments.last().map(|s| &s.ident),
_ => None,
}) else {
return syn::Error::new(
syn::spanned::Spanned::span(self_ty),
"expected this path to be an identifier",
)),
};
let self_ty = match self_ty_as_ident {
Ok(ident) => ident,
Err(e) => return e.to_compile_error(),
"expected this path to have an identifier",
)
.to_compile_error();
};

let mut plugin_type = NestedMetaItem::<syn::Path>::new("plugin_type").value_required();
Expand All @@ -46,7 +45,7 @@ pub fn impl_object_interface(input: &mut syn::ItemImpl) -> TokenStream {

let register_object_interface = match found {
Err(e) => return e.to_compile_error(),
Ok(None) => register_object_interface_as_static(&crate_ident, self_ty),
Ok(None) => register_object_interface_as_static(&crate_ident, generics, self_ty),
Ok(Some(_)) => {
// remove attribute 'object_interface_dynamic' from the attribute list because it is not a real proc_macro_attribute
attrs.retain(|attr| !attr.path().is_ident("object_interface_dynamic"));
Expand All @@ -57,7 +56,9 @@ pub fn impl_object_interface(input: &mut syn::ItemImpl) -> TokenStream {
let lazy_registration = lazy_registration.value.map(|b| b.value).unwrap_or_default();
register_object_interface_as_dynamic(
&crate_ident,
generics,
self_ty,
self_ty_ident,
plugin_ty,
lazy_registration,
)
Expand Down Expand Up @@ -94,7 +95,7 @@ pub fn impl_object_interface(input: &mut syn::ItemImpl) -> TokenStream {
#(#items)*
}

unsafe impl #crate_ident::subclass::interface::ObjectInterfaceType for #self_ty {
unsafe impl #generics #crate_ident::subclass::interface::ObjectInterfaceType for #self_ty {
#[inline]
fn type_() -> #crate_ident::Type {
Self::register_interface()
Expand All @@ -108,11 +109,12 @@ pub fn impl_object_interface(input: &mut syn::ItemImpl) -> TokenStream {
// Registers the object interface as a static type.
fn register_object_interface_as_static(
crate_ident: &TokenStream,
self_ty: &syn::Ident,
generics: &syn::Generics,
self_ty: &syn::Type,
) -> TokenStream {
// registers the interface on first use (lazy registration).
quote! {
impl #self_ty {
impl #generics #self_ty {
/// Registers the interface only once.
#[inline]
fn register_interface() -> #crate_ident::Type {
Expand All @@ -135,7 +137,9 @@ fn register_object_interface_as_static(
// An object interface can be reregistered as a dynamic type.
fn register_object_interface_as_dynamic(
crate_ident: &TokenStream,
self_ty: &syn::Ident,
generics: &syn::Generics,
self_ty: &syn::Type,
self_ty_ident: &syn::Ident,
plugin_ty: TokenStream,
lazy_registration: bool,
) -> TokenStream {
Expand All @@ -147,7 +151,7 @@ fn register_object_interface_as_dynamic(
// this implementation relies on a static storage of a weak reference on the plugin and of the GLib type to know if the object interface has been registered.

// the registration status type.
let registration_status_type = format_ident!("{}RegistrationStatus", self_ty);
let registration_status_type = format_ident!("{}RegistrationStatus", self_ty_ident);
// name of the static variable to store the registration status.
let registration_status = format_ident!(
"{}",
Expand All @@ -162,7 +166,7 @@ fn register_object_interface_as_dynamic(
/// The registration status protected by a mutex guarantees so that no other threads are concurrently accessing the data.
static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);

impl #self_ty {
impl #generics #self_ty {
/// Registers the object interface as a dynamic type within the plugin only once.
/// Plugin must have been used at least once.
/// Do nothing if plugin has never been used or if the object interface is already registered as a dynamic type.
Expand Down Expand Up @@ -232,13 +236,16 @@ fn register_object_interface_as_dynamic(
// registers immediately the object interface as a dynamic type.

// name of the static variable to store the GLib type.
let gtype_status = format_ident!("{}_G_TYPE", self_ty.to_string().to_shouty_snake_case());
let gtype_status = format_ident!(
"{}_G_TYPE",
self_ty_ident.to_string().to_shouty_snake_case()
);

quote! {
/// The GLib type which can be safely shared between threads.
static #gtype_status: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID);

impl #self_ty {
impl #generics #self_ty {
/// Do nothing as the object interface has been registered on implementation load.
#[inline]
fn register_interface() -> #crate_ident::Type {
Expand Down
55 changes: 32 additions & 23 deletions glib-macros/src/object_subclass_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,15 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
..
} = input;

let self_ty_as_ident = match &**self_ty {
syn::Type::Path(syn::TypePath { path, .. }) => path.require_ident(),
_ => Err(syn::Error::new(
let Some(self_ty_ident) = (match &**self_ty {
syn::Type::Path(syn::TypePath { path, .. }) => path.segments.last().map(|s| &s.ident),
_ => None,
}) else {
return syn::Error::new(
syn::spanned::Spanned::span(self_ty),
"expected this path to be an identifier",
)),
};
let self_ty = match self_ty_as_ident {
Ok(ident) => ident,
Err(e) => return e.to_compile_error(),
"expected this path to have an identifier",
)
.to_compile_error();
};

let mut plugin_type = NestedMetaItem::<syn::Path>::new("plugin_type").value_required();
Expand All @@ -45,7 +44,7 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {

let register_object_subclass = match found {
Err(e) => return e.to_compile_error(),
Ok(None) => register_object_subclass_as_static(&crate_ident, self_ty),
Ok(None) => register_object_subclass_as_static(&crate_ident, generics, self_ty),
Ok(Some(_)) => {
// remove attribute 'object_subclass_dynamic' from the attribute list because it is not a real proc_macro_attribute
attrs.retain(|attr| !attr.path().is_ident("object_subclass_dynamic"));
Expand All @@ -54,7 +53,14 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
.map(|p| p.into_token_stream())
.unwrap_or(quote!(#crate_ident::TypeModule));
let lazy_registration = lazy_registration.value.map(|b| b.value).unwrap_or_default();
register_object_subclass_as_dynamic(&crate_ident, self_ty, plugin_ty, lazy_registration)
register_object_subclass_as_dynamic(
&crate_ident,
generics,
self_ty,
self_ty_ident,
plugin_ty,
lazy_registration,
)
}
};

Expand Down Expand Up @@ -130,7 +136,7 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
#(#items)*
}

unsafe impl #crate_ident::subclass::types::ObjectSubclassType for #self_ty {
unsafe impl #generics #crate_ident::subclass::types::ObjectSubclassType for #self_ty {
#[inline]
fn type_data() -> ::std::ptr::NonNull<#crate_ident::subclass::TypeData> {
static mut DATA: #crate_ident::subclass::TypeData =
Expand All @@ -154,7 +160,7 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
#register_object_subclass

#[doc(hidden)]
impl #crate_ident::subclass::types::FromObject for #self_ty {
impl #generics #crate_ident::subclass::types::FromObject for #self_ty {
type FromObjectType = <Self as #crate_ident::subclass::types::ObjectSubclass>::Type;
#[inline]
fn from_object(obj: &Self::FromObjectType) -> &Self {
Expand All @@ -163,7 +169,7 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
}

#[doc(hidden)]
impl #crate_ident::clone::Downgrade for #self_ty {
impl #generics #crate_ident::clone::Downgrade for #self_ty {
type Weak = #crate_ident::subclass::ObjectImplWeakRef<#self_ty>;

#[inline]
Expand All @@ -173,15 +179,15 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
}
}

impl #self_ty {
impl #generics #self_ty {
#[inline]
pub fn downgrade(&self) -> <Self as #crate_ident::clone::Downgrade>::Weak {
#crate_ident::clone::Downgrade::downgrade(self)
}
}

#[doc(hidden)]
impl ::std::borrow::ToOwned for #self_ty {
impl #generics ::std::borrow::ToOwned for #self_ty {
type Owned = #crate_ident::subclass::ObjectImplRef<#self_ty>;

#[inline]
Expand All @@ -191,7 +197,7 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
}

#[doc(hidden)]
impl ::std::borrow::Borrow<#self_ty> for #crate_ident::subclass::ObjectImplRef<#self_ty> {
impl #generics ::std::borrow::Borrow<#self_ty> for #crate_ident::subclass::ObjectImplRef<#self_ty> {
#[inline]
fn borrow(&self) -> &#self_ty {
self
Expand All @@ -203,11 +209,12 @@ pub fn impl_object_subclass(input: &mut syn::ItemImpl) -> TokenStream {
// Registers the object subclass as a static type.
fn register_object_subclass_as_static(
crate_ident: &TokenStream,
self_ty: &syn::Ident,
generics: &syn::Generics,
self_ty: &syn::Type,
) -> TokenStream {
// registers the object subclass on first use (lazy registration).
quote! {
impl #self_ty {
impl #generics #self_ty {
/// Registers the type only once.
#[inline]
fn register_type() {
Expand All @@ -225,7 +232,9 @@ fn register_object_subclass_as_static(
// An object subclass can be reregistered as a dynamic type.
fn register_object_subclass_as_dynamic(
crate_ident: &TokenStream,
self_ty: &syn::Ident,
generics: &syn::Generics,
self_ty: &syn::Type,
self_ty_ident: &syn::Ident,
plugin_ty: TokenStream,
lazy_registration: bool,
) -> TokenStream {
Expand All @@ -237,7 +246,7 @@ fn register_object_subclass_as_dynamic(
// this implementation relies on a static storage of a weak reference on the plugin and of the GLib type to know if the object subclass has been registered.

// the registration status type.
let registration_status_type = format_ident!("{}RegistrationStatus", self_ty);
let registration_status_type = format_ident!("{}RegistrationStatus", self_ty_ident);
// name of the static variable to store the registration status.
let registration_status = format_ident!(
"{}",
Expand All @@ -252,7 +261,7 @@ fn register_object_subclass_as_dynamic(
/// The registration status protected by a mutex guarantees so that no other threads are concurrently accessing the data.
static #registration_status: ::std::sync::Mutex<Option<#registration_status_type>> = ::std::sync::Mutex::new(None);

impl #self_ty {
impl #generics #self_ty {
/// Registers the object subclass as a dynamic type within the plugin only once.
/// Plugin must have been used at least once.
/// Do nothing if plugin has never been used or if the object subclass is already registered as a dynamic type.
Expand Down Expand Up @@ -321,7 +330,7 @@ fn register_object_subclass_as_dynamic(
// registers immediately the object subclass as a dynamic type.

quote! {
impl #self_ty {
impl #generics #self_ty {
/// Do nothing as the object subclass has been registered on implementation load.
#[inline]
fn register_type() { }
Expand Down
68 changes: 68 additions & 0 deletions glib-macros/tests/object_interface_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use glib::subclass::prelude::*;

pub mod imp {
use std::marker::PhantomData;

use super::*;

#[repr(C)]
pub struct MyGenericInterface<T> {
parent: glib::gobject_ffi::GTypeInterface,
marker: PhantomData<T>,
}

impl<T> Clone for MyGenericInterface<T> {
fn clone(&self) -> Self {
Self {
parent: self.parent,
marker: self.marker,
}
}
}

impl<T> Copy for MyGenericInterface<T> {}

#[glib::object_interface]
unsafe impl<T: 'static> ObjectInterface for MyGenericInterface<T> {
const NAME: &'static str = "MyGenericInterface";
}

pub trait MyGenericInterfaceImpl<T>: ObjectImpl + ObjectSubclass {}

pub struct MyGenericType<T> {
marker: PhantomData<T>,
}

#[glib::object_subclass]
impl<T: 'static> ObjectSubclass for MyGenericType<T> {
const NAME: &'static str = "MyGenericType";
type Type = super::MyGenericType<T>;
type Interfaces = (super::MyGenericInterface<T>,);

fn new() -> Self {
Self {
marker: PhantomData,
}
}
}

impl<T: 'static> ObjectImpl for MyGenericType<T> {}

impl<T: 'static> MyGenericInterfaceImpl<T> for MyGenericType<T> {}

pub trait MyGenericTypeImpl<T>: ObjectImpl + ObjectSubclass {}
}

glib::wrapper! {
pub struct MyGenericInterface<T: 'static>(ObjectInterface<imp::MyGenericInterface<T>>);
}

unsafe impl<I: imp::MyGenericInterfaceImpl<T>, T> IsImplementable<I> for MyGenericInterface<T> {}

glib::wrapper! {
pub struct MyGenericType<T: 'static>(ObjectSubclass<imp::MyGenericType<T>>) @implements MyGenericInterface<T>;
}

unsafe impl<I: imp::MyGenericTypeImpl<T>, T> IsSubclassable<I> for MyGenericType<T> {}
26 changes: 26 additions & 0 deletions glib-macros/tests/object_subclass_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use glib::subclass::prelude::*;

pub mod imp {
use super::*;
use std::marker::PhantomData;

pub struct MyGenericType<T>(PhantomData<T>);

#[glib::object_subclass]
impl<T: 'static> ObjectSubclass for MyGenericType<T> {
const NAME: &'static str = "MyGenericType";
type Type = super::MyGenericType<T>;

fn new() -> Self {
MyGenericType(PhantomData::<T>)
}
}

impl<T: 'static> ObjectImpl for MyGenericType<T> {}
}

glib::wrapper! {
pub struct MyGenericType<T: 'static>(ObjectSubclass<imp::MyGenericType::<T>>);
}
Loading