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}