Skip to content

Commit 4b40df2

Browse files
committed
Support deriving erase/rebrand for non-gc lifetimes if Self: NullTrace
Emits a warning to reflect the current unsafety/uncheckedness of the GcErase/GcRebrand default-implementations. TODO: Fix this This is due to current limitations in the type-system and how we choose to implement GcErase + GcRebrand. See the docs of `&'a T` for more on why we require `T: NullTrace` TODO: Figure out a way around this.
1 parent ecac108 commit 4b40df2

File tree

2 files changed

+134
-24
lines changed

2 files changed

+134
-24
lines changed

libs/derive/src/lib.rs

Lines changed: 133 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
extern crate proc_macro;
66

77
use quote::{quote, quote_spanned};
8-
use syn::{parse_macro_input, parenthesized, parse_quote, DeriveInput, Data, Error, Generics, GenericParam, TypeParamBound, Fields, Member, Index, Type, GenericArgument, Attribute, PathArguments, Meta, TypeParam, WherePredicate, PredicateType, Token, Lifetime, NestedMeta, Lit, PredicateLifetime};
8+
use syn::{parse_macro_input, parenthesized, parse_quote, DeriveInput, Data, Error, Generics, GenericParam, TypeParamBound, Fields, Member, Index, Type, GenericArgument, Attribute, PathArguments, Meta, TypeParam, WherePredicate, PredicateType, Token, Lifetime, NestedMeta, Lit};
99
use proc_macro2::{Ident, TokenStream, Span};
1010
use syn::spanned::Spanned;
1111
use syn::parse::{ParseStream, Parse};
@@ -328,8 +328,16 @@ fn impl_derive_trace(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
328328
} else {
329329
impl_trace(&input, &info)?
330330
};
331-
let rebrand_impl = impl_rebrand(&input, &info)?;
332-
let erase_impl = impl_erase(&input, &info)?;
331+
let rebrand_impl = if info.config.nop_trace {
332+
impl_rebrand_nop(&input, &info)?
333+
} else {
334+
impl_rebrand(&input, &info)?
335+
};
336+
let erase_impl = if info.config.nop_trace {
337+
impl_erase_nop(&input, &info)?
338+
} else {
339+
impl_erase(&input, &info)?
340+
};
333341
let gc_safe_impl = impl_gc_safe(&input, &info)?;
334342
let extra_impls = impl_extras(&input, &info)?;
335343
Ok(quote! {
@@ -465,6 +473,62 @@ fn impl_extras(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, E
465473
})
466474
}
467475

