//! cimery-macros — Feature DSL proc-macro 인프라 (Sprint 38). //! //! # 현재 범위 //! - `#[derive(ParamSummary)]` — 구조체의 필드 이름·개수·타입을 컴파일 타임에 //! 요약 제공. UI 자동생성·파라미터 인스펙터의 기반. //! //! # ADR-002 D 로드맵 (미래) //! - `#[param(unit="mm", range=1000..=3000, default=1800)]` 속성 attribute //! - `#[derive(Feature)]` — validation / builder / IFC 매핑 자동 생성 //! - 선언적 `feature!` 매크로 — DSL 설탕 //! //! # 현 시점 사용 예 //! ```ignore //! use cimery_macros::ParamSummary; //! //! #[derive(ParamSummary)] //! pub struct GirderParams { //! pub total_height: f64, //! pub top_flange_width: f64, //! } //! //! fn main() { //! assert_eq!(GirderParams::PARAM_COUNT, 2); //! assert_eq!(GirderParams::PARAM_NAMES, &["total_height", "top_flange_width"]); //! } //! ``` use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Fields}; /// `#[derive(ParamSummary)]` — named field 구조체의 필드 이름·개수 상수 생성. /// /// - `Self::PARAM_COUNT: usize` /// - `Self::PARAM_NAMES: &'static [&'static str]` /// /// # 제약 /// - named field 구조체만 지원 (tuple/unit/enum 은 컴파일 에러). /// - 모든 필드가 노출됨 (private 필드 포함). 필터링은 Phase 2. #[proc_macro_derive(ParamSummary)] pub fn derive_param_summary(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let fields = match &input.data { Data::Struct(s) => match &s.fields { Fields::Named(named) => &named.named, _ => { return syn::Error::new_spanned( name, "ParamSummary only supports structs with named fields", ) .to_compile_error() .into(); } }, _ => { return syn::Error::new_spanned( name, "ParamSummary only supports structs, not enums or unions", ) .to_compile_error() .into(); } }; let names: Vec = fields .iter() .filter_map(|f| f.ident.as_ref().map(|i| i.to_string())) .collect(); let count = names.len(); let expanded = quote! { impl #name { /// `#[derive(ParamSummary)]` 로 생성된 필드 개수. pub const PARAM_COUNT: usize = #count; /// `#[derive(ParamSummary)]` 로 생성된 필드 이름 목록 (선언 순서). pub const PARAM_NAMES: &'static [&'static str] = &[ #( #names ),* ]; } }; expanded.into() }