mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-18 21:25:01 +00:00
Improve node macro and add more diagnostics (#1999)
* Improve node macro ergonomics * Fix type error in stub import * Fix wasm nodes * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
3eb98c6d6d
commit
cd4124a596
15 changed files with 358 additions and 122 deletions
109
node-graph/node-macro/src/validation.rs
Normal file
109
node-graph/node-macro/src/validation.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use crate::parsing::{Implementation, ParsedField, ParsedNodeFn};
|
||||
|
||||
use proc_macro_error::emit_error;
|
||||
use quote::quote;
|
||||
use syn::{spanned::Spanned, GenericParam, Type};
|
||||
|
||||
pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> {
|
||||
let validators: &[fn(&ParsedNodeFn)] = &[
|
||||
// Add more validators here as needed
|
||||
validate_implementations_for_generics,
|
||||
validate_primary_input_expose,
|
||||
];
|
||||
|
||||
for validator in validators {
|
||||
validator(parsed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_primary_input_expose(parsed: &ParsedNodeFn) {
|
||||
if let Some(ParsedField::Regular { exposed: true, pat_ident, .. }) = parsed.fields.first() {
|
||||
emit_error!(
|
||||
pat_ident.span(),
|
||||
"Unnecessary #[expose] attribute on primary input `{}`. Primary inputs are always exposed.",
|
||||
pat_ident.ident;
|
||||
help = "You can safely remove the #[expose] attribute from this field.";
|
||||
note = "The function's second argument, `{}`, is the node's primary input and it's always exposed by default", pat_ident.ident
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_implementations_for_generics(parsed: &ParsedNodeFn) {
|
||||
let has_skip_impl = parsed.attributes.skip_impl;
|
||||
|
||||
if !has_skip_impl && !parsed.fn_generics.is_empty() {
|
||||
for field in &parsed.fields {
|
||||
match field {
|
||||
ParsedField::Regular { ty, implementations, pat_ident, .. } => {
|
||||
if contains_generic_param(ty, &parsed.fn_generics) && implementations.is_empty() {
|
||||
emit_error!(
|
||||
ty.span(),
|
||||
"Generic type `{}` in field `{}` requires an #[implementations(...)] attribute",
|
||||
quote!(#ty),
|
||||
pat_ident.ident;
|
||||
help = "Add #[implementations(ConcreteType1, ConcreteType2)] to field '{}'", pat_ident.ident;
|
||||
help = "Or use #[skip_impl] if you want to manually implement the node"
|
||||
);
|
||||
}
|
||||
}
|
||||
ParsedField::Node {
|
||||
input_type,
|
||||
output_type,
|
||||
implementations,
|
||||
pat_ident,
|
||||
..
|
||||
} => {
|
||||
if (contains_generic_param(input_type, &parsed.fn_generics) || contains_generic_param(output_type, &parsed.fn_generics)) && implementations.is_empty() {
|
||||
emit_error!(
|
||||
pat_ident.span(),
|
||||
"Generic types in Node field `{}` require an #[implementations(...)] attribute",
|
||||
pat_ident.ident;
|
||||
help = "Add #[implementations(InputType1 -> OutputType1, InputType2 -> OutputType2)] to field '{}'", pat_ident.ident;
|
||||
help = "Or use #[skip_impl] if you want to manually implement the node"
|
||||
);
|
||||
}
|
||||
// Additional check for Node implementations
|
||||
for impl_ in implementations {
|
||||
validate_node_implementation(impl_, input_type, output_type, &parsed.fn_generics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_node_implementation(impl_: &Implementation, input_type: &Type, output_type: &Type, fn_generics: &[GenericParam]) {
|
||||
if contains_generic_param(&impl_.input, fn_generics) || contains_generic_param(&impl_.output, fn_generics) {
|
||||
emit_error!(
|
||||
impl_.input.span(),
|
||||
"Implementation types `{}` and `{}` must be concrete, not generic",
|
||||
quote!(#input_type), quote!(#output_type);
|
||||
help = "Replace generic types with concrete types in the implementation"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_generic_param(ty: &Type, fn_generics: &[GenericParam]) -> bool {
|
||||
struct GenericParamChecker<'a> {
|
||||
fn_generics: &'a [GenericParam],
|
||||
found: bool,
|
||||
}
|
||||
|
||||
impl<'a> syn::visit::Visit<'a> for GenericParamChecker<'a> {
|
||||
fn visit_ident(&mut self, ident: &'a syn::Ident) {
|
||||
if self
|
||||
.fn_generics
|
||||
.iter()
|
||||
.any(|param| if let GenericParam::Type(type_param) = param { type_param.ident == *ident } else { false })
|
||||
{
|
||||
self.found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut checker = GenericParamChecker { fn_generics, found: false };
|
||||
syn::visit::visit_type(&mut checker, ty);
|
||||
checker.found
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue