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    BuiltinMetadata {
484        name: "format_percent",
485        signature: "format_percent(value) -> string",
486        description: "Format a number as a percentage string.",
487        category: "Utility",
488        parameters: &[BuiltinParam {
489            name: "value",
490            param_type: "number",
491            optional: false,
492            description: "Decimal value",
493        }],
494        return_type: "string",
495        example: Some("format_percent(0.15) // \"15%\""),
496    },
497    BuiltinMetadata {
498        name: "format_number",
499        signature: "format_number(value, decimals?) -> string",
500        description: "Format a number with optional decimal places.",
501        category: "Utility",
502        parameters: &[
503            BuiltinParam {
504                name: "value",
505                param_type: "number",
506                optional: false,
507                description: "Number to format",
508            },
509            BuiltinParam {
510                name: "decimals",
511                param_type: "number",
512                optional: true,
513                description: "Decimal places",
514            },
515        ],
516        return_type: "string",
517        example: Some("format_number(1234.5, 2) // \"1234.50\""),
518    },
519    // Column operations
520    BuiltinMetadata {
521        name: "shift",
522        signature: "shift(table: Table<any>, periods: number) -> Table<any>",
523        description: "Shift series values by a number of periods.",
524        category: "Column",
525        parameters: &[
526            BuiltinParam {
527                name: "series",
528                param_type: "Table<any>",
529                optional: false,
530                description: "Input series",
531            },
532            BuiltinParam {
533                name: "periods",
534                param_type: "number",
535                optional: false,
536                description: "Number of periods to shift",
537            },
538        ],
539        return_type: "Table<any>",
540        example: Some("shift(prices, 1) // previous day's prices"),
541    },
542    BuiltinMetadata {
543        name: "resample",
544        signature: "resample(table: Table<any>, timeframe: string, method: string) -> Table<any>",
545        description: "Resample a series to a different timeframe.",
546        category: "Column",
547        parameters: &[
548            BuiltinParam {
549                name: "series",
550                param_type: "Table<any>",
551                optional: false,
552                description: "Input series",
553            },
554            BuiltinParam {
555                name: "timeframe",
556                param_type: "string",
557                optional: false,
558                description: "Target timeframe",
559            },
560            BuiltinParam {
561                name: "method",
562                param_type: "string",
563                optional: false,
564                description: "Aggregation method",
565            },
566        ],
567        return_type: "Table<any>",
568        example: Some("resample(prices, \"1h\", \"last\")"),
569    },
570    BuiltinMetadata {
571        name: "map",
572        signature: "map(table: Table<any>, fn: Function) -> Table<any>",
573        description: "Apply a function to each element of a series.",
574        category: "Column",
575        parameters: &[
576            BuiltinParam {
577                name: "series",
578                param_type: "Table<any>",
579                optional: false,
580                description: "Input series",
581            },
582            BuiltinParam {
583                name: "fn",
584                param_type: "Function",
585                optional: false,
586                description: "Transform function",
587            },
588        ],
589        return_type: "Table<any>",
590        example: Some("map(prices, (p) => p * 1.1)"),
591    },
592    BuiltinMetadata {
593        name: "filter",
594        signature: "filter(table: Table<any>, predicate: Function) -> Table<any>",
595        description: "Filter series elements by a predicate function.",
596        category: "Column",
597        parameters: &[
598            BuiltinParam {
599                name: "series",
600                param_type: "Table<any>",
601                optional: false,
602                description: "Input series",
603            },
604            BuiltinParam {
605                name: "predicate",
606                param_type: "Function",
607                optional: false,
608                description: "Filter predicate",
609            },
610        ],
611        return_type: "Table<any>",
612        example: Some("filter(prices, (p) => p > 100)"),
613    },
614    // Result constructors
615    BuiltinMetadata {
616        name: "Ok",
617        signature: "Ok(value) -> Result<T>",
618        description: "Wrap a value in a successful Result.",
619        category: "Utility",
620        parameters: &[BuiltinParam {
621            name: "value",
622            param_type: "any",
623            optional: false,
624            description: "Success value",
625        }],
626        return_type: "Result<T>",
627        example: Some("Ok(42)"),
628    },
629    BuiltinMetadata {
630        name: "Err",
631        signature: "Err(error) -> Result<T>",
632        description: "Create an error Result.",
633        category: "Utility",
634        parameters: &[BuiltinParam {
635            name: "error",
636            param_type: "any",
637            optional: false,
638            description: "Error value",
639        }],
640        return_type: "Result<T>",
641        example: Some("Err(\"not found\")"),
642    },
643    // Resumability
644    BuiltinMetadata {
645        name: "snapshot",
646        signature: "snapshot() -> Snapshot",
647        description: "Create a snapshot suspension point. Returns Snapshot::Hash(id) after saving, or Snapshot::Resumed when restoring from a snapshot.",
648        category: "Resumability",
649        parameters: &[],
650        return_type: "Snapshot",
651        example: Some(
652            "let result = snapshot()\nmatch result {\n    Snapshot::Hash(id) => print(\"Saved: \" + id),\n    Snapshot::Resumed => print(\"Restored!\"),\n}",
653        ),
654    },
655    BuiltinMetadata {
656        name: "exit",
657        signature: "exit(code?: number) -> ()",
658        description: "Terminate the process with an optional exit code.",
659        category: "Utility",
660        parameters: &[BuiltinParam {
661            name: "code",
662            param_type: "number",
663            optional: true,
664            description: "Exit code (default 0)",
665        }],
666        return_type: "()",
667        example: Some("exit(0)"),
668    },
669    // Comptime-only builtins
670    BuiltinMetadata {
671        name: "implements",
672        signature: "implements(type_name: string, trait_name: string) -> bool",
673        description: "Returns true if the given type implements the specified trait. Only valid inside comptime blocks.",
674        category: "Comptime",
675        parameters: &[
676            BuiltinParam {
677                name: "type_name",
678                param_type: "string",
679                optional: false,
680                description: "Type name to check",
681            },
682            BuiltinParam {
683                name: "trait_name",
684                param_type: "string",
685                optional: false,
686                description: "Trait name to check",
687            },
688        ],
689        return_type: "bool",
690        example: Some("comptime { implements(\"Point\", \"Display\") }"),
691    },
692    BuiltinMetadata {
693        name: "warning",
694        signature: "warning(msg: string) -> ()",
695        description: "Emit a compile-time warning. Only valid inside comptime blocks.",
696        category: "Comptime",
697        parameters: &[BuiltinParam {
698            name: "msg",
699            param_type: "string",
700            optional: false,
701            description: "Warning message",
702        }],
703        return_type: "()",
704        example: Some("comptime { warning(\"generated fallback path\") }"),
705    },
706    BuiltinMetadata {
707        name: "error",
708        signature: "error(msg: string) -> never",
709        description: "Emit a compile-time error and abort compilation. Only valid inside comptime blocks.",
710        category: "Comptime",
711        parameters: &[BuiltinParam {
712            name: "msg",
713            param_type: "string",
714            optional: false,
715            description: "Error message",
716        }],
717        return_type: "never",
718        example: Some("comptime { error(\"invariant violated\") }"),
719    },
720    BuiltinMetadata {
721        name: "build_config",
722        signature: "build_config() -> Object",
723        description: "Returns build-time configuration (debug, version, target_os, target_arch). Only valid inside comptime blocks.",
724        category: "Comptime",
725        parameters: &[],
726        return_type: "Object",
727        example: Some("let v = comptime { build_config().version }"),
728    },
729];
730
731/// Collect all builtin metadata for LSP introspection.
732///
733/// Returns references to static metadata for all core builtin functions.
734pub fn collect_builtin_metadata() -> Vec<&'static BuiltinMetadata> {
735    CORE_BUILTINS.iter().collect()
736}
737
738/// Convert all collected builtin metadata to FunctionInfo for LSP
739pub fn builtin_functions_from_macros() -> Vec<FunctionInfo> {
740    collect_builtin_metadata()
741        .into_iter()
742        .map(|m| m.into())
743        .collect()
744}
745
746/// Returns true if the function is a comptime-only builtin.
747///
748/// This is the single source of truth used by compiler and LSP.
749pub fn is_comptime_builtin_function(name: &str) -> bool {
750    CORE_BUILTINS
751        .iter()
752        .any(|m| m.name == name && m.category == "Comptime")
753}
754
755/// Static type metadata for Row
756/// Row is a generic container with dynamic fields discovered at runtime.
757/// Domain-specific fields (e.g., OHLCV for finance) are defined in stdlib types.
758pub static TYPE_METADATA_ROW: TypeMetadata = TypeMetadata {
759    name: "Row",
760    description: "Generic data row with dynamic fields",
761    properties: &[],
762};
763
764/// Collect core type metadata from derive macro generated constants.
765///
766/// This function returns ONLY generic/core type metadata that is
767/// industry-agnostic. Domain-specific types (like finance types)
768/// should be registered separately via `collect_domain_type_metadata`.
769pub fn collect_core_type_metadata() -> Vec<&'static TypeMetadata> {
770    vec![&TYPE_METADATA_ROW]
771}
772
773/// Collect domain-specific type metadata (finance/trading types).
774///
775/// These types are related to trading/backtesting infrastructure.
776/// In a fully modular system, these would be registered by a domain-specific
777/// stdlib module.
778pub fn collect_domain_type_metadata() -> Vec<&'static TypeMetadata> {
779    vec![]
780}
781
782/// Collect all type metadata from derive macro generated constants.
783///
784/// This function returns all type metadata that was generated
785/// by the `#[derive(ShapeType)]` derive macro. It's used by the LSP to
786/// provide property completions on typed expressions.
787///
788/// Note: This includes both core types and domain-specific types.
789/// For domain separation, use `collect_core_type_metadata` and
790/// `collect_domain_type_metadata` separately.
791pub fn collect_type_metadata() -> Vec<&'static TypeMetadata> {
792    let mut types = collect_core_type_metadata();
793    types.extend(collect_domain_type_metadata());
794    types
795}
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800
801    #[test]
802    fn test_builtin_metadata_to_function_info() {
803        static TEST_PARAMS: &[BuiltinParam] = &[BuiltinParam {
804            name: "value",
805            param_type: "Number",
806            optional: false,
807            description: "Input value",
808        }];
809
810        let meta = BuiltinMetadata {
811            name: "test_fn",
812            signature: "test_fn(value: Number) -> Number",
813            description: "A test function",
814            category: "Math",
815            parameters: TEST_PARAMS,
816            return_type: "Number",
817            example: Some("test_fn(42)"),
818        };
819
820        let info: FunctionInfo = (&meta).into();
821
822        assert_eq!(info.name, "test_fn");
823        assert_eq!(info.signature, "test_fn(value: Number) -> Number");
824        assert_eq!(info.description, "A test function");
825        assert_eq!(info.category, FunctionCategory::Math);
826        assert_eq!(info.parameters.len(), 1);
827        assert_eq!(info.parameters[0].name, "value");
828        assert_eq!(info.return_type, "Number");
829        assert_eq!(info.example, Some("test_fn(42)".to_string()));
830        assert!(info.implemented);
831    }
832}