feat: Don't require an explicit return type in the task macro

This commit is contained in:
TechnoPorg authored and GitHub committed 2026-01-29 06:50:12 +01:00
commit 4ad88bcd68
42 files changed
+51 -75

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
//! DAG creation benchmarks //! DAG creation benchmarks
use criterion::Criterion; use criterion::Criterion;
use dagx::{DagRunner, Task}; use dagx::DagRunner;
use std::hint::black_box; use std::hint::black_box;
// Simple task for benchmarking // Simple task for benchmarking
+1 -1
View File
@@ -1,7 +1,7 @@
//! Basic DAG execution benchmarks //! Basic DAG execution benchmarks
use criterion::Criterion; use criterion::Criterion;
use dagx::{task_fn, DagRunner, Task}; use dagx::{task_fn, DagRunner};
use futures::FutureExt; use futures::FutureExt;
pub fn bench_dag_execution(c: &mut Criterion) { pub fn bench_dag_execution(c: &mut Criterion) {
+5 -14
View File
@@ -279,16 +279,7 @@ pub fn task(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Extract return type // Extract return type
let output_type = match &run_method.sig.output { let output_type = match &run_method.sig.output {
ReturnType::Default => { ReturnType::Default => syn::parse_quote!(()),
return syn::Error::new_spanned(
&run_method.sig,
"run() method must have an explicit return type\n\n\
Specify the output type: async fn run(...) -> OutputType\n\
For tasks that don't return a value, use '-> ()'.",
)
.to_compile_error()
.into();
}
ReturnType::Type(_, ty) => ty.clone(), ReturnType::Type(_, ty) => ty.clone(),
}; };
@@ -421,7 +412,7 @@ pub fn task(_attr: TokenStream, item: TokenStream) -> TokenStream {
if is_async { if is_async {
// Async with self // Async with self
quote! { quote! {
impl Task for #struct_name { impl ::dagx::Task for #struct_name {
type Input = #input_type; type Input = #input_type;
type Output = #output_type; type Output = #output_type;
@@ -440,7 +431,7 @@ pub fn task(_attr: TokenStream, item: TokenStream) -> TokenStream {
} else { } else {
// Sync with self - wrap in async block // Sync with self - wrap in async block
quote! { quote! {
impl Task for #struct_name { impl ::dagx::Task for #struct_name {
type Input = #input_type; type Input = #input_type;
type Output = #output_type; type Output = #output_type;
@@ -462,7 +453,7 @@ pub fn task(_attr: TokenStream, item: TokenStream) -> TokenStream {
if is_async { if is_async {
// Async stateless // Async stateless
quote! { quote! {
impl Task for #struct_name { impl ::dagx::Task for #struct_name {
type Input = #input_type; type Input = #input_type;
type Output = #output_type; type Output = #output_type;
@@ -482,7 +473,7 @@ pub fn task(_attr: TokenStream, item: TokenStream) -> TokenStream {
} else { } else {
// Sync stateless // Sync stateless
quote! { quote! {
impl Task for #struct_name { impl ::dagx::Task for #struct_name {
type Input = #input_type; type Input = #input_type;
type Output = #output_type; type Output = #output_type;
+1 -1
View File
@@ -30,7 +30,7 @@
//! Result: 5 //! Result: 5
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Step 1: Define a source task (no dependencies) // Step 1: Define a source task (no dependencies)
+1 -1
View File
@@ -55,7 +55,7 @@
//! Squared: 100 //! Squared: 100
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Source task with a value field // Source task with a value field
+1 -1
View File
@@ -61,7 +61,7 @@
//! Active: true //! Active: true
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
struct FetchName; struct FetchName;
+1 -1
View File
@@ -150,7 +150,7 @@
//! Result 7: Ok("Success") //! Result 7: Ok("Success")
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
+1 -1
View File
@@ -66,7 +66,7 @@
//! Combined: 6 items, sum=57, avg=9.50 //! Combined: 6 items, sum=57, avg=9.50
//! ``` //! ```
use dagx::{task, DagResult, DagRunner, Task}; use dagx::{task, DagResult, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Extract: Load data from different sources // Extract: Load data from different sources
+1 -1
View File
@@ -55,7 +55,7 @@
//! Expected: Categories: [low, invalid, high] //! Expected: Categories: [low, invalid, high]
//! ``` //! ```
use dagx::{task, DagResult, DagRunner, Task}; use dagx::{task, DagResult, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Task that validates and processes input // Task that validates and processes input
+1 -1
View File
@@ -36,7 +36,7 @@
//! Validation: Alice is valid (age 30 >= 18) //! Validation: Alice is valid (age 30 >= 18)
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Custom type - just derive Clone! // Custom type - just derive Clone!
+1 -1
View File
@@ -75,7 +75,7 @@
//! ============================ //! ============================
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Data source tasks // Data source tasks
+1 -1
View File
@@ -114,7 +114,7 @@
//! FulfillOrder: 30ms //! FulfillOrder: 30ms
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::time::Instant; use std::time::Instant;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
+1 -1
View File
@@ -114,7 +114,7 @@
//! === Error Handling Example Complete === //! === Error Handling Example Complete ===
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
// Source task that provides a string input // Source task that provides a string input
+1 -1
View File
@@ -109,7 +109,7 @@
//! - Memory overhead is minimal (~ 200 bytes per task) //! - Memory overhead is minimal (~ 200 bytes per task)
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::time::Instant; use std::time::Instant;
+1 -1
View File
@@ -47,7 +47,7 @@
//! This proves TRUE PARALLEL EXECUTION! //! This proves TRUE PARALLEL EXECUTION!
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tokio::time::sleep; use tokio::time::sleep;
+1 -1
View File
@@ -152,7 +152,7 @@
//! Result: Err("Retry exhausted after 3 attempts: Connection timeout") //! Result: Err("Retry exhausted after 3 attempts: Connection timeout")
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
+1 -1
View File
@@ -98,7 +98,7 @@
//! === Timeout Example Complete === //! === Timeout Example Complete ===
//! ``` //! ```
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use tokio::time::{sleep, timeout, Duration}; use tokio::time::{sleep, timeout, Duration};
+1 -1
View File
@@ -33,7 +33,7 @@
//! - **ERROR**: Errors, panics, cycle detection //! - **ERROR**: Errors, panics, cycle detection
//! //!
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::{fmt, EnvFilter};
-1
View File
@@ -2,7 +2,6 @@
use crate::builder::IsUnitType; use crate::builder::IsUnitType;
use crate::runner::DagRunner; use crate::runner::DagRunner;
use crate::task::Task;
use crate::types::TaskHandle; use crate::types::TaskHandle;
// Initialize tracing subscriber for tests (idempotent) // Initialize tracing subscriber for tests (idempotent)
-1
View File
@@ -2,7 +2,6 @@
use crate::deps::DepsTuple; use crate::deps::DepsTuple;
use crate::runner::DagRunner; use crate::runner::DagRunner;
use crate::task::Task;
use crate::types::{NodeId, TaskHandle}; use crate::types::{NodeId, TaskHandle};
// Test task for dependency testing // Test task for dependency testing
-1
View File
@@ -3,7 +3,6 @@
#![allow(dead_code)] // Test helpers generate unused code via macro #![allow(dead_code)] // Test helpers generate unused code via macro
use crate::extract::ExtractInput; use crate::extract::ExtractInput;
use crate::task::Task;
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
+2
View File
@@ -774,6 +774,8 @@
#![allow(private_bounds, private_interfaces)] #![allow(private_bounds, private_interfaces)]
extern crate self as dagx;
// Module declarations // Module declarations
mod builder; mod builder;
pub mod cycle_prevention; pub mod cycle_prevention;
-1
View File
@@ -3,7 +3,6 @@
use std::sync::Arc; use std::sync::Arc;
use crate::node::{ExecutableNode, TypedNode}; use crate::node::{ExecutableNode, TypedNode};
use crate::task::Task;
use crate::types::NodeId; use crate::types::NodeId;
// Simple test task // Simple test task
+3 -3
View File
@@ -235,7 +235,7 @@ impl DagRunner {
/// # Parameters /// # Parameters
/// ///
/// - `spawner`: A function that spawns futures on the async runtime /// - `spawner`: A function that spawns futures on the async runtime
/// and returns a handle to the task. This is the only way to run tasks on separate threads. Examples: /// and returns a handle to the task. This is the only way to run tasks on separate threads. Examples:
/// - Tokio: `|fut| { tokio::spawn(fut).map(Result::unwrap) }` /// - Tokio: `|fut| { tokio::spawn(fut).map(Result::unwrap) }`
/// - Smol: `|fut| { smol::spawn(fut) }` /// - Smol: `|fut| { smol::spawn(fut) }`
/// - Async-std: `|fut| { async_std::task::spawn(fut) }` /// - Async-std: `|fut| { async_std::task::spawn(fut) }`
@@ -374,7 +374,7 @@ impl DagRunner {
let dependencies: Vec<_> = edges[&node_id] let dependencies: Vec<_> = edges[&node_id]
.iter() .iter()
.flat_map(|dep| outputs.get(dep)) .flat_map(|dep| outputs.get(dep))
.map(|value| value.clone()) .cloned()
.collect(); .collect();
// Execute inline with panic handling. // Execute inline with panic handling.
@@ -437,7 +437,7 @@ impl DagRunner {
let dependencies: Vec<_> = edges[&node_id] let dependencies: Vec<_> = edges[&node_id]
.iter() .iter()
.flat_map(|dep| outputs.get(dep)) .flat_map(|dep| outputs.get(dep))
.map(|value| value.clone()) .cloned()
.collect(); .collect();
// Spawn the task using the provided spawner // Spawn the task using the provided spawner
-1
View File
@@ -4,7 +4,6 @@
use crate::error::DagError; use crate::error::DagError;
use crate::runner::DagRunner; use crate::runner::DagRunner;
use crate::task::Task;
use crate::types::TaskHandle; use crate::types::TaskHandle;
use futures::FutureExt; use futures::FutureExt;
use std::collections::HashMap; use std::collections::HashMap;
+4 -11
View File
@@ -2,7 +2,6 @@
use crate::error::DagError; use crate::error::DagError;
use crate::runner::DagRunner; use crate::runner::DagRunner;
use crate::task::Task;
use crate::types::TaskHandle; use crate::types::TaskHandle;
use futures::FutureExt; use futures::FutureExt;
@@ -161,17 +160,11 @@ async fn test_concurrent_run_protection() {
assert!(result1.is_ok() != result2.is_ok()); assert!(result1.is_ok() != result2.is_ok());
// The error should be about concurrent execution // The error should be about concurrent execution
if result1.is_err() { if let Err(e) = result1 {
assert!(matches!( assert!(matches!(e, crate::error::DagError::ConcurrentExecution));
result1.unwrap_err(),
crate::error::DagError::ConcurrentExecution
));
} }
if result2.is_err() { if let Err(e) = result2 {
assert!(matches!( assert!(matches!(e, crate::error::DagError::ConcurrentExecution));
result2.unwrap_err(),
crate::error::DagError::ConcurrentExecution
));
} }
} }
-1
View File
@@ -167,7 +167,6 @@ async fn test_task_fn_with_option_type() {
#[tokio::test] #[tokio::test]
async fn test_task_fn_explicit_trait_call() { async fn test_task_fn_explicit_trait_call() {
// Test explicit Task trait method call to cover line 121 // Test explicit Task trait method call to cover line 121
use crate::task::Task;
let task = task_fn(|x: i32| async move { x * 3 }); let task = task_fn(|x: i32| async move { x * 3 });
+1 -2
View File
@@ -2,8 +2,7 @@
//! //!
//! These tests ensure we hit line 71 in the From<TaskBuilder> implementation //! These tests ensure we hit line 71 in the From<TaskBuilder> implementation
use crate::types::TaskHandle; use crate::{task, DagRunner, TaskHandle};
use crate::{task, DagRunner, Task};
use futures::FutureExt; use futures::FutureExt;
#[tokio::test] #[tokio::test]
-1
View File
@@ -1,6 +1,5 @@
//! Unit tests for types module //! Unit tests for types module
use crate::task::Task;
use crate::types::{NodeId, TaskHandle}; use crate::types::{NodeId, TaskHandle};
use std::marker::PhantomData; use std::marker::PhantomData;
+1 -1
View File
@@ -9,7 +9,7 @@
//! //!
//! These tests serve as both validation and documentation for proper Arc usage. //! These tests serve as both validation and documentation for proper Arc usage.
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
+1 -1
View File
@@ -1,7 +1,7 @@
//! Tests for empty DAGs and minimal task configurations //! Tests for empty DAGs and minimal task configurations
use crate::common::task_fn; use crate::common::task_fn;
use dagx::{task, DagResult, DagRunner, Task}; use dagx::{task, DagResult, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
+1 -1
View File
@@ -7,7 +7,7 @@ pub use dagx::task_fn;
// Re-export commonly used test structs // Re-export commonly used test structs
#[allow(dead_code)] #[allow(dead_code)]
pub mod tasks { pub mod tasks {
use dagx::{task, Task}; use dagx::task;
// Simple seed task with mutable state // Simple seed task with mutable state
pub struct Seed { pub struct Seed {
+1 -1
View File
@@ -1,5 +1,5 @@
// Test that custom types work without any trait implementations // Test that custom types work without any trait implementations
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
+1 -1
View File
@@ -3,7 +3,7 @@
//! These tests show that even if you try to create cycles, the Rust compiler //! These tests show that even if you try to create cycles, the Rust compiler
//! prevents it through the type-state pattern. //! prevents it through the type-state pattern.
use dagx::{task, DagRunner, Task, TaskHandle}; use dagx::{task, DagRunner, TaskHandle};
use futures::FutureExt; use futures::FutureExt;
/// Demonstrates that TaskBuilder is consumed by depends_on, /// Demonstrates that TaskBuilder is consumed by depends_on,
+2 -1
View File
@@ -1,7 +1,8 @@
//! Tests for timing and speedup verification //! Tests for timing and speedup verification
use crate::common::task_fn; use crate::common::task_fn;
use dagx::{DagResult, DagRunner, Task};
use dagx::{DagResult, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
+1
View File
@@ -12,6 +12,7 @@ async fn test_2d_grid_pattern() -> DagResult<()> {
let mut grid: Vec<Vec<Option<dagx::TaskHandle<i32>>>> = vec![vec![None; 4]; 4]; let mut grid: Vec<Vec<Option<dagx::TaskHandle<i32>>>> = vec![vec![None; 4]; 4];
// Initialize first row and column // Initialize first row and column
#[allow(clippy::needless_range_loop)]
for i in 0..4 { for i in 0..4 {
let task = dag.add_task(task_fn(move |_: ()| async move { i as i32 })); let task = dag.add_task(task_fn(move |_: ()| async move { i as i32 }));
grid[0][i] = Some((&task).into()); grid[0][i] = Some((&task).into());
+4 -4
View File
@@ -1,7 +1,7 @@
//! Runtime compatibility tests for async-std //! Runtime compatibility tests for async-std
use crate::common::task_fn; use crate::common::task_fn;
use dagx::{DagRunner, Task}; use dagx::DagRunner;
struct Value(i32); struct Value(i32);
#[dagx::task] #[dagx::task]
@@ -27,7 +27,7 @@ async fn test_basic_dag_async_std() {
let y = dag.add_task(Value(3)); let y = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&x, &y)); let sum = dag.add_task(Add).depends_on((&x, &y));
dag.run(|fut| async_std::task::spawn(fut)).await.unwrap(); dag.run(async_std::task::spawn).await.unwrap();
assert_eq!(dag.get(sum).unwrap(), 5); assert_eq!(dag.get(sum).unwrap(), 5);
} }
@@ -40,7 +40,7 @@ async fn test_parallel_execution_async_std() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 }))) .map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 })))
.collect(); .collect();
dag.run(|fut| async_std::task::spawn(fut)).await.unwrap(); dag.run(async_std::task::spawn).await.unwrap();
for (i, task) in tasks.iter().enumerate() { for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i * 2); assert_eq!(dag.get(task).unwrap(), i * 2);
@@ -58,7 +58,7 @@ async fn test_complex_dependencies_async_std() {
.add_task(task_fn(|x: i32| async move { x * 2 })) .add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(sum); .depends_on(sum);
dag.run(|fut| async_std::task::spawn(fut)).await.unwrap(); dag.run(async_std::task::spawn).await.unwrap();
assert_eq!(dag.get(double).unwrap(), 60); assert_eq!(dag.get(double).unwrap(), 60);
} }
+1 -1
View File
@@ -1,6 +1,6 @@
//! Runtime integration tests - verify dagx works correctly across different runtimes //! Runtime integration tests - verify dagx works correctly across different runtimes
use dagx::{DagResult, DagRunner, Task}; use dagx::{DagResult, DagRunner};
use futures::FutureExt; use futures::FutureExt;
struct Value(i32); struct Value(i32);
-4
View File
@@ -444,8 +444,6 @@ fn test_futures_executor_multi_pool() {
#[test] #[test]
fn test_sync_tasks_on_async_executor() { fn test_sync_tasks_on_async_executor() {
use dagx::Task;
struct SyncAdd; struct SyncAdd;
#[dagx::task] #[dagx::task]
@@ -473,8 +471,6 @@ fn test_sync_tasks_on_async_executor() {
#[test] #[test]
fn test_sync_tasks_on_pollster() { fn test_sync_tasks_on_pollster() {
use dagx::Task;
struct SyncDouble; struct SyncDouble;
#[dagx::task] #[dagx::task]
+4 -4
View File
@@ -1,7 +1,7 @@
//! Runtime compatibility tests for smol //! Runtime compatibility tests for smol
use crate::common::task_fn; use crate::common::task_fn;
use dagx::{DagRunner, Task}; use dagx::DagRunner;
struct Value(i32); struct Value(i32);
#[dagx::task] #[dagx::task]
@@ -28,7 +28,7 @@ fn test_basic_dag_smol() {
let y = dag.add_task(Value(3)); let y = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&x, &y)); let sum = dag.add_task(Add).depends_on((&x, &y));
dag.run(|fut| smol::spawn(fut)).await.unwrap(); dag.run(smol::spawn).await.unwrap();
assert_eq!(dag.get(sum).unwrap(), 5); assert_eq!(dag.get(sum).unwrap(), 5);
}); });
@@ -43,7 +43,7 @@ fn test_parallel_execution_smol() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 }))) .map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 })))
.collect(); .collect();
dag.run(|fut| smol::spawn(fut)).await.unwrap(); dag.run(smol::spawn).await.unwrap();
for (i, task) in tasks.iter().enumerate() { for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i * 2); assert_eq!(dag.get(task).unwrap(), i * 2);
@@ -63,7 +63,7 @@ fn test_complex_dependencies_smol() {
.add_task(task_fn(|x: i32| async move { x * 2 })) .add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(sum); .depends_on(sum);
dag.run(|fut| smol::spawn(fut)).await.unwrap(); dag.run(smol::spawn).await.unwrap();
assert_eq!(dag.get(double).unwrap(), 60); assert_eq!(dag.get(double).unwrap(), 60);
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
//! Tests with tracing feature enabled //! Tests with tracing feature enabled
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::{fmt, EnvFilter};
+1 -1
View File
@@ -1,6 +1,6 @@
//! Tests that the library works correctly without tracing feature //! Tests that the library works correctly without tracing feature
use dagx::{task, DagRunner, Task}; use dagx::{task, DagRunner};
use futures::FutureExt; use futures::FutureExt;
struct Value(i32); struct Value(i32);