476+
477+
fn impl_erase_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Error> {
478+
let name = &target.ident;
479+
let mut generics: Generics = target.generics.clone();
480+
for param in &mut generics.params {
481+
match param {
482+
GenericParam::Type(ref mut type_param) => {
483+
// Require all params are NullTrace
484+
type_param.bounds.push(parse_quote!(::zerogc::NullTrace));
485+
},
486+
GenericParam::Lifetime(ref mut l) => {
487+
if l.lifetime == info.config.gc_lifetime() {
488+
assert!(!info.config.ignored_lifetimes.contains(&l.lifetime));
489+
return Err(Error::new(
490+
l.lifetime.span(),
491+
"Unexpected GC lifetime: Expected #[zerogc(nop_trace)] during #[derive(GcErase)]"
492+
))
493+
} else if info.config.ignored_lifetimes.contains(&l.lifetime) {
494+
// Explicitly ignored is okay, as long as it outlives the `'min`
495+
l.bounds.push(parse_quote!('min));
496+
} else {
497+
return Err(Error::new(
498+
l.span(),
499+
"Lifetime must be explicitly ignored"
500+
))
501+
}
502+
},
503+
GenericParam::Const(_) => {}
504+
}
505+
}
506+
let mut impl_generics = generics.clone();
507+
impl_generics.params.push(GenericParam::Lifetime(parse_quote!('min)));
508+
impl_generics.params.push(GenericParam::Type(parse_quote!(S: ::zerogc::CollectorId)));
509+
// Require that `Self: NullTrace`
510+
impl_generics.make_where_clause().predicates.push(WherePredicate::Type(PredicateType {
511+
lifetimes: None,
512+
bounded_ty: parse_quote!(Self),
513+
bounds: parse_quote!(::zerogc::NullTrace),
514+
colon_token: Default::default()
515+
}));
516+
let (_, ty_generics, _) = generics.split_for_impl();
517+
let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
518+
::proc_macro::Diagnostic::spanned(
519+
::proc_macro::Span::call_site(),
520+
::proc_macro::Level::Note,
521+
// We know this is safe because we know that `Self: NullTrace`
522+
"derive(GcRebrand) is safe for NullTrace, unlike standard implementation"
523+
).emit();
524+
Ok(quote! {
525+
unsafe impl #impl_generics ::zerogc::GcErase<'min, S>
526+
for #name #ty_generics #where_clause {
527+
// We can pass-through because we are NullTrace
528+
type Erased = Self;
529+
}
530+
})
531+
}
468532
fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Error> {
469533
let name = &target.ident;
470534
let mut generics: Generics = target.generics.clone();
@@ -475,10 +539,10 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Er
475539
match param {
476540
GenericParam::Type(ref mut type_param) => {
477541
let original_bounds = type_param.bounds.iter().cloned().collect::<Vec<_>>();
478-
type_param.bounds.push(parse_quote!(::zerogc::GcErase<'max, S>));
479-
type_param.bounds.push(parse_quote!('max));
542+
type_param.bounds.push(parse_quote!(::zerogc::GcErase<'min, S>));
543+
type_param.bounds.push(parse_quote!('min));
480544
let param_name = &type_param.ident;
481-
let rewritten_type: Type = parse_quote!(<#param_name as ::zerogc::GcErase<'max, S>>::Erased);
545+
let rewritten_type: Type = parse_quote!(<#param_name as ::zerogc::GcErase<'min, S>>::Erased);
482546
rewritten_restrictions.push(WherePredicate::Type(PredicateType {
483547
lifetimes: None,
484548
bounded_ty: rewritten_type.clone(),
@@ -491,19 +555,11 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Er
491555
if l.lifetime == info.config.gc_lifetime() {
492556
rewritten_param = parse_quote!('static);
493557
assert!(!info.config.ignored_lifetimes.contains(&l.lifetime));
494-
} else if info.config.ignored_lifetimes.contains(&l.lifetime) {
495-
let lt = l.lifetime.clone();
496-
rewritten_restrictions.push(WherePredicate::Lifetime(PredicateLifetime {
497-
lifetime: parse_quote!('max),
498-
colon_token: Default::default(),
499-
bounds: parse_quote!(#lt)
500-
}));
501-
rewritten_param = GenericArgument::Lifetime(lt);
502558
} else {
503559
return Err(Error::new(
504560
l.span(),
505-
"Unable to handle lifetime"
506-
));
561+
"Unless Self: NullTrace, derive(GcErase) is currently unable to handle lifetimes"
562+
))
507563
}
508564
},
509565
GenericParam::Const(ref param) => {
@@ -514,7 +570,7 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Er
514570
rewritten_params.push(rewritten_param);
515571
}
516572
let mut impl_generics = generics.clone();
517-
impl_generics.params.push(GenericParam::Lifetime(parse_quote!('max)));
573+
impl_generics.params.push(GenericParam::Lifetime(parse_quote!('min)));
518574
impl_generics.params.push(GenericParam::Type(parse_quote!(S: ::zerogc::CollectorId)));
519575
impl_generics.make_where_clause().predicates.extend(rewritten_restrictions);
520576
let (_, ty_generics, _) = generics.split_for_impl();
@@ -525,13 +581,69 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Er
525581
"derive(GcErase) doesn't currently verify the correctness of its fields"
526582
).emit();
527583
Ok(quote! {
528-
unsafe impl #impl_generics ::zerogc::GcErase<'max, S>
584+
unsafe impl #impl_generics ::zerogc::GcErase<'min, S>
529585
for #name #ty_generics #where_clause {
530586
type Erased = #name::<#(#rewritten_params),*>;
531587
}
532588
})
533589
}
534590

591+
592+
fn impl_rebrand_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Error> {
593+
let name = &target.ident;
594+
let mut generics: Generics = target.generics.clone();
595+
for param in &mut generics.params {
596+
match param {
597+
GenericParam::Type(ref mut type_param) => {
598+
// Require all params are NullTrace
599+
type_param.bounds.push(parse_quote!(::zerogc::NullTrace));
600+
},
601+
GenericParam::Lifetime(ref mut l) => {
602+
if l.lifetime == info.config.gc_lifetime() {
603+
assert!(!info.config.ignored_lifetimes.contains(&l.lifetime));
604+
return Err(Error::new(
605+
l.lifetime.span(),
606+
"Unexpected GC lifetime: Expected #[zerogc(nop_trace)] during #[derive(GcRebrand)]"
607+
))
608+
} else if info.config.ignored_lifetimes.contains(&l.lifetime) {
609+
// Explicitly ignored is okay, as long as it outlives the `'new_gc`
610+
l.bounds.push(parse_quote!('new_gc));
611+
} else {
612+
return Err(Error::new(
613+
l.span(),
614+
"Lifetime must be explicitly ignored"
615+
))
616+
}
617+
},
618+
GenericParam::Const(_) => {}
619+
}
620+
}
621+
let mut impl_generics = generics.clone();
622+
impl_generics.params.push(GenericParam::Lifetime(parse_quote!('new_gc)));
623+
impl_generics.params.push(GenericParam::Type(parse_quote!(S: ::zerogc::CollectorId)));
624+
// Require that `Self: NullTrace`
625+
impl_generics.make_where_clause().predicates.push(WherePredicate::Type(PredicateType {
626+
lifetimes: None,
627+
bounded_ty: parse_quote!(Self),
628+
bounds: parse_quote!(::zerogc::NullTrace),
629+
colon_token: Default::default()
630+
}));
631+
let (_, ty_generics, _) = generics.split_for_impl();
632+
let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
633+
::proc_macro::Diagnostic::spanned(
634+
::proc_macro::Span::call_site(),
635+
::proc_macro::Level::Note,
636+
// We know this is safe because we know that `Self: NullTrace`
637+
"derive(GcRebrand) is safe for NullTrace, unlike standard implementation"
638+
).emit();
639+
Ok(quote! {
640+
unsafe impl #impl_generics ::zerogc::GcRebrand<'new_gc, S>
641+
for #name #ty_generics #where_clause {
642+
// We can pass-through because we are NullTrace
643+
type Branded = Self;
644+
}
645+
})
646+
}
535647
fn impl_rebrand(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream, Error> {
536648
let name = &target.ident;
537649
let mut generics: Generics = target.generics.clone();
@@ -557,13 +669,11 @@ fn impl_rebrand(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream,
557669
if l.lifetime == info.config.gc_lifetime() {
558670
rewritten_param = parse_quote!('new_gc);
559671
assert!(!info.config.ignored_lifetimes.contains(&l.lifetime));
560-
} else if info.config.ignored_lifetimes.contains(&l.lifetime) {
561-
rewritten_param = GenericArgument::Lifetime(l.lifetime.clone());
562672
} else {
563673
return Err(Error::new(
564674
l.span(),
565-
"Unable to handle lifetime"
566-
));
675+
"Unless Self: NullTrace, derive(GcRebrand) is currently unable to handle lifetimes"
676+
))
567677
}
568678
},
569679
GenericParam::Const(ref param) => {
@@ -769,6 +879,7 @@ fn impl_nop_trace(target: &DeriveInput, info: &GcTypeInfo) -> Result<TokenStream
769879
));
770880
},
771881
}
882+
// TODO: We should have some sort of const-assertion for this....
772883
let trace_assertions = field_types.iter()
773884
.map(|&t| {
774885
let ty_span = t.span();

libs/derive/tests/basic.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,10 @@ struct NopTrace {
6161
wow: Box<NopTrace>
6262
}
6363

64-
#[cfg(disabled)] // TODO: Fixup
6564
#[derive(Trace)]
6665
#[zerogc(nop_trace, ignore_lifetimes("'a"), ignore_params(T))]
6766
#[allow(unused)]
68-
struct LifetimeTrace<'a, T: GcSafe> {
67+
struct LifetimeTrace<'a, T: GcSafe + 'a> {
6968
s: String,
7069
i: i32,
7170
wow: Box<NopTrace>,

0 commit comments

Comments
 (0)