Skip to main content

shape_runtime/
builtin_metadata.rs

1//! Builtin function metadata for LSP introspection
2//!
3//! This module defines the metadata structures used by the `#[shape_builtin]`
4//! proc-macro and `#[derive(ShapeType)]` to generate compile-time metadata.
5
6use crate::metadata::{FunctionCategory, FunctionInfo, ParameterInfo, PropertyInfo};
7
8/// Metadata for a builtin function, generated by proc-macro at compile time.
9///
10/// Uses `&'static str` for zero-allocation at runtime.
11#[derive(Clone, Debug)]
12pub struct BuiltinMetadata {
13    /// Function name as exposed to Shape (e.g., "sma", "abs")
14    pub name: &'static str,
15    /// Full signature (e.g., "sma(column: Column, period: Number) -> Column")
16    pub signature: &'static str,
17    /// Description extracted from doc comments
18    pub description: &'static str,
19    /// Category (e.g., "Indicator", "Data", "Trading")
20    pub category: &'static str,
21    /// Parameter information
22    pub parameters: &'static [BuiltinParam],
23    /// Return type
24    pub return_type: &'static str,
25    /// Optional example code
26    pub example: Option<&'static str>,
27}
28
29/// Parameter metadata for builtin functions
30#[derive(Clone, Debug)]
31pub struct BuiltinParam {
32    /// Parameter name
33    pub name: &'static str,
34    /// Parameter type
35    pub param_type: &'static str,
36    /// Whether the parameter is optional
37    pub optional: bool,
38    /// Description of the parameter
39    pub description: &'static str,
40}
41
42/// Metadata for a Shape type (struct), generated by derive macro at compile time.
43#[derive(Clone, Debug)]
44pub struct TypeMetadata {
45    /// Type name as exposed to Shape (e.g., "Row", "Trade")
46    pub name: &'static str,
47    /// Description extracted from struct doc comments
48    pub description: &'static str,
49    /// Property information for this type
50    pub properties: &'static [PropertyMetadata],
51}
52
53/// Property metadata for Shape types
54#[derive(Clone, Debug)]
55pub struct PropertyMetadata {
56    /// Property name
57    pub name: &'static str,
58    /// Property type in Shape
59    pub prop_type: &'static str,
60    /// Description of the property
61    pub description: &'static str,
62}
63
64impl TypeMetadata {
65    /// Convert to runtime PropertyInfo for LSP
66    pub fn to_property_infos(&self) -> Vec<PropertyInfo> {
67        self.properties
68            .iter()
69            .map(|p| PropertyInfo {
70                name: p.name.to_string(),
71                property_type: p.prop_type.to_string(),
72                description: p.description.to_string(),
73            })
74            .collect()
75    }
76}
77
78impl From<&BuiltinMetadata> for FunctionInfo {
79    fn from(meta: &BuiltinMetadata) -> Self {
80        let category = match meta.category {
81            "Simulation" => FunctionCategory::Simulation,
82            "Math" => FunctionCategory::Math,
83            "Vec" => FunctionCategory::Array,
84            "Column" => FunctionCategory::Column,
85            "Statistics" => FunctionCategory::Statistics,
86            "Data" => FunctionCategory::Data,
87            _ => FunctionCategory::Utility,
88        };
89
90        FunctionInfo {
91            name: meta.name.to_string(),
92            signature: meta.signature.to_string(),
93            description: meta.description.to_string(),
94            category,
95            parameters: meta
96                .parameters
97                .iter()
98                .map(|p| ParameterInfo {
99                    name: p.name.to_string(),
100                    param_type: p.param_type.to_string(),
101                    optional: p.optional,
102                    description: p.description.to_string(),
103                    constraints: None, // TODO: Extract from annotations
104                })
105                .collect(),
106            return_type: meta.return_type.to_string(),
107            example: meta.example.map(|s| s.to_string()),
108            implemented: true,
109            comptime_only: meta.category == "Comptime",
110        }
111    }
112}
113
114/// Static builtin metadata for core functions.
115///
116/// These correspond to the functions registered in `semantic/builtins.rs`
117/// for type-checking, mirrored here for LSP introspection (completions,
118/// hover, signature help).
119static CORE_BUILTINS: &[BuiltinMetadata] = &[
120    // Math
121    BuiltinMetadata {
122        name: "abs",
123        signature: "abs(value: number) -> number",
124        description: "Return the absolute value of a number.",
125        category: "Math",
126        parameters: &[BuiltinParam {
127            name: "value",
128            param_type: "number",
129            optional: false,
130            description: "Input value",
131        }],
132        return_type: "number",
133        example: Some("abs(-5) // 5"),
134    },
135    BuiltinMetadata {
136        name: "sqrt",
137        signature: "sqrt(value: number) -> number",
138        description: "Return the square root of a number.",
139        category: "Math",
140        parameters: &[BuiltinParam {
141            name: "value",
142            param_type: "number",
143            optional: false,
144            description: "Input value",
145        }],
146        return_type: "number",
147        example: Some("sqrt(16) // 4"),
148    },
149    BuiltinMetadata {
150        name: "pow",
151        signature: "pow(base: number, exponent: number) -> number",
152        description: "Raise base to the power of exponent.",
153        category: "Math",
154        parameters: &[
155            BuiltinParam {
156                name: "base",
157                param_type: "number",
158                optional: false,
159                description: "Base value",
160            },
161            BuiltinParam {
162                name: "exponent",
163                param_type: "number",
164                optional: false,
165                description: "Exponent",
166            },
167        ],
168        return_type: "number",
169        example: Some("pow(2, 3) // 8"),
170    },
171    BuiltinMetadata {
172        name: "log",
173        signature: "log(value: number) -> number",
174        description: "Return the natural logarithm of a number.",
175        category: "Math",
176        parameters: &[BuiltinParam {
177            name: "value",
178            param_type: "number",
179            optional: false,
180            description: "Input value",
181        }],
182        return_type: "number",
183        example: Some("log(2.718) // ~1.0"),
184    },
185    BuiltinMetadata {
186        name: "exp",
187        signature: "exp(value: number) -> number",
188        description: "Return e raised to the power of value.",
189        category: "Math",
190        parameters: &[BuiltinParam {
191            name: "value",
192            param_type: "number",
193            optional: false,
194            description: "Exponent",
195        }],
196        return_type: "number",
197        example: Some("exp(1) // ~2.718"),
198    },
199    BuiltinMetadata {
200        name: "floor",
201        signature: "floor(value: number) -> number",
202        description: "Round down to the nearest integer.",
203        category: "Math",
204        parameters: &[BuiltinParam {
205            name: "value",
206            param_type: "number",
207            optional: false,
208            description: "Input value",
209        }],
210        return_type: "number",
211        example: Some("floor(3.7) // 3"),
212    },
213    BuiltinMetadata {
214        name: "ceil",
215        signature: "ceil(value: number) -> number",
216        description: "Round up to the nearest integer.",
217        category: "Math",
218        parameters: &[BuiltinParam {
219            name: "value",
220            param_type: "number",
221            optional: false,
222            description: "Input value",
223        }],
224        return_type: "number",
225        example: Some("ceil(3.2) // 4"),
226    },
227    BuiltinMetadata {
228        name: "round",
229        signature: "round(value: number, decimals?: number) -> number",
230        description: "Round a number to the specified number of decimal places.",
231        category: "Math",
232        parameters: &[
233            BuiltinParam {
234                name: "value",
235                param_type: "number",
236                optional: false,
237                description: "Input value",
238            },
239            BuiltinParam {
240                name: "decimals",
241                param_type: "number",
242                optional: true,
243                description: "Decimal places (default 0)",
244            },
245        ],
246        return_type: "number",
247        example: Some("round(3.456, 2) // 3.46"),
248    },
249    BuiltinMetadata {
250        name: "max",
251        signature: "max(a: number, b: number) -> number",
252        description: "Return the larger of two numbers.",
253        category: "Math",
254        parameters: &[
255            BuiltinParam {
256                name: "a",
257                param_type: "number",
258                optional: false,
259                description: "First value",
260            },
261            BuiltinParam {
262                name: "b",
263                param_type: "number",
264                optional: false,
265                description: "Second value",
266            },
267        ],
268        return_type: "number",
269        example: Some("max(3, 7) // 7"),
270    },
271    BuiltinMetadata {
272        name: "min",
273        signature: "min(a: number, b: number) -> number",
274        description: "Return the smaller of two numbers.",
275        category: "Math",
276        parameters: &[
277            BuiltinParam {
278                name: "a",
279                param_type: "number",
280                optional: false,
281                description: "First value",
282            },
283            BuiltinParam {
284                name: "b",
285                param_type: "number",
286                optional: false,
287                description: "Second value",
288            },
289        ],
290        return_type: "number",
291        example: Some("min(3, 7) // 3"),
292    },
293    // Utility
294    BuiltinMetadata {
295        name: "print",
296        signature: "print(...values) -> ()",
297        description: "Print values to output. Supports string interpolation and meta format functions.",
298        category: "Utility",
299        parameters: &[BuiltinParam {
300            name: "values",
301            param_type: "any",
302            optional: false,
303            description: "Values to print",
304        }],
305        return_type: "()",
306        example: Some("print(\"hello\", x)"),
307    },
308    BuiltinMetadata {
309        name: "range",
310        signature: "range(start, end, step?) -> Vec<number>",
311        description: "Generate an array of numbers from start to end.",
312        category: "Utility",
313        parameters: &[
314            BuiltinParam {
315                name: "start",
316                param_type: "number",
317                optional: false,
318                description: "Start value",
319            },
320            BuiltinParam {
321                name: "end",
322                param_type: "number",
323                optional: false,
324                description: "End value (exclusive)",
325            },
326            BuiltinParam {
327                name: "step",
328                param_type: "number",
329                optional: true,
330                description: "Step size (default 1)",
331            },
332        ],
333        return_type: "Vec<number>",
334        example: Some("range(0, 5) // [0, 1, 2, 3, 4]"),
335    },
336    // throw removed: Shape uses Result types, not exceptions
337    // Column / Statistics
338    BuiltinMetadata {
339        name: "avg",
340        signature: "avg(collection, value: number) -> number",
341        description: "Compute the average of values in a collection.",
342        category: "Statistics",
343        parameters: &[
344            BuiltinParam {
345                name: "collection",
346                param_type: "any",
347                optional: false,
348                description: "Input collection",
349            },
350            BuiltinParam {
351                name: "value",
352                param_type: "number",
353                optional: false,
354                description: "Value to average",
355            },
356        ],
357        return_type: "number",
358        example: Some("avg(items, row => row.price)"),
359    },
360    BuiltinMetadata {
361        name: "sum",
362        signature: "sum(table: Table<any>) -> number",
363        description: "Compute the sum of all values in a series.",
364        category: "Statistics",
365        parameters: &[BuiltinParam {
366            name: "series",
367            param_type: "Table<any>",
368            optional: false,
369            description: "Input series",
370        }],
371        return_type: "number",
372        example: Some("sum(volumes)"),
373    },
374    BuiltinMetadata {
375        name: "mean",
376        signature: "mean(table: Table<any>) -> number",
377        description: "Compute the mean of all values in a series.",
378        category: "Statistics",
379        parameters: &[BuiltinParam {
380            name: "series",
381            param_type: "Table<any>",
382            optional: false,
383            description: "Input series",
384        }],
385        return_type: "number",
386        example: Some("mean(prices)"),
387    },
388    BuiltinMetadata {
389        name: "stddev",
390        signature: "stddev(values) -> number",
391        description: "Compute the standard deviation.",
392        category: "Statistics",
393        parameters: &[BuiltinParam {
394            name: "values",
395            param_type: "any",
396            optional: false,
397            description: "Input values",
398        }],
399        return_type: "number",
400        example: Some("stddev(returns)"),
401    },
402    BuiltinMetadata {
403        name: "count",
404        signature: "count(array: Vec) -> number",
405        description: "Count the number of elements in an array.",
406        category: "Vec",
407        parameters: &[BuiltinParam {
408            name: "array",
409            param_type: "Vec",
410            optional: false,
411            description: "Input array",
412        }],
413        return_type: "number",
414        example: Some("count(items)"),
415    },
416    BuiltinMetadata {
417        name: "highest",
418        signature: "highest(collection, count: number) -> number",
419        description: "Return the highest value from a collection.",
420        category: "Vec",
421        parameters: &[
422            BuiltinParam {
423                name: "collection",
424                param_type: "any",
425                optional: false,
426                description: "Input collection",
427            },
428            BuiltinParam {
429                name: "count",
430                param_type: "number",
431                optional: false,
432                description: "Number of values",
433            },
434        ],
435        return_type: "number",
436        example: Some("highest(prices, 10)"),
437    },
438    BuiltinMetadata {
439        name: "lowest",
440        signature: "lowest(collection, count: number) -> number",
441        description: "Return the lowest value from a collection.",
442        category: "Vec",
443        parameters: &[
444            BuiltinParam {
445                name: "collection",
446                param_type: "any",
447                optional: false,
448                description: "Input collection",
449            },
450            BuiltinParam {
451                name: "count",
452                param_type: "number",
453                optional: false,
454                description: "Number of values",
455            },
456        ],
457        return_type: "number",
458        example: Some("lowest(prices, 10)"),
459    },
460    // Formatting
461    BuiltinMetadata {
462        name: "format",
463        signature: "format(value, template) -> string",
464        description: "Format a value using a template string.",
465        category: "Utility",
466        parameters: &[
467            BuiltinParam {
468                name: "value",
469                param_type: "any",
470                optional: false,
471                description: "Value to format",
472            },
473            BuiltinParam {
474                name: "template",
475                param_type: "any",
476                optional: false,
477                description: "Format template",
478            },
479        ],
480        return_type: "string",
481        example: Some("format(0.15, \"percent\") // \"15%\""),
482    },
483    // Criterion F / KC #2 resolve-by-deletion (2026-05-22): the
484    // `format_percent` and `format_number` BuiltinMetadata entries were
485    // removed alongside their `builtin fn` declarations in
486    // `stdlib-src/core/intrinsics.shape`. The single `format(value,
487    // template)` global and `DateTime.format(...)` method survive. No
488    // backwards-compat aliases.
489    // Column operations
490    BuiltinMetadata {
491        name: "shift",
492        signature: "shift(table: Table<any>, periods: number) -> Table<any>",
493        description: "Shift series values by a number of periods.",
494        category: "Column",
495        parameters: &[
496            BuiltinParam {
497                name: "series",
498                param_type: "Table<any>",
499                optional: false,
500                description: "Input series",
501            },
502            BuiltinParam {
503                name: "periods",
504                param_type: "number",
505                optional: false,
506                description: "Number of periods to shift",
507            },
508        ],
509        return_type: "Table<any>",
510        example: Some("shift(prices, 1) // previous day's prices"),
511    },
512    BuiltinMetadata {
513        name: "resample",
514        signature: "resample(table: Table<any>, timeframe: string, method: string) -> Table<any>",
515        description: "Resample a series to a different timeframe.",
516        category: "Column",
517        parameters: &[
518            BuiltinParam {
519                name: "series",
520                param_type: "Table<any>",
521                optional: false,
522                description: "Input series",
523            },
524            BuiltinParam {
525                name: "timeframe",
526                param_type: "string",
527                optional: false,
528                description: "Target timeframe",
529            },
530            BuiltinParam {
531                name: "method",
532                param_type: "string",
533                optional: false,
534                description: "Aggregation method",
535            },
536        ],
537        return_type: "Table<any>",
538        example: Some("resample(prices, \"1h\", \"last\")"),
539    },
540    BuiltinMetadata {
541        name: "map",
542        signature: "map(table: Table<any>, fn: Function) -> Table<any>",
543        description: "Apply a function to each element of a series.",
544        category: "Column",
545        parameters: &[
546            BuiltinParam {
547                name: "series",
548                param_type: "Table<any>",
549                optional: false,
550                description: "Input series",
551            },
552            BuiltinParam {
553                name: "fn",
554                param_type: "Function",
555                optional: false,
556                description: "Transform function",
557            },
558        ],
559        return_type: "Table<any>",
560        example: Some("map(prices, (p) => p * 1.1)"),
561    },
562    BuiltinMetadata {
563        name: "filter",
564        signature: "filter(table: Table<any>, predicate: Function) -> Table<any>",
565        description: "Filter series elements by a predicate function.",
566        category: "Column",
567        parameters: &[
568            BuiltinParam {
569                name: "series",
570                param_type: "Table<any>",
571                optional: false,
572                description: "Input series",
573            },
574            BuiltinParam {
575                name: "predicate",
576                param_type: "Function",
577                optional: false,
578                description: "Filter predicate",
579            },
580        ],
581        return_type: "Table<any>",
582        example: Some("filter(prices, (p) => p > 100)"),
583    },
584    // Result constructors
585    BuiltinMetadata {
586        name: "Ok",
587        signature: "Ok(value) -> Result<T>",
588        description: "Wrap a value in a successful Result.",
589        category: "Utility",
590        parameters: &[BuiltinParam {
591            name: "value",
592            param_type: "any",
593            optional: false,
594            description: "Success value",
595        }],
596        return_type: "Result<T>",
597        example: Some("Ok(42)"),
598    },
599    BuiltinMetadata {
600        name: "Err",
601        signature: "Err(error) -> Result<T>",
602        description: "Create an error Result.",
603        category: "Utility",
604        parameters: &[BuiltinParam {
605            name: "error",
606            param_type: "any",
607            optional: false,
608            description: "Error value",
609        }],
610        return_type: "Result<T>",
611        example: Some("Err(\"not found\")"),
612    },
613    // Resumability
614    BuiltinMetadata {
615        name: "snapshot",
616        signature: "snapshot() -> Snapshot",
617        description: "Create a snapshot suspension point. Returns Snapshot::Hash(id) after saving, or Snapshot::Resumed when restoring from a snapshot.",
618        category: "Resumability",
619        parameters: &[],
620        return_type: "Snapshot",
621        example: Some(
622            "let result = snapshot()\nmatch result {\n    Snapshot::Hash(id) => print(\"Saved: \" + id),\n    Snapshot::Resumed => print(\"Restored!\"),\n}",
623        ),
624    },
625    BuiltinMetadata {
626        name: "exit",
627        signature: "exit(code?: number) -> ()",
628        description: "Terminate the process with an optional exit code.",
629        category: "Utility",
630        parameters: &[BuiltinParam {
631            name: "code",
632            param_type: "number",
633            optional: true,
634            description: "Exit code (default 0)",
635        }],
636        return_type: "()",
637        example: Some("exit(0)"),
638    },
639    // Comptime-only builtins
640    BuiltinMetadata {
641        name: "implements",
642        signature: "implements(type_name: string, trait_name: string) -> bool",
643        description: "Returns true if the given type implements the specified trait. Only valid inside comptime blocks.",
644        category: "Comptime",
645        parameters: &[
646            BuiltinParam {
647                name: "type_name",
648                param_type: "string",
649                optional: false,
650                description: "Type name to check",
651            },
652            BuiltinParam {
653                name: "trait_name",
654                param_type: "string",
655                optional: false,
656                description: "Trait name to check",
657            },
658        ],
659        return_type: "bool",
660        example: Some("comptime { implements(\"Point\", \"Display\") }"),
661    },
662    BuiltinMetadata {
663        name: "warning",
664        signature: "warning(msg: string) -> ()",
665        description: "Emit a compile-time warning. Only valid inside comptime blocks.",
666        category: "Comptime",
667        parameters: &[BuiltinParam {
668            name: "msg",
669            param_type: "string",
670            optional: false,
671            description: "Warning message",
672        }],
673        return_type: "()",
674        example: Some("comptime { warning(\"generated fallback path\") }"),
675    },
676    BuiltinMetadata {
677        name: "error",
678        signature: "error(msg: string) -> never",
679        description: "Emit a compile-time error and abort compilation. Only valid inside comptime blocks.",
680        category: "Comptime",
681        parameters: &[BuiltinParam {
682            name: "msg",
683            param_type: "string",
684            optional: false,
685            description: "Error message",
686        }],
687        return_type: "never",
688        example: Some("comptime { error(\"invariant violated\") }"),
689    },
690    BuiltinMetadata {
691        name: "build_config",
692        signature: "build_config() -> Object",
693        description: "Returns build-time configuration (debug, version, target_os, target_arch). Only valid inside comptime blocks.",
694        category: "Comptime",
695        parameters: &[],
696        return_type: "Object",
697        example: Some("let v = comptime { build_config().version }"),
698    },
699    // W7 (2026-05-17) — `type_info(T)` comptime builtin re-introduced per
700    // `docs/cluster-audits/v0.3-w7-type_info-comptime-typed-return.md` §4
701    // (recommendation Option (b) TypeInfo struct return) + §8 (user
702    // dispositions Q1-Q5). Returns the `TypeInfo` struct declared in
703    // `std::core::types`; pattern mirrors `build_config` (pre-registered
704    // schema + `typed_object_from_pairs`).
705    BuiltinMetadata {
706        name: "type_info",
707        signature: "type_info(type_name: string) -> TypeInfo",
708        description: "Returns the `TypeInfo` reflection record for the named type. Only valid inside comptime blocks. Bare type identifiers passed at the call site are rewritten to string literals by the comptime preprocessor.",
709        category: "Comptime",
710        parameters: &[BuiltinParam {
711            name: "type_name",
712            param_type: "string",
713            optional: false,
714            description: "Type name to reflect on (bare identifiers are auto-stringified at the call site)",
715        }],
716        return_type: "TypeInfo",
717        example: Some("comptime { let ti = type_info(Point); print(ti.name) }"),
718    },
719];
720
721/// Collect all builtin metadata for LSP introspection.
722///
723/// Returns references to static metadata for all core builtin functions.
724pub fn collect_builtin_metadata() -> Vec<&'static BuiltinMetadata> {
725    CORE_BUILTINS.iter().collect()
726}
727
728/// Convert all collected builtin metadata to FunctionInfo for LSP
729pub fn builtin_functions_from_macros() -> Vec<FunctionInfo> {
730    collect_builtin_metadata()
731        .into_iter()
732        .map(|m| m.into())
733        .collect()
734}
735
736/// Returns true if the function is a comptime-only builtin.
737///
738/// This is the single source of truth used by compiler and LSP.
739pub fn is_comptime_builtin_function(name: &str) -> bool {
740    CORE_BUILTINS
741        .iter()
742        .any(|m| m.name == name && m.category == "Comptime")
743}
744
745/// Static type metadata for Row
746/// Row is a generic container with dynamic fields discovered at runtime.
747/// Domain-specific fields (e.g., OHLCV for finance) are defined in stdlib types.
748pub static TYPE_METADATA_ROW: TypeMetadata = TypeMetadata {
749    name: "Row",
750    description: "Generic data row with dynamic fields",
751    properties: &[],
752};
753
754/// Collect core type metadata from derive macro generated constants.
755///
756/// This function returns ONLY generic/core type metadata that is
757/// industry-agnostic. Domain-specific types (like finance types)
758/// should be registered separately via `collect_domain_type_metadata`.
759pub fn collect_core_type_metadata() -> Vec<&'static TypeMetadata> {
760    vec![&TYPE_METADATA_ROW]
761}
762
763/// Collect domain-specific type metadata (finance/trading types).
764///
765/// These types are related to trading/backtesting infrastructure.
766/// In a fully modular system, these would be registered by a domain-specific
767/// stdlib module.
768pub fn collect_domain_type_metadata() -> Vec<&'static TypeMetadata> {
769    vec![]
770}
771
772/// Collect all type metadata from derive macro generated constants.
773///
774/// This function returns all type metadata that was generated
775/// by the `#[derive(ShapeType)]` derive macro. It's used by the LSP to
776/// provide property completions on typed expressions.
777///
778/// Note: This includes both core types and domain-specific types.
779/// For domain separation, use `collect_core_type_metadata` and
780/// `collect_domain_type_metadata` separately.
781pub fn collect_type_metadata() -> Vec<&'static TypeMetadata> {
782    let mut types = collect_core_type_metadata();
783    types.extend(collect_domain_type_metadata());
784    types
785}
786
787#[cfg(test)]
788mod tests {
789    use super::*;
790
791    #[test]
792    fn test_builtin_metadata_to_function_info() {
793        static TEST_PARAMS: &[BuiltinParam] = &[BuiltinParam {
794            name: "value",
795            param_type: "Number",
796            optional: false,
797            description: "Input value",
798        }];
799
800        let meta = BuiltinMetadata {
801            name: "test_fn",
802            signature: "test_fn(value: Number) -> Number",
803            description: "A test function",
804            category: "Math",
805            parameters: TEST_PARAMS,
806            return_type: "Number",
807            example: Some("test_fn(42)"),
808        };
809
810        let info: FunctionInfo = (&meta).into();
811
812        assert_eq!(info.name, "test_fn");
813        assert_eq!(info.signature, "test_fn(value: Number) -> Number");
814        assert_eq!(info.description, "A test function");
815        assert_eq!(info.category, FunctionCategory::Math);
816        assert_eq!(info.parameters.len(), 1);
817        assert_eq!(info.parameters[0].name, "value");
818        assert_eq!(info.return_type, "Number");
819        assert_eq!(info.example, Some("test_fn(42)".to_string()));
820        assert!(info.implemented);
821    }
822}