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
|
|
@ -3850,6 +3850,7 @@ dependencies = [
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
"indoc",
|
"indoc",
|
||||||
"proc-macro-crate 3.2.0",
|
"proc-macro-crate 3.2.0",
|
||||||
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.77",
|
"syn 2.0.77",
|
||||||
|
|
|
||||||
|
|
@ -234,8 +234,8 @@ impl ArtboardGroup {
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn layer<F: 'n + Copy + Send>(
|
async fn layer<F: 'n + Copy + Send>(
|
||||||
#[implementations((), Footprint)] footprint: F,
|
#[implementations((), Footprint)] footprint: F,
|
||||||
#[implementations(((), GraphicGroup), (Footprint, GraphicGroup))] stack: impl Node<F, Output = GraphicGroup>,
|
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] stack: impl Node<F, Output = GraphicGroup>,
|
||||||
#[implementations(((), GraphicElement), (Footprint, GraphicElement))] graphic_element: impl Node<F, Output = GraphicElement>,
|
#[implementations(() -> GraphicElement, Footprint -> GraphicElement)] graphic_element: impl Node<F, Output = GraphicElement>,
|
||||||
node_path: Vec<NodeId>,
|
node_path: Vec<NodeId>,
|
||||||
) -> GraphicGroup {
|
) -> GraphicGroup {
|
||||||
let mut element = graphic_element.eval(footprint).await;
|
let mut element = graphic_element.eval(footprint).await;
|
||||||
|
|
@ -257,15 +257,15 @@ async fn layer<F: 'n + Copy + Send>(
|
||||||
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
|
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
|
||||||
#[implementations((), (), (), (), Footprint)] footprint: F,
|
#[implementations((), (), (), (), Footprint)] footprint: F,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
((), VectorData),
|
() -> VectorData,
|
||||||
((), ImageFrame<Color>),
|
() -> ImageFrame<Color>,
|
||||||
((), GraphicGroup),
|
() -> GraphicGroup,
|
||||||
((), TextureFrame),
|
() -> TextureFrame,
|
||||||
(Footprint, VectorData),
|
Footprint -> VectorData,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GraphicGroup),
|
Footprint -> GraphicGroup,
|
||||||
(Footprint, TextureFrame),
|
Footprint -> TextureFrame,
|
||||||
)]
|
)]
|
||||||
data: impl Node<F, Output = Data>,
|
data: impl Node<F, Output = Data>,
|
||||||
) -> GraphicElement {
|
) -> GraphicElement {
|
||||||
data.eval(footprint).await.into()
|
data.eval(footprint).await.into()
|
||||||
|
|
@ -275,14 +275,14 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
|
||||||
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
|
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
|
||||||
#[implementations((), (), (), (), Footprint)] footprint: F,
|
#[implementations((), (), (), (), Footprint)] footprint: F,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
((), VectorData),
|
() -> VectorData,
|
||||||
((), ImageFrame<Color>),
|
() -> ImageFrame<Color>,
|
||||||
((), GraphicGroup),
|
() -> GraphicGroup,
|
||||||
((), TextureFrame),
|
() -> TextureFrame,
|
||||||
(Footprint, VectorData),
|
Footprint -> VectorData,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GraphicGroup),
|
Footprint -> GraphicGroup,
|
||||||
(Footprint, TextureFrame),
|
Footprint -> TextureFrame,
|
||||||
)]
|
)]
|
||||||
element: impl Node<F, Output = Data>,
|
element: impl Node<F, Output = Data>,
|
||||||
) -> GraphicGroup {
|
) -> GraphicGroup {
|
||||||
|
|
@ -292,7 +292,7 @@ async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
|
async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
|
||||||
#[implementations((), Footprint)] mut footprint: F,
|
#[implementations((), Footprint)] mut footprint: F,
|
||||||
#[implementations(((), GraphicGroup), (Footprint, GraphicGroup))] contents: impl Node<F, Output = GraphicGroup>,
|
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] contents: impl Node<F, Output = GraphicGroup>,
|
||||||
label: String,
|
label: String,
|
||||||
location: IVec2,
|
location: IVec2,
|
||||||
dimensions: IVec2,
|
dimensions: IVec2,
|
||||||
|
|
@ -314,8 +314,8 @@ async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn append_artboard<F: 'n + Copy + Send>(
|
async fn append_artboard<F: 'n + Copy + Send>(
|
||||||
#[implementations((), Footprint)] footprint: F,
|
#[implementations((), Footprint)] footprint: F,
|
||||||
#[implementations(((), ArtboardGroup), (Footprint, ArtboardGroup))] artboards: impl Node<F, Output = ArtboardGroup>,
|
#[implementations(() -> ArtboardGroup, Footprint -> ArtboardGroup)] artboards: impl Node<F, Output = ArtboardGroup>,
|
||||||
#[implementations(((), Artboard), (Footprint, Artboard))] artboard: impl Node<F, Output = Artboard>,
|
#[implementations(() -> Artboard, Footprint -> Artboard)] artboard: impl Node<F, Output = Artboard>,
|
||||||
node_path: Vec<NodeId>,
|
node_path: Vec<NodeId>,
|
||||||
) -> ArtboardGroup {
|
) -> ArtboardGroup {
|
||||||
let artboard = artboard.eval(footprint).await;
|
let artboard = artboard.eval(footprint).await;
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ impl SetBlendMode for ImageFrame<Color> {
|
||||||
#[node_macro::node(category("Style"))]
|
#[node_macro::node(category("Style"))]
|
||||||
async fn blend_mode<T: SetBlendMode>(
|
async fn blend_mode<T: SetBlendMode>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, crate::vector::VectorData), (Footprint, crate::GraphicGroup), (Footprint, ImageFrame<Color>))] value: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame<Color>)] value: impl Node<Footprint, Output = T>,
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut value = value.eval(footprint).await;
|
let mut value = value.eval(footprint).await;
|
||||||
|
|
@ -321,7 +321,7 @@ async fn blend_mode<T: SetBlendMode>(
|
||||||
#[node_macro::node(category("Style"))]
|
#[node_macro::node(category("Style"))]
|
||||||
async fn opacity<T: MultiplyAlpha>(
|
async fn opacity<T: MultiplyAlpha>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, crate::vector::VectorData), (Footprint, crate::GraphicGroup), (Footprint, ImageFrame<Color>))] value: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> crate::vector::VectorData, Footprint -> crate::GraphicGroup, Footprint -> ImageFrame<Color>)] value: impl Node<Footprint, Output = T>,
|
||||||
#[default(100.)] factor: Percentage,
|
#[default(100.)] factor: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut value = value.eval(footprint).await;
|
let mut value = value.eval(footprint).await;
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ impl From<BlendMode> for vello::peniko::Mix {
|
||||||
#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite
|
#[node_macro::node(category("Raster: Adjustment"))] // Unique to Graphite
|
||||||
async fn luminance<T: Adjust<Color>>(
|
async fn luminance<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
|
||||||
luminance_calc: LuminanceCalculation,
|
luminance_calc: LuminanceCalculation,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
let mut input = input.eval(footprint).await;
|
||||||
|
|
@ -291,7 +291,7 @@ async fn luminance<T: Adjust<Color>>(
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn extract_channel<T: Adjust<Color>>(
|
async fn extract_channel<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
|
||||||
channel: RedGreenBlueAlpha,
|
channel: RedGreenBlueAlpha,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
let mut input = input.eval(footprint).await;
|
||||||
|
|
@ -308,7 +308,7 @@ async fn extract_channel<T: Adjust<Color>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn make_opaque<T: Adjust<Color>>(footprint: Footprint, #[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>) -> T {
|
async fn make_opaque<T: Adjust<Color>>(footprint: Footprint, #[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
let mut input = input.eval(footprint).await;
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
if color.a() == 0. {
|
if color.a() == 0. {
|
||||||
|
|
@ -323,7 +323,7 @@ async fn make_opaque<T: Adjust<Color>>(footprint: Footprint, #[implementations((
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn levels<T: Adjust<Color>>(
|
async fn levels<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] image: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
|
||||||
#[default(0.)] shadows: Percentage,
|
#[default(0.)] shadows: Percentage,
|
||||||
#[default(50.)] midtones: Percentage,
|
#[default(50.)] midtones: Percentage,
|
||||||
#[default(100.)] highlights: Percentage,
|
#[default(100.)] highlights: Percentage,
|
||||||
|
|
@ -381,7 +381,7 @@ async fn levels<T: Adjust<Color>>(
|
||||||
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
|
#[node_macro::node(name("Black & White"), category("Raster: Adjustment"))]
|
||||||
async fn black_and_white<T: Adjust<Color>>(
|
async fn black_and_white<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] image: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
|
||||||
#[default(000000ff)] tint: Color,
|
#[default(000000ff)] tint: Color,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
#[range((-200., 300.))]
|
#[range((-200., 300.))]
|
||||||
|
|
@ -446,7 +446,7 @@ async fn black_and_white<T: Adjust<Color>>(
|
||||||
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
|
#[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))]
|
||||||
async fn hue_saturation<T: Adjust<Color>>(
|
async fn hue_saturation<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
|
||||||
hue_shift: Angle,
|
hue_shift: Angle,
|
||||||
saturation_shift: SignedPercentage,
|
saturation_shift: SignedPercentage,
|
||||||
lightness_shift: SignedPercentage,
|
lightness_shift: SignedPercentage,
|
||||||
|
|
@ -472,7 +472,7 @@ async fn hue_saturation<T: Adjust<Color>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn invert<T: Adjust<Color>>(footprint: Footprint, #[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>) -> T {
|
async fn invert<T: Adjust<Color>>(footprint: Footprint, #[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>) -> T {
|
||||||
let mut input = input.eval(footprint).await;
|
let mut input = input.eval(footprint).await;
|
||||||
input.adjust(|color| {
|
input.adjust(|color| {
|
||||||
let color = color.to_gamma_srgb();
|
let color = color.to_gamma_srgb();
|
||||||
|
|
@ -487,7 +487,7 @@ async fn invert<T: Adjust<Color>>(footprint: Footprint, #[implementations((Footp
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn threshold<T: Adjust<Color>>(
|
async fn threshold<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] image: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
|
||||||
#[default(50.)] min_luminance: Percentage,
|
#[default(50.)] min_luminance: Percentage,
|
||||||
#[default(100.)] max_luminance: Percentage,
|
#[default(100.)] max_luminance: Percentage,
|
||||||
luminance_calc: LuminanceCalculation,
|
luminance_calc: LuminanceCalculation,
|
||||||
|
|
@ -574,22 +574,22 @@ impl Blend<Color> for GradientStops {
|
||||||
async fn blend<F: 'n + Copy + Send, T: Blend<Color> + Send>(
|
async fn blend<F: 'n + Copy + Send, T: Blend<Color> + Send>(
|
||||||
#[implementations((), (), (), Footprint)] footprint: F,
|
#[implementations((), (), (), Footprint)] footprint: F,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
((), Color),
|
() -> Color,
|
||||||
((), ImageFrame<Color>),
|
() -> ImageFrame<Color>,
|
||||||
((), GradientStops),
|
() -> GradientStops,
|
||||||
(Footprint, Color),
|
Footprint -> Color,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GradientStops),
|
Footprint -> GradientStops,
|
||||||
)]
|
)]
|
||||||
over: impl Node<F, Output = T>,
|
over: impl Node<F, Output = T>,
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(
|
#[implementations(
|
||||||
((), Color),
|
() -> Color,
|
||||||
((), ImageFrame<Color>),
|
() -> ImageFrame<Color>,
|
||||||
((), GradientStops),
|
() -> GradientStops,
|
||||||
(Footprint, Color),
|
Footprint -> Color,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GradientStops),
|
Footprint -> GradientStops,
|
||||||
)]
|
)]
|
||||||
under: impl Node<F, Output = T>,
|
under: impl Node<F, Output = T>,
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
|
|
@ -693,12 +693,12 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode,
|
||||||
async fn gradient_map<F: 'n + Copy + Send, T: Adjust<Color>>(
|
async fn gradient_map<F: 'n + Copy + Send, T: Adjust<Color>>(
|
||||||
#[implementations((), (), (), Footprint)] footprint: F,
|
#[implementations((), (), (), Footprint)] footprint: F,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
((), Color),
|
() -> Color,
|
||||||
((), ImageFrame<Color>),
|
() -> ImageFrame<Color>,
|
||||||
((), GradientStops),
|
() -> GradientStops,
|
||||||
(Footprint, Color),
|
Footprint -> Color,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GradientStops),
|
Footprint -> GradientStops,
|
||||||
)]
|
)]
|
||||||
image: impl Node<F, Output = T>,
|
image: impl Node<F, Output = T>,
|
||||||
gradient: GradientStops,
|
gradient: GradientStops,
|
||||||
|
|
@ -720,7 +720,7 @@ async fn gradient_map<F: 'n + Copy + Send, T: Adjust<Color>>(
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn vibrance<T: Adjust<Color>>(
|
async fn vibrance<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] image: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
|
||||||
vibrance: SignedPercentage,
|
vibrance: SignedPercentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = image.eval(footprint).await;
|
let mut input = image.eval(footprint).await;
|
||||||
|
|
@ -1003,7 +1003,7 @@ impl DomainWarpType {
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn channel_mixer<T: Adjust<Color>>(
|
async fn channel_mixer<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] image: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
|
||||||
|
|
||||||
monochrome: bool,
|
monochrome: bool,
|
||||||
#[default(40.)]
|
#[default(40.)]
|
||||||
|
|
@ -1145,7 +1145,7 @@ impl core::fmt::Display for SelectiveColorChoice {
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn selective_color<T: Adjust<Color>>(
|
async fn selective_color<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] image: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] image: impl Node<Footprint, Output = T>,
|
||||||
mode: RelativeAbsolute,
|
mode: RelativeAbsolute,
|
||||||
#[name("(Reds) Cyan")] r_c: f64,
|
#[name("(Reds) Cyan")] r_c: f64,
|
||||||
#[name("(Reds) Magenta")] r_m: f64,
|
#[name("(Reds) Magenta")] r_m: f64,
|
||||||
|
|
@ -1293,7 +1293,7 @@ impl<P: Pixel> MultiplyAlpha for ImageFrame<P> {
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn posterize<T: Adjust<Color>>(
|
async fn posterize<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
|
||||||
#[default(4)]
|
#[default(4)]
|
||||||
#[min(2.)]
|
#[min(2.)]
|
||||||
levels: u32,
|
levels: u32,
|
||||||
|
|
@ -1317,7 +1317,7 @@ async fn posterize<T: Adjust<Color>>(
|
||||||
#[node_macro::node(category("Raster: Adjustment"))]
|
#[node_macro::node(category("Raster: Adjustment"))]
|
||||||
async fn exposure<T: Adjust<Color>>(
|
async fn exposure<T: Adjust<Color>>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> Color, Footprint -> ImageFrame<Color>)] input: impl Node<Footprint, Output = T>,
|
||||||
exposure: f64,
|
exposure: f64,
|
||||||
offset: f64,
|
offset: f64,
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
|
|
@ -1387,12 +1387,12 @@ fn generate_curves<C: Channel + super::Linear>(_: (), curve: Curve, #[implementa
|
||||||
async fn color_overlay<F: 'n + Copy + Send, T: Adjust<Color>>(
|
async fn color_overlay<F: 'n + Copy + Send, T: Adjust<Color>>(
|
||||||
#[implementations((), (), (), Footprint)] footprint: F,
|
#[implementations((), (), (), Footprint)] footprint: F,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
((), Color),
|
() -> Color,
|
||||||
((), ImageFrame<Color>),
|
() -> ImageFrame<Color>,
|
||||||
((), GradientStops),
|
() -> GradientStops,
|
||||||
(Footprint, Color),
|
Footprint -> Color,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GradientStops),
|
Footprint -> GradientStops,
|
||||||
)]
|
)]
|
||||||
image: impl Node<F, Output = T>,
|
image: impl Node<F, Output = T>,
|
||||||
#[default(000000ff)] color: Color,
|
#[default(000000ff)] color: Color,
|
||||||
|
|
|
||||||
|
|
@ -220,12 +220,12 @@ impl ApplyTransform for () {
|
||||||
async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Sync, T: TransformMut + 'n>(
|
async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Sync, T: TransformMut + 'n>(
|
||||||
#[implementations(Footprint, Footprint, Footprint, (), (), ())] mut input: I,
|
#[implementations(Footprint, Footprint, Footprint, (), (), ())] mut input: I,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(Footprint, VectorData),
|
Footprint -> VectorData,
|
||||||
(Footprint, GraphicGroup),
|
Footprint -> GraphicGroup,
|
||||||
(Footprint, ImageFrame<crate::Color>),
|
Footprint -> ImageFrame<crate::Color>,
|
||||||
((), VectorData),
|
() -> VectorData,
|
||||||
((), GraphicGroup),
|
() -> GraphicGroup,
|
||||||
((), ImageFrame<crate::Color>),
|
() -> ImageFrame<crate::Color>,
|
||||||
)]
|
)]
|
||||||
transform_target: impl Node<I, Output = T>,
|
transform_target: impl Node<I, Output = T>,
|
||||||
translate: DVec2,
|
translate: DVec2,
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ use crate::transform::Footprint;
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn path_modify<F: 'n + Send + Sync + Clone>(
|
async fn path_modify<F: 'n + Send + Sync + Clone>(
|
||||||
#[implementations((), Footprint)] input: F,
|
#[implementations((), Footprint)] input: F,
|
||||||
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
|
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>,
|
||||||
modification: Box<VectorModification>,
|
modification: Box<VectorModification>,
|
||||||
) -> VectorData {
|
) -> VectorData {
|
||||||
let mut vector_data = vector_data.eval(input).await;
|
let mut vector_data = vector_data.eval(input).await;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ impl VectorIterMut for VectorData {
|
||||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||||
async fn assign_colors<T: VectorIterMut>(
|
async fn assign_colors<T: VectorIterMut>(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
#[implementations((Footprint, GraphicGroup), (Footprint, VectorData))] vector_group: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> GraphicGroup, Footprint -> VectorData)] vector_group: impl Node<Footprint, Output = T>,
|
||||||
#[default(true)] fill: bool,
|
#[default(true)] fill: bool,
|
||||||
stroke: bool,
|
stroke: bool,
|
||||||
gradient: GradientStops,
|
gradient: GradientStops,
|
||||||
|
|
@ -177,7 +177,7 @@ async fn circular_repeat(
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn bounding_box<F: 'n + Copy + Send>(
|
async fn bounding_box<F: 'n + Copy + Send>(
|
||||||
#[implementations((), Footprint)] footprint: F,
|
#[implementations((), Footprint)] footprint: F,
|
||||||
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
|
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>,
|
||||||
) -> VectorData {
|
) -> VectorData {
|
||||||
let vector_data = vector_data.eval(footprint).await;
|
let vector_data = vector_data.eval(footprint).await;
|
||||||
|
|
||||||
|
|
@ -253,7 +253,7 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
points: impl Node<Footprint, Output = VectorData>,
|
points: impl Node<Footprint, Output = VectorData>,
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations((Footprint, VectorData), (Footprint, GraphicGroup))]
|
#[implementations(Footprint -> VectorData, Footprint -> GraphicGroup)]
|
||||||
instance: impl Node<Footprint, Output = I>,
|
instance: impl Node<Footprint, Output = I>,
|
||||||
#[default(1)] random_scale_min: f64,
|
#[default(1)] random_scale_min: f64,
|
||||||
#[default(1)] random_scale_max: f64,
|
#[default(1)] random_scale_max: f64,
|
||||||
|
|
@ -387,7 +387,7 @@ async fn sample_points(
|
||||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||||
async fn poisson_disk_points<F: 'n + Copy + Send>(
|
async fn poisson_disk_points<F: 'n + Copy + Send>(
|
||||||
#[implementations((), Footprint)] footprint: F,
|
#[implementations((), Footprint)] footprint: F,
|
||||||
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
|
#[implementations(() -> VectorData, Footprint -> VectorData)] vector_data: impl Node<F, Output = VectorData>,
|
||||||
#[default(10.)]
|
#[default(10.)]
|
||||||
#[min(0.01)]
|
#[min(0.01)]
|
||||||
separation_disk_diameter: f64,
|
separation_disk_diameter: f64,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use graphene_core::Color;
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
async fn image_color_palette<F: 'n + Send>(
|
async fn image_color_palette<F: 'n + Send>(
|
||||||
#[implementations((), Footprint)] footprint: F,
|
#[implementations((), Footprint)] footprint: F,
|
||||||
#[implementations(((), ImageFrame<Color>), (Footprint, ImageFrame<Color>))] image: impl Node<F, Output = ImageFrame<Color>>,
|
#[implementations(() -> ImageFrame<Color>, Footprint -> ImageFrame<Color>)] image: impl Node<F, Output = ImageFrame<Color>>,
|
||||||
#[min(1.)]
|
#[min(1.)]
|
||||||
#[max(28.)]
|
#[max(28.)]
|
||||||
max_size: u32,
|
max_size: u32,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use std::ops::{Div, Mul};
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn boolean_operation<F: 'n + Copy + Send>(
|
async fn boolean_operation<F: 'n + Copy + Send>(
|
||||||
#[implementations((), Footprint)] footprint: F,
|
#[implementations((), Footprint)] footprint: F,
|
||||||
#[implementations(((), GraphicGroup), (Footprint, GraphicGroup))] group_of_paths: impl Node<F, Output = GraphicGroup>,
|
#[implementations(() -> GraphicGroup, Footprint -> GraphicGroup)] group_of_paths: impl Node<F, Output = GraphicGroup>,
|
||||||
operation: BooleanOperation,
|
operation: BooleanOperation,
|
||||||
) -> VectorData {
|
) -> VectorData {
|
||||||
let group_of_paths = group_of_paths.eval(footprint).await;
|
let group_of_paths = group_of_paths.eval(footprint).await;
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
|
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
|
||||||
_: (),
|
_: (),
|
||||||
#[implementations((Footprint, VectorData), (Footprint, ImageFrame<Color>), (Footprint, GraphicGroup))] data: impl Node<Footprint, Output = T>,
|
#[implementations(Footprint -> VectorData, Footprint -> ImageFrame<Color>, Footprint -> GraphicGroup)] data: impl Node<Footprint, Output = T>,
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
||||||
) -> ImageFrame<Color> {
|
) -> ImageFrame<Color> {
|
||||||
|
|
@ -190,17 +190,17 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
||||||
render_config: RenderConfig,
|
render_config: RenderConfig,
|
||||||
editor_api: &'a WasmEditorApi,
|
editor_api: &'a WasmEditorApi,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(Footprint, VectorData),
|
Footprint -> VectorData,
|
||||||
(Footprint, ImageFrame<Color>),
|
Footprint -> ImageFrame<Color>,
|
||||||
(Footprint, GraphicGroup),
|
Footprint -> GraphicGroup,
|
||||||
(Footprint, graphene_core::Artboard),
|
Footprint -> graphene_core::Artboard,
|
||||||
(Footprint, graphene_core::ArtboardGroup),
|
Footprint -> graphene_core::ArtboardGroup,
|
||||||
(Footprint, Option<Color>),
|
Footprint -> Option<Color>,
|
||||||
(Footprint, Vec<Color>),
|
Footprint -> Vec<Color>,
|
||||||
(Footprint, bool),
|
Footprint -> bool,
|
||||||
(Footprint, f32),
|
Footprint -> f32,
|
||||||
(Footprint, f64),
|
Footprint -> f64,
|
||||||
(Footprint, String),
|
Footprint -> String,
|
||||||
)]
|
)]
|
||||||
data: impl Node<Footprint, Output = T>,
|
data: impl Node<Footprint, Output = T>,
|
||||||
_surface_handle: impl Node<(), Output = Option<wgpu_executor::WgpuSurface>>,
|
_surface_handle: impl Node<(), Output = Option<wgpu_executor::WgpuSurface>>,
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
syn = { workspace = true, features = ["extra-traits", "full", "printing", "parsing", "clone-impls", "proc-macro", "visit-mut"] }
|
syn = { workspace = true, features = [ "extra-traits", "full", "printing", "parsing", "clone-impls", "proc-macro", "visit-mut", "visit"] }
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2 = { workspace = true, features = [ "span-locations" ] }
|
||||||
quote = { workspace = true }
|
quote = { workspace = true }
|
||||||
convert_case = { workspace = true }
|
convert_case = { workspace = true }
|
||||||
|
|
||||||
indoc = "2.0.5"
|
indoc = "2.0.5"
|
||||||
proc-macro-crate = "3.1.0"
|
proc-macro-crate = "3.1.0"
|
||||||
|
proc-macro-error = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
graphene-core = { workspace = true }
|
graphene-core = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
|
|
||||||
let all_implementation_types = fields.iter().flat_map(|field| match field {
|
let all_implementation_types = fields.iter().flat_map(|field| match field {
|
||||||
ParsedField::Regular { implementations, .. } => implementations.into_iter().cloned().collect::<Vec<_>>(),
|
ParsedField::Regular { implementations, .. } => implementations.into_iter().cloned().collect::<Vec<_>>(),
|
||||||
ParsedField::Node { implementations, .. } => implementations.into_iter().map(|tuple| syn::Type::Tuple(tuple.clone())).collect(),
|
ParsedField::Node { implementations, .. } => implementations
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|implementation| [implementation.input.clone(), implementation.output.clone()])
|
||||||
|
.collect(),
|
||||||
});
|
});
|
||||||
let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());
|
let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());
|
||||||
|
|
||||||
|
|
@ -200,6 +203,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name)));
|
let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name)));
|
||||||
|
|
||||||
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
|
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
|
||||||
|
let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
/// Underlying implementation for [#struct_name]
|
/// Underlying implementation for [#struct_name]
|
||||||
|
|
@ -227,8 +231,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||||
use gcore::ctor::ctor;
|
use gcore::ctor::ctor;
|
||||||
|
|
||||||
// Use the types specified in the implementation
|
// Use the types specified in the implementation
|
||||||
#[cfg(__never_compiled)]
|
|
||||||
static _IMPORTS: core::marker::PhantomData<#(#all_implementation_types,)*> = core::marker::PhantomData;
|
static #import_name: core::marker::PhantomData<(#(#all_implementation_types,)*)> = core::marker::PhantomData;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct #struct_name<#(#struct_generics,)*> {
|
pub struct #struct_name<#(#struct_generics,)*> {
|
||||||
|
|
@ -286,19 +290,19 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||||
match field {
|
match field {
|
||||||
ParsedField::Regular { implementations, ty, .. } => {
|
ParsedField::Regular { implementations, ty, .. } => {
|
||||||
if !implementations.is_empty() {
|
if !implementations.is_empty() {
|
||||||
implementations.into_iter().map(|ty| (&unit, ty, false)).collect()
|
implementations.iter().map(|ty| (&unit, ty, false)).collect()
|
||||||
} else {
|
} else {
|
||||||
vec![(&unit, ty, false)]
|
vec![(&unit, ty, false)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParsedField::Node {
|
ParsedField::Node {
|
||||||
implementations,
|
implementations,
|
||||||
output_type,
|
|
||||||
input_type,
|
input_type,
|
||||||
|
output_type,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if !implementations.is_empty() {
|
if !implementations.is_empty() {
|
||||||
implementations.into_iter().map(|tup| (&tup.elems[0], &tup.elems[1], true)).collect()
|
implementations.iter().map(|impl_| (&impl_.input, &impl_.output, true)).collect()
|
||||||
} else {
|
} else {
|
||||||
vec![(input_type, output_type, true)]
|
vec![(input_type, output_type, true)]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
|
use proc_macro_error::proc_macro_error;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, punctuated::Punctuated, token::Comma, AngleBracketedGenericArguments, AssocType, FnArg, GenericArgument, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PathArguments,
|
parse_macro_input, punctuated::Punctuated, token::Comma, AngleBracketedGenericArguments, AssocType, FnArg, GenericArgument, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PathArguments,
|
||||||
|
|
@ -8,6 +9,7 @@ use syn::{
|
||||||
|
|
||||||
mod codegen;
|
mod codegen;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
|
mod validation;
|
||||||
|
|
||||||
/// A macro used to construct a proto node implementation from the given struct and the decorated function.
|
/// A macro used to construct a proto node implementation from the given struct and the decorated function.
|
||||||
///
|
///
|
||||||
|
|
@ -102,6 +104,7 @@ pub fn old_node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
new_constructor
|
new_constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
// Performs the `node_impl` macro's functionality of attaching an `impl Node for TheGivenStruct` block to the node struct
|
// Performs the `node_impl` macro's functionality of attaching an `impl Node for TheGivenStruct` block to the node struct
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use indoc::indoc;
|
use indoc::{formatdoc, indoc};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, ToTokens};
|
use quote::{format_ident, ToTokens};
|
||||||
use syn::parse::{Parse, ParseStream, Parser};
|
use syn::parse::{Parse, ParseStream, Parser};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::token::Comma;
|
use syn::token::{Comma, RArrow};
|
||||||
use syn::{Attribute, Error, ExprTuple, FnArg, GenericParam, Ident, ItemFn, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeTuple, WhereClause};
|
use syn::{Attribute, Error, ExprTuple, FnArg, GenericParam, Ident, ItemFn, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, WhereClause};
|
||||||
|
|
||||||
use crate::codegen::generate_node_code;
|
use crate::codegen::generate_node_code;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Implementation {
|
||||||
|
pub(crate) input: Type,
|
||||||
|
pub(crate) _arrow: RArrow,
|
||||||
|
pub(crate) output: Type,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct ParsedNodeFn {
|
pub(crate) struct ParsedNodeFn {
|
||||||
pub(crate) attributes: NodeFnAttributes,
|
pub(crate) attributes: NodeFnAttributes,
|
||||||
|
|
@ -60,7 +67,7 @@ pub(crate) enum ParsedField {
|
||||||
name: Option<LitStr>,
|
name: Option<LitStr>,
|
||||||
input_type: Type,
|
input_type: Type,
|
||||||
output_type: Type,
|
output_type: Type,
|
||||||
implementations: Punctuated<TypeTuple, Comma>,
|
implementations: Punctuated<Implementation, Comma>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -70,6 +77,46 @@ pub(crate) struct Input {
|
||||||
pub(crate) implementations: Punctuated<Type, Comma>,
|
pub(crate) implementations: Punctuated<Type, Comma>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Parse for Implementation {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let input_type: Type = input.parse().map_err(|e| {
|
||||||
|
Error::new(
|
||||||
|
input.span(),
|
||||||
|
formatdoc!(
|
||||||
|
"Failed to parse input type for #[implementation(...)]. Expected a valid Rust type.
|
||||||
|
Error: {}",
|
||||||
|
e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let arrow: RArrow = input.parse().map_err(|_| {
|
||||||
|
Error::new(
|
||||||
|
input.span(),
|
||||||
|
indoc!(
|
||||||
|
"Expected `->` arrow after input type in #[implementations(...)] on a field of type `impl Node`.
|
||||||
|
The correct syntax is `InputType -> OutputType`."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let output_type: Type = input.parse().map_err(|e| {
|
||||||
|
Error::new(
|
||||||
|
input.span(),
|
||||||
|
formatdoc!(
|
||||||
|
"Failed to parse output type for #[implementation(...)]. Expected a valid Rust type after `->`.
|
||||||
|
Error: {}",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Implementation {
|
||||||
|
input: input_type,
|
||||||
|
_arrow: arrow,
|
||||||
|
output: output_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Parse for NodeFnAttributes {
|
impl Parse for NodeFnAttributes {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
let mut category = None;
|
let mut category = None;
|
||||||
|
|
@ -228,14 +275,31 @@ fn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<Pa
|
||||||
Ok((input, fields))
|
Ok((input, fields))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_implementations<T: Parse>(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<T, Comma>> {
|
fn parse_implementations(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<Type, Comma>> {
|
||||||
let content: TokenStream2 = attr
|
let content: TokenStream2 = attr.parse_args()?;
|
||||||
.parse_args()
|
let parser = Punctuated::<Type, Comma>::parse_terminated;
|
||||||
.map_err(|e| Error::new_spanned(attr, format!("Invalid implementations for argument '{}': {}", name, e)))?;
|
parser.parse2(content.clone()).map_err(|e| {
|
||||||
|
let span = e.span(); // Get the span of the error
|
||||||
|
Error::new(span, format!("Failed to parse implementations for argument '{}': {}", name, e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_node_implementations<T: Parse>(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<T, Comma>> {
|
||||||
|
let content: TokenStream2 = attr.parse_args()?;
|
||||||
let parser = Punctuated::<T, Comma>::parse_terminated;
|
let parser = Punctuated::<T, Comma>::parse_terminated;
|
||||||
parser
|
parser.parse2(content.clone()).map_err(|e| {
|
||||||
.parse2(content)
|
Error::new(
|
||||||
.map_err(|e| Error::new_spanned(attr, format!("Failed to parse implementations for argument '{}': {}", name, e)))
|
e.span(),
|
||||||
|
formatdoc!(
|
||||||
|
"Invalid #[implementations(...)] for argument `{}`.
|
||||||
|
Expected a comma-separated list of `InputType -> OutputType` pairs.
|
||||||
|
Example: #[implementations(i32 -> f64, String -> Vec<u8>)]
|
||||||
|
Error: {}",
|
||||||
|
name,
|
||||||
|
e
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Result<ParsedField> {
|
fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Result<ParsedField> {
|
||||||
|
|
@ -300,11 +364,6 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let implementations = extract_attribute(attrs, "implementations")
|
|
||||||
.map(|attr| parse_implementations(attr, ident))
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let (is_node, node_input_type, node_output_type) = parse_node_type(&ty);
|
let (is_node, node_input_type, node_output_type) = parse_node_type(&ty);
|
||||||
|
|
||||||
if is_node {
|
if is_node {
|
||||||
|
|
@ -315,7 +374,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||||
return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed"));
|
return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed"));
|
||||||
}
|
}
|
||||||
let implementations = extract_attribute(attrs, "implementations")
|
let implementations = extract_attribute(attrs, "implementations")
|
||||||
.map(|attr| parse_implementations(attr, ident))
|
.map(|attr| parse_node_implementations(attr, ident))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
|
@ -327,6 +386,10 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||||
implementations,
|
implementations,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
let implementations = extract_attribute(attrs, "implementations")
|
||||||
|
.map(|attr| parse_implementations(attr, ident))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
Ok(ParsedField::Regular {
|
Ok(ParsedField::Regular {
|
||||||
pat_ident,
|
pat_ident,
|
||||||
name,
|
name,
|
||||||
|
|
@ -381,16 +444,16 @@ fn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attri
|
||||||
|
|
||||||
// Modify the new_node_fn function to use the code generation
|
// Modify the new_node_fn function to use the code generation
|
||||||
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||||
match parse_node_fn(attr, item.clone()).and_then(|x| generate_node_code(&x)) {
|
let parse_result = parse_node_fn(attr, item.clone());
|
||||||
Ok(parsed) => {
|
let Ok(parsed_node) = parse_result else {
|
||||||
/*let generated_code = generate_node_code(&parsed);
|
let e = parse_result.unwrap_err();
|
||||||
// panic!("{}", generated_code.to_string());
|
return Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error();
|
||||||
quote! {
|
};
|
||||||
// #item
|
if let Err(e) = crate::validation::validate_node_fn(&parsed_node) {
|
||||||
#generated_code
|
return Error::new(e.span(), format!("Validation Error:\n{e}")).to_compile_error();
|
||||||
}*/
|
}
|
||||||
parsed
|
match generate_node_code(&parsed_node) {
|
||||||
}
|
Ok(parsed) => parsed,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Return the error as a compile error
|
// Return the error as a compile error
|
||||||
Error::new(e.span(), format!("Failed to parse node function: {}", e)).to_compile_error()
|
Error::new(e.span(), format!("Failed to parse node function: {}", e)).to_compile_error()
|
||||||
|
|
@ -403,7 +466,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use proc_macro_crate::FoundCrate;
|
use proc_macro_crate::FoundCrate;
|
||||||
use quote::quote;
|
use quote::{quote, quote_spanned};
|
||||||
use syn::parse_quote;
|
use syn::parse_quote;
|
||||||
fn pat_ident(name: &str) -> PatIdent {
|
fn pat_ident(name: &str) -> PatIdent {
|
||||||
PatIdent {
|
PatIdent {
|
||||||
|
|
@ -869,4 +932,59 @@ mod tests {
|
||||||
);
|
);
|
||||||
parse_node_fn(attr, input).unwrap();
|
parse_node_fn(attr, input).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_implementation_syntax() {
|
||||||
|
let attr = quote!(category("Test"));
|
||||||
|
let input = quote!(
|
||||||
|
fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, ImageFrame<Color>))] input: impl Node<Footprint, Output = T>) -> T {
|
||||||
|
// Implementation details...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = parse_node_fn(attr, input);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
let error_message = error.to_string();
|
||||||
|
assert!(error_message.contains("Invalid #[implementations(...)] for argument `input`"));
|
||||||
|
assert!(error_message.contains("Expected a comma-separated list of `InputType -> OutputType` pairs"));
|
||||||
|
assert!(error_message.contains("Expected `->` arrow after input type in #[implementations(...)] on a field of type `impl Node`"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_implementation_on_first_arg() {
|
||||||
|
let attr = quote!(category("Test"));
|
||||||
|
|
||||||
|
// Use quote_spanned! to attach a specific span to the problematic part
|
||||||
|
let problem_span = proc_macro2::Span::call_site(); // You could create a custom span here if needed
|
||||||
|
let tuples = quote_spanned!(problem_span=> () ());
|
||||||
|
let input = quote! {
|
||||||
|
fn test_node(
|
||||||
|
#[implementations((), #tuples, Footprint)] footprint: F,
|
||||||
|
#[implementations(
|
||||||
|
() -> Color,
|
||||||
|
() -> ImageFrame<Color>,
|
||||||
|
() -> GradientStops,
|
||||||
|
Footprint -> Color,
|
||||||
|
Footprint -> ImageFrame<Color>,
|
||||||
|
Footprint -> GradientStops,
|
||||||
|
)]
|
||||||
|
image: impl Node<F, Output = T>,
|
||||||
|
) -> T {
|
||||||
|
// Implementation details...
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = parse_node_fn(attr, input);
|
||||||
|
assert!(result.is_err(), "Expected an error, but parsing succeeded");
|
||||||
|
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
let error_string = error.to_string();
|
||||||
|
assert!(error_string.contains("Failed to parse implementations for argument 'footprint'"));
|
||||||
|
assert!(error_string.contains("expected `,`"));
|
||||||
|
|
||||||
|
// Instead of checking for exact line and column,
|
||||||
|
// verify that the error span is the one we specified
|
||||||
|
assert_eq!(error.span().start(), problem_span.start());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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…
Reference in New Issue