File size: 5,863 Bytes
2409829 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
use crate::parsing::{Implementation, ParsedField, ParsedNodeFn};
use proc_macro_error2::emit_error;
use quote::quote;
use syn::spanned::Spanned;
use syn::{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,
validate_min_max,
];
for validator in validators {
validator(parsed);
}
Ok(())
}
fn validate_min_max(parsed: &ParsedNodeFn) {
for field in &parsed.fields {
if let ParsedField::Regular {
number_hard_max,
number_hard_min,
number_soft_max,
number_soft_min,
pat_ident,
..
} = field
{
if let (Some(soft_min), Some(hard_min)) = (number_soft_min, number_hard_min) {
let soft_min_value: f64 = soft_min.base10_parse().unwrap_or_default();
let hard_min_value: f64 = hard_min.base10_parse().unwrap_or_default();
if soft_min_value == hard_min_value {
emit_error!(
pat_ident.span(),
"Unnecessary #[soft_min] attribute on `{}`, as #[hard_min] has the same value.",
pat_ident.ident;
help = "You can safely remove the #[soft_min] attribute from this field.";
note = "#[soft_min] is redundant when it equals #[hard_min].",
);
} else if soft_min_value < hard_min_value {
emit_error!(
pat_ident.span(),
"The #[soft_min] attribute on `{}` is incorrectly greater than #[hard_min].",
pat_ident.ident;
help = "You probably meant to reverse the two attribute values.";
note = "Allowing the possible slider range to preceed #[hard_min] doesn't make sense.",
);
}
}
if let (Some(soft_max), Some(hard_max)) = (number_soft_max, number_hard_max) {
let soft_max_value: f64 = soft_max.base10_parse().unwrap_or_default();
let hard_max_value: f64 = hard_max.base10_parse().unwrap_or_default();
if soft_max_value == hard_max_value {
emit_error!(
pat_ident.span(),
"Unnecessary #[soft_max] attribute on `{}`, as #[hard_max] has the same value.",
pat_ident.ident;
help = "You can safely remove the #[soft_max] attribute from this field.";
note = "#[soft_max] is redundant when it equals #[hard_max].",
);
} else if soft_max_value < hard_max_value {
emit_error!(
pat_ident.span(),
"The #[soft_max] attribute on `{}` is incorrectly greater than #[hard_max].",
pat_ident.ident;
help = "You probably meant to reverse the two attribute values.";
note = "Allowing the possible slider range to exceed #[hard_max] doesn't make sense.",
);
}
}
}
}
}
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
}
|