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: "len",
310        signature: "len(value) -> number",
311        description: "Return the length of an array, string, or collection.",
312        category: "Utility",
313        parameters: &[BuiltinParam {
314            name: "value",
315            param_type: "any",
316            optional: false,
317            description: "Collection or string",
318        }],
319        return_type: "number",
320        example: Some("len([1, 2, 3]) // 3"),
321    },
322    BuiltinMetadata {
323        name: "range",
324        signature: "range(start, end, step?) -> Vec<number>",
325        description: "Generate an array of numbers from start to end.",
326        category: "Utility",
327        parameters: &[
328            BuiltinParam {
329                name: "start",
330                param_type: "number",
331                optional: false,
332                description: "Start value",
333            },
334            BuiltinParam {
335                name: "end",
336                param_type: "number",
337                optional: false,
338                description: "End value (exclusive)",
339            },
340            BuiltinParam {
341                name: "step",
342                param_type: "number",
343                optional: true,
344                description: "Step size (default 1)",
345            },
346        ],
347        return_type: "Vec<number>",
348        example: Some("range(0, 5) // [0, 1, 2, 3, 4]"),
349    },
350    // throw removed: Shape uses Result types, not exceptions
351    // Column / Statistics
352    BuiltinMetadata {
353        name: "avg",
354        signature: "avg(collection, value: number) -> number",
355        description: "Compute the average of values in a collection.",
356        category: "Statistics",
357        parameters: &[
358            BuiltinParam {
359                name: "collection",
360                param_type: "any",
361                optional: false,
362                description: "Input collection",
363            },
364            BuiltinParam {
365                name: "value",
366                param_type: "number",
367                optional: false,
368                description: "Value to average",
369            },
370        ],
371        return_type: "number",
372        example: Some("avg(items, row => row.price)"),
373    },
374    BuiltinMetadata {
375        name: "sum",
376        signature: "sum(table: Table<any>) -> number",
377        description: "Compute the sum 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("sum(volumes)"),
387    },
388    BuiltinMetadata {
389        name: "mean",
390        signature: "mean(table: Table<any>) -> number",
391        description: "Compute the mean of all values in a series.",
392        category: "Statistics",
393        parameters: &[BuiltinParam {
394            name: "series",
395            param_type: "Table<any>",
396            optional: false,
397            description: "Input series",
398        }],
399        return_type: "number",
400        example: Some("mean(prices)"),
401    },
402    BuiltinMetadata {
403        name: "stddev",
404        signature: "stddev(values) -> number",
405        description: "Compute the standard deviation.",
406        category: "Statistics",
407        parameters: &[BuiltinParam {
408            name: "values",
409            param_type: "any",
410            optional: false,
411            description: "Input values",
412        }],
413        return_type: "number",
414        example: Some("stddev(returns)"),
415    },
416    BuiltinMetadata {
417        name: "count",
418        signature: "count(array: Vec) -> number",
419        description: "Count the number of elements in an array.",
420        category: "Vec",
421        parameters: &[BuiltinParam {
422            name: "array",
423            param_type: "Vec",
424            optional: false,
425            description: "Input array",
426        }],
427        return_type: "number",
428        example: Some("count(items)"),
429    },
430    BuiltinMetadata {
431        name: "highest",
432        signature: "highest(collection, count: number) -> number",
433        description: "Return the highest value from a collection.",
434        category: "Vec",
435        parameters: &[
436            BuiltinParam {
437                name: "collection",
438                param_type: "any",
439                optional: false,
440                description: "Input collection",
441            },
442            BuiltinParam {
443                name: "count",
444                param_type: "number",
445                optional: false,
446                description: "Number of values",
447            },
448        ],
449        return_type: "number",
450        example: Some("highest(prices, 10)"),
451    },
452    BuiltinMetadata {
453        name: "lowest",
454        signature: "lowest(collection, count: number) -> number",
455        description: "Return the lowest value from a collection.",
456        category: "Vec",
457        parameters: &[
458            BuiltinParam {
459                name: "collection",
460                param_type: "any",
461                optional: false,
462                description: "Input collection",
463            },
464            BuiltinParam {
465                name: "count",
466                param_type: "number",
467                optional: false,
468                description: "Number of values",
469            },
470        ],
471        return_type: "number",
472        example: Some("lowest(prices, 10)"),
473    },
474    // Formatting
475    BuiltinMetadata {
476        name: "format",
477        signature: "format(value, template) -> string",
478        description: "Format a value using a template string.",
479        category: "Utility",
480        parameters: &[
481            BuiltinParam {
482                name: "value",
483                param_type: "any",
484                optional: false,
485                description: "Value to format",
486            },
487            BuiltinParam {
488                name: "template",
489                param_type: "any",
490                optional: false,
491                description: "Format template",
492            },
493        ],
494        return_type: "string",
495        example: Some("format(0.15, \"percent\") // \"15%\""),
496    },
497    BuiltinMetadata {
498        name: "format_percent",
499        signature: "format_percent(value) -> string",
500        description: "Format a number as a percentage string.",
501        category: "Utility",
502        parameters: &[BuiltinParam {
503            name: "value",
504            param_type: "number",
505            optional: false,
506            description: "Decimal value",
507        }],
508        return_type: "string",
509        example: Some("format_percent(0.15) // \"15%\""),
510    },
511    BuiltinMetadata {
512        name: "format_number",
513        signature: "format_number(value, decimals?) -> string",
514        description: "Format a number with optional decimal places.",
515        category: "Utility",
516        parameters: &[
517            BuiltinParam {
518                name: "value",
519                param_type: "number",
520                optional: false,
521                description: "Number to format",
522            },
523            BuiltinParam {
524                name: "decimals",
525                param_type: "number",
526                optional: true,
527                description: "Decimal places",
528            },
529        ],
530        return_type: "string",
531        example: Some("format_number(1234.5, 2) // \"1234.50\""),
532    },
533    // Column operations
534    BuiltinMetadata {
535        name: "shift",
536        signature: "shift(table: Table<any>, periods: number) -> Table<any>",
537        description: "Shift series values by a number of periods.",
538        category: "Column",
539        parameters: &[
540            BuiltinParam {
541                name: "series",
542                param_type: "Table<any>",
543                optional: false,
544                description: "Input series",
545            },
546            BuiltinParam {
547                name: "periods",
548                param_type: "number",
549                optional: false,
550                description: "Number of periods to shift",
551            },
552        ],
553        return_type: "Table<any>",
554        example: Some("shift(prices, 1) // previous day's prices"),
555    },
556    BuiltinMetadata {
557        name: "resample",
558        signature: "resample(table: Table<any>, timeframe: string, method: string) -> Table<any>",
559        description: "Resample a series to a different timeframe.",
560        category: "Column",
561        parameters: &[
562            BuiltinParam {
563                name: "series",
564                param_type: "Table<any>",
565                optional: false,
566                description: "Input series",
567            },
568            BuiltinParam {
569                name: "timeframe",
570                param_type: "string",
571                optional: false,
572                description: "Target timeframe",
573            },
574            BuiltinParam {
575                name: "method",
576                param_type: "string",
577                optional: false,
578                description: "Aggregation method",
579            },
580        ],
581        return_type: "Table<any>",
582        example: Some("resample(prices, \"1h\", \"last\")"),
583    },
584    BuiltinMetadata {
585        name: "map",
586        signature: "map(table: Table<any>, fn: Function) -> Table<any>",
587        description: "Apply a function to each element of a series.",
588        category: "Column",
589        parameters: &[
590            BuiltinParam {
591                name: "series",
592                param_type: "Table<any>",
593                optional: false,
594                description: "Input series",
595            },
596            BuiltinParam {
597                name: "fn",
598                param_type: "Function",
599                optional: false,
600                description: "Transform function",
601            },
602        ],
603        return_type: "Table<any>",
604        example: Some("map(prices, (p) => p * 1.1)"),
605    },
606    BuiltinMetadata {
607        name: "filter",
608        signature: "filter(table: Table<any>, predicate: Function) -> Table<any>",
609        description: "Filter series elements by a predicate function.",
610        category: "Column",
611        parameters: &[
612            BuiltinParam {
613                name: "series",
614                param_type: "Table<any>",
615                optional: false,
616                description: "Input series",
617            },
618            BuiltinParam {
619                name: "predicate",
620                param_type: "Function",
621                optional: false,
622                description: "Filter predicate",
623            },
624        ],
625        return_type: "Table<any>",
626        example: Some("filter(prices, (p) => p > 100)"),
627    },
628    // Result constructors
629    BuiltinMetadata {
630        name: "Ok",
631        signature: "Ok(value) -> Result<T>",
632        description: "Wrap a value in a successful Result.",
633        category: "Utility",
634        parameters: &[BuiltinParam {
635            name: "value",
636            param_type: "any",
637            optional: false,
638            description: "Success value",
639        }],
640        return_type: "Result<T>",
641        example: Some("Ok(42)"),
642    },
643    BuiltinMetadata {
644        name: "Err",
645        signature: "Err(error) -> Result<T>",
646        description: "Create an error Result.",
647        category: "Utility",
648        parameters: &[BuiltinParam {
649            name: "error",
650            param_type: "any",
651            optional: false,
652            description: "Error value",
653        }],
654        return_type: "Result<T>",
655        example: Some("Err(\"not found\")"),
656    },
657    // Resumability
658    BuiltinMetadata {
659        name: "snapshot",
660        signature: "snapshot() -> Snapshot",
661        description: "Create a snapshot suspension point. Returns Snapshot::Hash(id) after saving, or Snapshot::Resumed when restoring from a snapshot.",
662        category: "Resumability",
663        parameters: &[],
664        return_type: "Snapshot",
665        example: Some(
666            "let result = snapshot()\nmatch result {\n    Snapshot::Hash(id) => print(\"Saved: \" + id),\n    Snapshot::Resumed => print(\"Restored!\"),\n}",
667        ),
668    },
669    BuiltinMetadata {
670        name: "exit",
671        signature: "exit(code?: number) -> ()",
672        description: "Terminate the process with an optional exit code.",
673        category: "Utility",
674        parameters: &[BuiltinParam {
675            name: "code",
676            param_type: "number",
677            optional: true,
678            description: "Exit code (default 0)",
679        }],
680        return_type: "()",
681        example: Some("exit(0)"),
682    },
683    // Comptime-only builtins
684    BuiltinMetadata {
685        name: "implements",
686        signature: "implements(type_name: string, trait_name: string) -> bool",
687        description: "Returns true if the given type implements the specified trait. Only valid inside comptime blocks.",
688        category: "Comptime",
689        parameters: &[
690            BuiltinParam {
691                name: "type_name",
692                param_type: "string",
693                optional: false,
694                description: "Type name to check",
695            },
696            BuiltinParam {
697                name: "trait_name",
698                param_type: "string",
699                optional: false,
700                description: "Trait name to check",
701            },
702        ],
703        return_type: "bool",
704        example: Some("comptime { implements(\"Point\", \"Display\") }"),
705    },
706    BuiltinMetadata {
707        name: "warning",
708        signature: "warning(msg: string) -> ()",
709        description: "Emit a compile-time warning. Only valid inside comptime blocks.",
710        category: "Comptime",
711        parameters: &[BuiltinParam {
712            name: "msg",
713            param_type: "string",
714            optional: false,
715            description: "Warning message",
716        }],
717        return_type: "()",
718        example: Some("comptime { warning(\"generated fallback path\") }"),
719    },
720    BuiltinMetadata {
721        name: "error",
722        signature: "error(msg: string) -> never",
723        description: "Emit a compile-time error and abort compilation. Only valid inside comptime blocks.",
724        category: "Comptime",
725        parameters: &[BuiltinParam {
726            name: "msg",
727            param_type: "string",
728            optional: false,
729            description: "Error message",
730        }],
731        return_type: "never",
732        example: Some("comptime { error(\"invariant violated\") }"),
733    },
734    BuiltinMetadata {
735        name: "build_config",
736        signature: "build_config() -> Object",
737        description: "Returns build-time configuration (debug, version, target_os, target_arch). Only valid inside comptime blocks.",
738        category: "Comptime",
739        parameters: &[],
740        return_type: "Object",
741        example: Some("let v = comptime { build_config().version }"),
742    },
743];
744
745/// Collect all builtin metadata for LSP introspection.
746///
747/// Returns references to static metadata for all core builtin functions.
748pub fn collect_builtin_metadata() -> Vec<&'static BuiltinMetadata> {
749    CORE_BUILTINS.iter().collect()
750}
751
752/// Convert all collected builtin metadata to FunctionInfo for LSP
753pub fn builtin_functions_from_macros() -> Vec<FunctionInfo> {
754    collect_builtin_metadata()
755        .into_iter()
756        .map(|m| m.into())
757        .collect()
758}
759
760/// Returns true if the function is a comptime-only builtin.
761///
762/// This is the single source of truth used by compiler and LSP.
763pub fn is_comptime_builtin_function(name: &str) -> bool {
764    CORE_BUILTINS
765        .iter()
766        .any(|m| m.name == name && m.category == "Comptime")
767}
768
769/// Static type metadata for Row
770/// Row is a generic container with dynamic fields discovered at runtime.
771/// Domain-specific fields (e.g., OHLCV for finance) are defined in stdlib types.
772pub static TYPE_METADATA_ROW: TypeMetadata = TypeMetadata {
773    name: "Row",
774    description: "Generic data row with dynamic fields",
775    properties: &[],
776};
777
778/// Collect core type metadata from derive macro generated constants.
779///
780/// This function returns ONLY generic/core type metadata that is
781/// industry-agnostic. Domain-specific types (like finance types)
782/// should be registered separately via `collect_domain_type_metadata`.
783pub fn collect_core_type_metadata() -> Vec<&'static TypeMetadata> {
784    vec![&TYPE_METADATA_ROW]
785}
786
787/// Collect domain-specific type metadata (finance/trading types).
788///
789/// These types are related to trading/backtesting infrastructure.
790/// In a fully modular system, these would be registered by a domain-specific
791/// stdlib module.
792pub fn collect_domain_type_metadata() -> Vec<&'static TypeMetadata> {
793    vec![]
794}
795
796/// Collect all type metadata from derive macro generated constants.
797///
798/// This function returns all type metadata that was generated
799/// by the `#[derive(ShapeType)]` derive macro. It's used by the LSP to
800/// provide property completions on typed expressions.
801///
802/// Note: This includes both core types and domain-specific types.
803/// For domain separation, use `collect_core_type_metadata` and
804/// `collect_domain_type_metadata` separately.
805pub fn collect_type_metadata() -> Vec<&'static TypeMetadata> {
806    let mut types = collect_core_type_metadata();
807    types.extend(collect_domain_type_metadata());
808    types
809}
810
811#[cfg(test)]
812mod tests {
813    use super::*;
814
815    #[test]
816    fn test_builtin_metadata_to_function_info() {
817        static TEST_PARAMS: &[BuiltinParam] = &[BuiltinParam {
818            name: "value",
819            param_type: "Number",
820            optional: false,
821            description: "Input value",
822        }];
823
824        let meta = BuiltinMetadata {
825            name: "test_fn",
826            signature: "test_fn(value: Number) -> Number",
827            description: "A test function",
828            category: "Math",
829            parameters: TEST_PARAMS,
830            return_type: "Number",
831            example: Some("test_fn(42)"),
832        };
833
834        let info: FunctionInfo = (&meta).into();
835
836        assert_eq!(info.name, "test_fn");
837        assert_eq!(info.signature, "test_fn(value: Number) -> Number");
838        assert_eq!(info.description, "A test function");
839        assert_eq!(info.category, FunctionCategory::Math);
840        assert_eq!(info.parameters.len(), 1);
841        assert_eq!(info.parameters[0].name, "value");
842        assert_eq!(info.return_type, "Number");
843        assert_eq!(info.example, Some("test_fn(42)".to_string()));
844        assert!(info.implemented);
845    }
846}