mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Support bare reference parameters and lending outputs in the node macro and make the clone node the clone-out adapter
This commit is contained in:
@@ -633,6 +633,27 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
]);
|
||||
}
|
||||
|
||||
let has_lend = parsed.fields.iter().any(|field| matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. })));
|
||||
let declared_arena_lifetime = ctx_param.and_then(|ctx_param| {
|
||||
ctx_param.bounds.iter().find_map(|bound| {
|
||||
let TypeParamBound::Trait(trait_bound) = bound else { return None };
|
||||
let segment = trait_bound.path.segments.last()?;
|
||||
if segment.ident != "ExtractArena" {
|
||||
return None;
|
||||
}
|
||||
let PathArguments::AngleBracketed(args) = &segment.arguments else { return None };
|
||||
match args.args.first() {
|
||||
Some(GenericArgument::Lifetime(lifetime)) => Some(lifetime.clone()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
});
|
||||
let introduced_lend_lifetime = (has_lend && declared_arena_lifetime.is_none()).then(|| Lifetime::new("'__lend", proc_macro2::Span::call_site()));
|
||||
let lend_lifetime = declared_arena_lifetime.or_else(|| introduced_lend_lifetime.clone());
|
||||
if let Some(lifetime) = &introduced_lend_lifetime {
|
||||
ctx_bounds.push(quote!(#core_types::context::ExtractArena<ArenaRef = &#lifetime #core_types::arena::Arena>));
|
||||
}
|
||||
|
||||
let derives = ctx_param.is_some_and(|ctx_param| {
|
||||
ctx_param.bounds.iter().any(|bound| match bound {
|
||||
TypeParamBound::Trait(trait_bound) => trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx"),
|
||||
@@ -655,6 +676,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
if ctx_param.is_none() {
|
||||
generics.push(ctx_generic);
|
||||
}
|
||||
if let Some(lifetime) = &introduced_lend_lifetime {
|
||||
generics.insert(0, quote!(#lifetime));
|
||||
}
|
||||
|
||||
let fn_name = &parsed.fn_name;
|
||||
let mod_name = format_ident!("_{}_mod", parsed.mod_name);
|
||||
@@ -702,6 +726,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let kernel_params = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).map(|field| {
|
||||
let pat = &field.pat_ident;
|
||||
match &field.ty {
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if raw_lazy => {
|
||||
let bound = lazy_bound(output_type);
|
||||
@@ -715,6 +740,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
});
|
||||
|
||||
let node_bounds = regular_fields.iter().zip(&node_generics).map(|(field, node_generic)| match &field.ty {
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => {
|
||||
let lifetime = lend_lifetime.as_ref().expect("lend fields imply the lend lifetime");
|
||||
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = &#lifetime #ty>)
|
||||
}
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #ty>),
|
||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
||||
let bound = lazy_bound(output_type);
|
||||
@@ -722,6 +751,23 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
});
|
||||
|
||||
let mut lend_outlives: Vec<TokenStream2> = regular_fields
|
||||
.iter()
|
||||
.filter_map(|field| match &field.ty {
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => {
|
||||
let lifetime = lend_lifetime.as_ref().expect("lend fields imply the lend lifetime");
|
||||
Some(quote!(#ty: #lifetime))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
if let Type::Reference(reference) = &trait_output
|
||||
&& let Some(lifetime) = &reference.lifetime
|
||||
{
|
||||
let inner = &reference.elem;
|
||||
lend_outlives.push(quote!(#inner: #lifetime));
|
||||
}
|
||||
|
||||
let mut async_bounds = match (async_fn, future_kernel) {
|
||||
(false, false) => Vec::new(),
|
||||
(false, true) => vec![quote!(#trait_output: Clone)],
|
||||
@@ -1007,6 +1053,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
impl<#(#generics,)* #(#node_generics,)*> #core_types::node::Node<#ctx_ident> for #mod_name::#struct_name<#(#struct_type_params,)*>
|
||||
where
|
||||
#(#node_bounds,)*
|
||||
#(#lend_outlives,)*
|
||||
#(#clampable_bounds,)*
|
||||
#(#async_bounds,)*
|
||||
#(#where_predicates,)*
|
||||
@@ -1181,22 +1228,60 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let ref_output_inner = match slot_value_type(&parsed.output_type) {
|
||||
Type::Reference(reference) => Some((*reference.elem).clone()),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(inner) = &ref_output_inner {
|
||||
let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone());
|
||||
let open_generics = parsed.fn_generics.iter().filter_map(|param| match param {
|
||||
GenericParam::Type(type_param) if Some(&type_param.ident) != ctx_ident.as_ref() => Some(&type_param.ident),
|
||||
_ => None,
|
||||
});
|
||||
if open_generics.into_iter().any(|generic| type_contains_ident(inner, generic)) {
|
||||
return quote!();
|
||||
}
|
||||
}
|
||||
|
||||
let fn_name = &parsed.fn_name;
|
||||
let entries_name = format_ident!("{}_entries", fn_name);
|
||||
let arity = regular_fields.len();
|
||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
||||
let lend_flags: Vec<bool> = regular_fields
|
||||
.iter()
|
||||
.map(|field| matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. })))
|
||||
.collect();
|
||||
|
||||
let entries = rows.iter().map(|row| {
|
||||
let types = row.iter();
|
||||
let edge_types = row.iter().map(|ty| quote!(gcore::registry::SharedEdge<gcore::registry::ErasedNode<#ty>>));
|
||||
let input_types = row.iter().zip(&lend_flags).map(|(ty, lend)| match lend {
|
||||
true => quote!(gcore::registry::lend_edge_type::<#ty>()),
|
||||
false => quote!(gcore::registry::edge_type::<#ty>()),
|
||||
});
|
||||
let edge_types = row.iter().zip(&lend_flags).map(|(ty, lend)| match lend {
|
||||
true => quote!(gcore::registry::SharedEdge<gcore::registry::ErasedLendNode<#ty>>),
|
||||
false => quote!(gcore::registry::SharedEdge<gcore::registry::ErasedNode<#ty>>),
|
||||
});
|
||||
let output = quote!(<#struct_name<#(#edge_types),*> as gcore::node::Node<gcore::context::ContextImpl<'static>>>::Output);
|
||||
let downcasts = names.iter().zip(row.iter()).map(|(name, ty)| quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;));
|
||||
let (io_output, construct) = match &ref_output_inner {
|
||||
Some(inner) => (
|
||||
quote!(gcore::registry::ref_type::<#inner>()),
|
||||
quote!(Ok(gcore::registry::EdgeHandle::new_ref(::std::sync::Arc::new(#struct_name::new(#(#names),*)) as ::std::sync::Arc<gcore::registry::ErasedLendNode<#inner>>))),
|
||||
),
|
||||
None => (
|
||||
quote!(gcore::concrete!(#output)),
|
||||
quote!(Ok(gcore::registry::EdgeHandle::new(::std::sync::Arc::new(#struct_name::new(#(#names),*)) as ::std::sync::Arc<gcore::registry::ErasedNode<#output>>))),
|
||||
),
|
||||
};
|
||||
let downcasts = names.iter().zip(row.iter()).zip(&lend_flags).map(|((name, ty), lend)| match lend {
|
||||
true => quote!(let #name = inputs.next().unwrap().downcast_lend::<#ty>()?;),
|
||||
false => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;),
|
||||
});
|
||||
quote! {
|
||||
gcore::registry::RegistryEntry {
|
||||
io: gcore::registry::NodeIOTypes::new(
|
||||
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||
gcore::concrete!(#output),
|
||||
vec![#(gcore::registry::edge_type::<#types>()),*],
|
||||
#io_output,
|
||||
vec![#(#input_types),*],
|
||||
),
|
||||
constructor: |inputs| {
|
||||
if inputs.len() != #arity {
|
||||
@@ -1204,7 +1289,7 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
||||
}
|
||||
let mut inputs = inputs.into_iter();
|
||||
#(#downcasts)*
|
||||
Ok(gcore::registry::EdgeHandle::new(::std::sync::Arc::new(#struct_name::new(#(#names),*)) as ::std::sync::Arc<gcore::registry::ErasedNode<#output>>))
|
||||
#construct
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,8 @@ impl Parse for NumberRange {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RegularParsedField {
|
||||
pub ty: Type,
|
||||
/// The original reference tokens when the parameter was written `&T`; `ty` holds the peeled inner type.
|
||||
pub lend: Option<syn::TypeReference>,
|
||||
pub exposed: bool,
|
||||
pub value_source: ParsedValueSource,
|
||||
pub number_soft_min: Option<NumberBound>,
|
||||
@@ -904,6 +906,11 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let (ty, lend) = match ty {
|
||||
Type::Reference(reference) => ((*reference.elem).clone(), Some(reference)),
|
||||
ty => (ty, None),
|
||||
};
|
||||
|
||||
// Error if a float literal is given for a bound on an integer-typed field
|
||||
if is_integer_type(&ty) {
|
||||
let bound_attrs = [
|
||||
@@ -936,6 +943,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
number_hard_max,
|
||||
number_mode_range,
|
||||
ty,
|
||||
lend,
|
||||
value_source,
|
||||
implementations,
|
||||
gpu_image,
|
||||
@@ -1048,6 +1056,7 @@ impl ParsedNodeFn {
|
||||
widget_override: ParsedWidgetOverride::Hidden,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
ty,
|
||||
lend: None,
|
||||
exposed: false,
|
||||
value_source,
|
||||
number_soft_min: None,
|
||||
@@ -1218,6 +1227,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
lend: None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ParsedValueSource::None,
|
||||
@@ -1308,6 +1318,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
lend: None,
|
||||
ty: parse_quote!(DVec2),
|
||||
exposed: false,
|
||||
value_source: ParsedValueSource::None,
|
||||
@@ -1380,6 +1391,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
lend: None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ParsedValueSource::Default(quote!(50.)),
|
||||
@@ -1450,6 +1462,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
lend: None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ParsedValueSource::None,
|
||||
@@ -1532,6 +1545,7 @@ mod tests {
|
||||
description: String::from("b"),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
lend: None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ParsedValueSource::None,
|
||||
@@ -1617,6 +1631,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
lend: None,
|
||||
ty: parse_quote!(String),
|
||||
exposed: true,
|
||||
value_source: ParsedValueSource::None,
|
||||
|
||||
@@ -232,6 +232,7 @@ impl PerPixelAdjustCodegen<'_> {
|
||||
widget_override: Default::default(),
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
ty: parse_quote!(#wgpu_executor::WgpuExecutorHandle),
|
||||
lend: None,
|
||||
exposed: true,
|
||||
value_source: ParsedValueSource::Scope(Box::new(parse_quote!("graphene_std::platform_application_io::WgpuExecutorNode"))),
|
||||
number_soft_min: None,
|
||||
|
||||
@@ -12,6 +12,7 @@ pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> {
|
||||
validate_min_max,
|
||||
validate_range_slider_bounds,
|
||||
validate_async_source,
|
||||
validate_lend_fields,
|
||||
];
|
||||
|
||||
for validator in validators {
|
||||
@@ -63,6 +64,30 @@ fn validate_async_source(parsed: &ParsedNodeFn) {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_lend_fields(parsed: &ParsedNodeFn) {
|
||||
let future_kernel = crate::codegen::is_source_kernel(&parsed.output_type);
|
||||
for field in &parsed.fields {
|
||||
let ParsedFieldType::Regular(RegularParsedField { lend: Some(reference), .. }) = &field.ty else {
|
||||
continue;
|
||||
};
|
||||
if let Some(mutability) = &reference.mutability {
|
||||
emit_error!(mutability.span(), "reference parameters are read-only lends; `&mut` is not supported");
|
||||
}
|
||||
if let Some(lifetime) = &reference.lifetime {
|
||||
emit_error!(lifetime.span(), "reference parameters use the eval lifetime implicitly; write a bare `&T`");
|
||||
}
|
||||
if field.is_data_field {
|
||||
emit_error!(field.pat_ident.span(), "`#[data]` fields are node-resident state and cannot be references");
|
||||
}
|
||||
if parsed.is_async || future_kernel {
|
||||
emit_error!(
|
||||
field.pat_ident.span(),
|
||||
"source kernels move their inputs into the spawned task, so they cannot take reference parameters"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_min_max(parsed: &ParsedNodeFn) {
|
||||
for field in &parsed.fields {
|
||||
if let ParsedField {
|
||||
|
||||
Reference in New Issue
Block a user