fp-macros 0.8.0

Procedural macros for generating and working with Higher-Kinded Type (HKT) traits in the fp-library crate.
Documentation
#![expect(clippy::todo, reason = "Tests use panicking operations for brevity and clarity")]

use fp_macros::{
	document_module,
	impl_kind,
	trait_kind,
};

#[document_module(no_validation)]
mod test_mod {
	use super::*;

	trait_kind!(
		type Of<T>;
	);

	#[allow(dead_code, reason = "Test fixture for document_module macro")]
	pub struct MyBrand;
	#[allow(dead_code, reason = "Test fixture exists to exercise document_module macro")]
	pub struct MyType<T>(T);

	impl_kind! {
		for MyBrand {
			#[document_default]
			type Of<T> = MyType<T>;
		}
	}

	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub trait Functor {
		fn map<A, B>(
			self,
			f: impl Fn(A) -> B,
		) -> MyType<B>;
	}

	impl Functor for MyBrand {
		#[document_signature]
		fn map<A, B>(
			self,
			_f: impl Fn(A) -> B,
		) -> MyType<B> {
			todo!()
		}
	}
}

#[test]
fn test_document_module_integration() {
	// Compile-time test
}

#[test]
fn test_positional_matching() {
	// This is a compile-fail test or we can check the generated docs if we had a way.
	// For now, we just ensure it compiles.
}

#[document_module(no_validation)]
mod test_collision {
	use fp_macros::{
		impl_kind,
		trait_kind,
	};
	trait_kind!(
		type Of<T>;
	);
	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub struct Brand;
	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub struct MyType<T>(T);
	impl_kind! {
		for Brand {
			type Of<A> = MyType<A>;
		}
	}

	#[fp_macros::document_module]
	#[expect(unexpected_cfgs, reason = "Testing cfg-gated items in document_module")]
	mod test_cfg_no_conflict {
		use fp_macros::impl_kind;
		#[allow(dead_code, reason = "Test fixture for document_module macro")]
		pub struct Brand;
		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		pub struct SyncType<T>(T);
		#[allow(dead_code, reason = "Test fixture for document_module macro")]
		pub struct AsyncType<T>(T);

		#[cfg(feature = "sync")]
		impl_kind! {
			#[multi_brand]
			for Brand {
				type Of<T> = SyncType<T>;
			}
		}

		#[cfg(not(feature = "sync"))]
		impl_kind! {
			#[multi_brand]
			for Brand {
				type Of<T> = AsyncType<T>;
			}
		}

		// Add a manual impl of the Kind trait to satisfy the compiler
		// This allows document_module to scan it without erroring on missing trait
		#[allow(
			dead_code,
			non_camel_case_types,
			reason = "Test fixture exists to exercise document_module macro"
		)]
		trait Kind_ad6c20556a82a1f0 {
			type Of<T>;
		}

		#[allow(
			dead_code,
			non_camel_case_types,
			reason = "Test fixture for InferableBrand trait generated by impl_kind!"
		)]
		trait InferableBrand_ad6c20556a82a1f0<__InferableBrand_Brand: Kind_ad6c20556a82a1f0, T> {
			type Marker;
		}
	}

	#[fp_macros::document_module]
	mod test_dyn_formatting {
		#[allow(dead_code, reason = "Test fixture exists to exercise document_module macro")]
		pub trait MyTrait {}

		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		pub struct Brand;

		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		pub trait TestTrait {
			#[document_signature]
			fn foo() -> Box<dyn MyTrait>;
		}
	}
	// We can't have two impl_kind! for the same Brand in the same module
	// if they implement the same trait. But document_module should still
	// be able to merge them if they were valid.
	// For the sake of this test, we'll use a different Brand for the second one
	// or just test that one block works.
}

#[document_module(no_validation)]
mod test_erasure {
	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub struct Brand;
	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub trait MyTrait {
		#[document_signature]
		#[expect(
			clippy::needless_lifetimes,
			reason = "Testing lifetime handling in document_module macro"
		)]
		unsafe fn foo<'a, T: ?Sized>(x: &'a T) -> &'a T;
	}
}

#[document_module(no_validation)]
mod test_impl_level_document_parameters {
	use fp_macros::document_parameters;

	#[allow(dead_code, reason = "Test fixture exists to exercise document_module macro")]
	pub struct MyList<T>(Vec<T>);

