Skip to main content

praxis_stdlib/
completion.rs

1//! Completion-data generation from the method catalog (§19.8).
2//!
3//! The §19.8 acceptance criterion requires that "method completion data is
4//! generated from the same catalog used by the compiler." This module renders
5//! the [`MethodCatalog`](crate::MethodCatalog) into a serializable completion
6//! table for completion and signature help (§5.7: "The language server uses the
7//! same table"). No LSP wiring here — just the generation, plus a round-trip
8//! test proving the generated data covers the compiler's catalog 1:1.
9
10use crate::{MethodCatalog, MethodEntry};
11
12/// One completion item: the receiver shape, method name, parameter shapes,
13/// result shape, and doc — everything the LSP needs to offer a completion.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct CompletionItem {
16    /// The receiver type as a display string, e.g. `Vec[T]` or `Map[K, V]`.
17    pub receiver: String,
18    /// The method name, e.g. `push`.
19    pub name: String,
20    /// The parameter type-pattern display strings, in order.
21    pub params: Vec<String>,
22    /// The result type-pattern display string.
23    pub result: String,
24    /// The one-line doc.
25    pub doc: String,
26}
27
28/// Generate the full completion table from the catalog, in catalog order.
29/// Every entry becomes one [`CompletionItem`]; the output is a 1:1 rendering.
30#[must_use]
31pub fn completion_data(catalog: &MethodCatalog) -> Vec<CompletionItem> {
32    catalog.entries().iter().map(entry_to_item).collect()
33}
34
35/// Render one catalog entry as a completion item.
36fn entry_to_item(e: &MethodEntry) -> CompletionItem {
37    CompletionItem {
38        receiver: e.receiver.to_string(),
39        name: e.name.to_string(),
40        params: e.params.iter().map(|p| p.to_string()).collect(),
41        result: e.result.to_string(),
42        doc: e.doc.to_string(),
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn completion_data_covers_every_catalog_entry() {
52        // The §19.8 acceptance criterion: completion data is generated from the
53        // same catalog the compiler uses. Every builtin_catalog() entry must
54        // appear in the generated completion data, 1:1.
55        let cat = crate::builtin_catalog();
56        let items = completion_data(&cat);
57        assert_eq!(
58            items.len(),
59            cat.len(),
60            "completion data must cover every catalog entry"
61        );
62        // Spot-check a Vec and a Map entry (the headline receiver shapes).
63        let has_vec_push = items
64            .iter()
65            .any(|i| i.receiver == "Vec[T]" && i.name == "push");
66        assert!(has_vec_push, "Vec[T].push must be in completion data");
67        let has_map_insert = items
68            .iter()
69            .any(|i| i.receiver == "Map[K, V]" && i.name == "insert");
70        assert!(
71            has_map_insert,
72            "Map[K, V].insert must be in completion data"
73        );
74        let has_grid_neighbors4 = items
75            .iter()
76            .any(|i| i.receiver == "Grid[T]" && i.name == "neighbors4");
77        assert!(
78            has_grid_neighbors4,
79            "Grid[T].neighbors4 must be in completion data"
80        );
81        // A record-typed result renders as its *name*, which is what makes the
82        // neighbourhood rows offerable at all: `TypePattern`'s `Display` is the
83        // only thing between the catalog and the editor.
84        let around4 = items
85            .iter()
86            .find(|i| i.receiver == "Grid[T]" && i.name == "around4")
87            .expect("Grid[T].around4 must be in completion data");
88        assert_eq!(around4.result, "Around4");
89        assert_eq!(around4.params, vec!["(Int, Int)".to_string()]);
90    }
91
92    #[test]
93    fn completion_data_round_trips_receiver_name_arity() {
94        // Every (receiver, name, arity) triple in the catalog is unique (the
95        // builder rejects duplicates), so the completion items' triples must
96        // also be unique — a 1:1 mapping with no loss.
97        let cat = crate::builtin_catalog();
98        let items = completion_data(&cat);
99        let triples: Vec<(String, String, usize)> = items
100            .iter()
101            .map(|i| (i.receiver.clone(), i.name.clone(), i.params.len()))
102            .collect();
103        let unique: std::collections::HashSet<_> = triples.iter().collect();
104        assert_eq!(
105            unique.len(),
106            items.len(),
107            "completion triples must be unique"
108        );
109    }
110}