From 82a974c126421e322c5c1168a85f58b056385bcf Mon Sep 17 00:00:00 2001 From: Serhii Potapov Date: Sat, 31 Jan 2026 13:53:07 +0100 Subject: [PATCH] Support const fn kind() --- CHANGELOG.md | 1 + README.md | 18 +++++- kinded/src/lib.rs | 29 ++++++++-- kinded_macros/src/generate/main_enum.rs | 2 +- test_suite/src/lib.rs | 74 +++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6ce478..ab323fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Unreleased - Add `attrs` attribute to pass extra attributes to the generated kind enum (e.g., `#[kinded(attrs(serde(rename_all = "snake_case")))]`) +- Make `kind()` method `const fn`, allowing usage in const contexts (fixes #12) ## v0.4.1 - 2026-01-29 - Add `#[kinded(rename = "...")]` attribute for variants to customize display/parse names. diff --git a/README.md b/README.md index 55fc20b..a897e67 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ enum DrinkKind { } impl Drink { - fn kind(&self) -> DrinkKind { + const fn kind(&self) -> DrinkKind { match self { Drink::Mate => DrinkKind::Mate, Drink::Coffee(..) => DrinkKind::Coffee, @@ -46,6 +46,22 @@ impl Drink { } ``` +## Const context + +The `kind()` method is a `const fn`, so it can be used in const contexts: + +```rs +use kinded::Kinded; + +#[derive(Kinded)] +enum Status { + Active, + Inactive, +} + +const ACTIVE_KIND: StatusKind = Status::Active.kind(); +``` + ## Kinded trait The library provides `Kinded` trait: diff --git a/kinded/src/lib.rs b/kinded/src/lib.rs index 6d703af..a7838e7 100644 --- a/kinded/src/lib.rs +++ b/kinded/src/lib.rs @@ -30,18 +30,37 @@ //! } //! //! impl Drink { -//! fn kind(&self) -> DrinkKind { -//! Drink::Mate => DrinkKind::Mate, -//! Drink::Coffee(..) => DrinkKind::Coffee, -//! Drink::Tea { .. } => DrinkKind::Tea, +//! const fn kind(&self) -> DrinkKind { +//! match self { +//! Drink::Mate => DrinkKind::Mate, +//! Drink::Coffee(..) => DrinkKind::Coffee, +//! Drink::Tea { .. } => DrinkKind::Tea, +//! } //! } //! } +//! ``` +//! +//! ## Const context +//! +//! The `kind()` method is a `const fn`, so it can be used in const contexts: +//! +//! ``` +//! use kinded::Kinded; +//! +//! #[derive(Kinded)] +//! enum Status { +//! Active, +//! Inactive, +//! } +//! +//! const ACTIVE_KIND: StatusKind = Status::Active.kind(); +//! ``` //! //! ## Kinded trait //! //! The library provides `Kinded` trait: //! -//! ```rs +//! ```ignore //! pub trait Kinded { //! type Kind: PartialEq + Eq + Debug + Clone + Copy; //! diff --git a/kinded_macros/src/generate/main_enum.rs b/kinded_macros/src/generate/main_enum.rs index 12333d5..4c23c3f 100644 --- a/kinded_macros/src/generate/main_enum.rs +++ b/kinded_macros/src/generate/main_enum.rs @@ -27,7 +27,7 @@ fn gen_fn_kind(meta: &Meta) -> TokenStream { .map(|variant| gen_match_branch(name, &kind_name, variant)); quote!( - pub fn kind(&self) -> #kind_name { // pub fn kind(&self) -> DrinkKind { + pub const fn kind(&self) -> #kind_name { // pub const fn kind(&self) -> DrinkKind { match self { // match self { #(#match_branches),* // Drink::Coffee(..) => DrinkKind::Coffee, } // } diff --git a/test_suite/src/lib.rs b/test_suite/src/lib.rs index 66b024f..54bfee3 100644 --- a/test_suite/src/lib.rs +++ b/test_suite/src/lib.rs @@ -475,6 +475,80 @@ fn should_work_with_lifetimes() { assert_eq!(identifier.kind(), IdentifierKind::Name); } +mod const_kind { + use kinded::Kinded; + + #[derive(Kinded)] + enum Status { + Active, + Inactive, + Pending(i32), + Custom { value: i32 }, + } + + /// Test that kind() can be used in const context with unit variant + const ACTIVE_KIND: StatusKind = Status::Active.kind(); + + /// Test that kind() can be used in const context with unit variant (another) + const INACTIVE_KIND: StatusKind = Status::Inactive.kind(); + + #[test] + fn should_work_in_const_context_unit_variant() { + assert_eq!(ACTIVE_KIND, StatusKind::Active); + assert_eq!(INACTIVE_KIND, StatusKind::Inactive); + } + + #[test] + fn should_work_in_const_fn() { + const fn get_kind(status: &Status) -> StatusKind { + status.kind() + } + + const STATUS: Status = Status::Active; + const KIND: StatusKind = get_kind(&STATUS); + assert_eq!(KIND, StatusKind::Active); + } + + #[test] + fn should_work_in_const_match() { + const fn is_active(status: &Status) -> bool { + matches!(status.kind(), StatusKind::Active) + } + + const RESULT: bool = is_active(&Status::Active); + assert!(RESULT); + } + + /// Test with generic enum + #[derive(Kinded)] + enum Maybe { + Just(T), + Nothing, + } + + const NOTHING_KIND: MaybeKind = Maybe::::Nothing.kind(); + + #[test] + fn should_work_with_generic_enum_in_const() { + assert_eq!(NOTHING_KIND, MaybeKind::Nothing); + } + + /// Test with custom kind name + #[derive(Kinded)] + #[kinded(kind = SimpleStatus)] + enum ComplexStatus { + Ok, + Error(i32), + } + + const OK_KIND: SimpleStatus = ComplexStatus::Ok.kind(); + + #[test] + fn should_work_with_custom_kind_name_in_const() { + assert_eq!(OK_KIND, SimpleStatus::Ok); + } +} + mod rename { extern crate alloc; use alloc::string::ToString;