	/// Test impl-level document_parameters with receiver-only method
	#[document_type_parameters("The type of elements in the list")]
	#[document_parameters("The list instance")]
	impl<T> MyList<T> {
		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		#[document_signature]
		#[document_parameters]
		pub fn len(&self) -> usize {
			self.0.len()
		}

		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		#[document_signature]
		#[document_parameters]
		pub fn is_empty(&self) -> bool {
			self.0.is_empty()
		}

		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		#[document_signature]
		#[document_parameters("The element to append")]
		pub fn push(
			&mut self,
			item: T,
		) {
			self.0.push(item)
		}

		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		#[document_signature]
		#[document_parameters("The element to prepend")]
		pub fn cons(
			self,
			item: T,
		) -> Self {
			let mut new_vec = vec![item];
			new_vec.extend(self.0);
			MyList(new_vec)
		}

		// Static method (no receiver) should work without impl-level receiver doc
		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		#[document_signature]
		#[document_parameters("The initial capacity")]
		pub fn with_capacity(capacity: usize) -> Self {
			MyList(Vec::with_capacity(capacity))
		}
	}

	/// Test multiple impl blocks for the same type
	#[document_parameters("The list to operate on")]
	impl<T: Clone> MyList<T> {
		#[expect(dead_code, reason = "Test fixture for document_module macro")]
		#[document_signature]
		#[document_parameters]
		pub fn clone_list(&self) -> Self {
			MyList(self.0.clone())
		}
	}
}

#[test]
fn test_impl_level_document_parameters_integration() {
	// Compile-time test to ensure impl-level document_parameters works
}

// --- Trait support tests ---

/// Trait with fully documented methods - no validation warnings expected.
#[document_module]
mod test_trait_fully_documented {
	use fp_macros::{
		document_examples,
		document_parameters,
		document_returns,
	};

	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	#[fp_macros::document_type_parameters("The element type.")]
	#[document_parameters("The collection instance.")]
	pub trait MyCollection<T> {
		/// Returns the number of elements.
		#[fp_macros::document_signature]
		#[document_returns("The number of elements in the collection.")]
		#[document_examples]
		///
		/// ```
		/// // Example placeholder
		/// assert!(true);
		/// ```
		fn len(&self) -> usize;

		/// Adds an element.
		#[fp_macros::document_signature]
		#[document_parameters("The element to add.")]
		#[document_returns("Whether the element was added.")]
		#[document_examples]
		///
		/// ```
		/// assert!(true);
		/// ```
		fn add(
			&mut self,
			item: T,
		) -> bool;
	}
}

/// Marker trait (no methods) - no validation warnings expected.
#[document_module]
mod test_marker_trait {
	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub trait Marker {}
}

/// Module with both trait and impl - both validated/generated.
#[document_module(no_validation)]
mod test_trait_and_impl_together {
	use fp_macros::document_parameters;

	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	pub trait Greet {
		#[document_signature]
		fn greet(&self) -> String;
	}

	#[allow(dead_code, reason = "Test fixture for document_module macro")]
	pub struct Greeter;

	#[document_parameters("The greeter instance.")]
	impl Greet for Greeter {
		#[document_signature]
		#[document_parameters]
		fn greet(&self) -> String {
			"hello".into()
		}
	}
}

/// Trait-level #[document_parameters] - receiver doc applied to methods.
#[document_module(no_validation)]
mod test_trait_level_document_parameters {
	use fp_macros::document_parameters;

	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	#[document_parameters("The stack instance.")]
	pub trait Stack<T> {
		#[document_parameters("The element to push.")]
		fn push(
			&mut self,
			item: T,
		);

		#[document_parameters]
		fn pop(&mut self) -> Option<T>;
	}
}

#[test]
fn test_trait_support_integration() {
	// Compile-time test: trait support in document_module
}

#[test]
fn test_trait_level_document_parameters_integration() {
	// Compile-time test: trait-level #[document_parameters] with receiver doc
}

/// Trait with #[document_signature] on methods — correct ordering.
#[document_module]
mod test_trait_signature_with_examples {
	use fp_macros::{
		document_examples,
		document_returns,
	};

	#[expect(dead_code, reason = "Test fixture for document_module macro")]
	/// A test trait with examples.
	#[document_examples]
	///
	/// ```
	/// assert!(true);
	/// ```
	pub trait Testable {
		/// Does a thing.
		#[fp_macros::document_signature]
		#[document_returns("A result.")]
		#[document_examples]
		///
		/// ```
		/// assert!(true);
		/// ```
		fn do_thing() -> bool;
	}
}

#[test]
fn test_trait_signature_with_examples() {
	// Compile-time test: #[document_signature] on trait methods
}