1+ use mimium_lang:: ast:: Expr ;
2+ use mimium_lang:: ast:: program:: VisibilityMap ;
13use mimium_lang:: compiler:: mirgen;
2- use mimium_lang:: interner:: { Symbol , TypeNodeId } ;
4+ use mimium_lang:: interner:: { ExprNodeId , Symbol , TypeNodeId } ;
5+ use mimium_lang:: pattern:: Pattern ;
6+ use mimium_lang:: types:: Type ;
37use mimium_lang:: utils:: error:: ReportableError ;
48use ropey:: Rope ;
59use tower_lsp:: lsp_types:: {
@@ -14,10 +18,44 @@ pub struct AnalysisRequest {
1418 pub text : String ,
1519}
1620
21+ /// Parameter information for signature help.
22+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
23+ pub struct ParamInfo {
24+ pub name : String ,
25+ pub type_str : String ,
26+ pub has_default : bool ,
27+ }
28+
29+ /// Signature information for a function definition.
30+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
31+ pub struct FnSignature {
32+ pub name : String ,
33+ pub params : Vec < ParamInfo > ,
34+ pub return_type : String ,
35+ }
36+
37+ /// Kind of a completion symbol.
38+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
39+ pub enum CompletionKind {
40+ Function ,
41+ Variable ,
42+ }
43+
44+ /// A single symbol available for completion.
45+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
46+ pub struct CompletionSymbol {
47+ pub name : String ,
48+ pub kind : CompletionKind ,
49+ /// Human-readable type annotation, empty when unknown.
50+ pub type_str : String ,
51+ }
52+
1753#[ derive( Debug , serde:: Serialize , serde:: Deserialize ) ]
1854pub struct AnalysisResponse {
1955 pub diagnostics : Vec < Diagnostic > ,
2056 pub semantic_tokens : Vec < ImCompleteSemanticToken > ,
57+ pub fn_signatures : Vec < FnSignature > ,
58+ pub completion_symbols : Vec < CompletionSymbol > ,
2159}
2260
2361pub fn analyze_source (
@@ -42,6 +80,9 @@ pub fn analyze_source(
4280 module_info,
4381 } = parse ( src, url. as_str ( ) ) ;
4482
83+ // Save visibility info before module_info is consumed by typecheck.
84+ let visibility_map = module_info. visibility_map . clone ( ) ;
85+
4586 let errs = {
4687 let ast = ast. wrap_to_staged_expr ( ) ;
4788 let ( _, _, typeerrs) =
@@ -54,9 +95,215 @@ pub fn analyze_source(
5495 . flat_map ( |item| diagnostic_from_error ( item, url. clone ( ) , & rope) )
5596 . collect :: < Vec < Diagnostic > > ( ) ;
5697
98+ let fn_signatures = collect_fn_signatures ( ast, & visibility_map) ;
99+ let completion_symbols = collect_completion_symbols ( ast, & visibility_map) ;
100+
57101 AnalysisResponse {
58102 diagnostics,
59103 semantic_tokens,
104+ fn_signatures,
105+ completion_symbols,
106+ }
107+ }
108+
109+ /// Format a type for display in signature help.
110+ /// Returns an empty string for unknown types.
111+ fn format_type_for_display ( ty : & Type ) -> String {
112+ match ty {
113+ Type :: Unknown => String :: new ( ) ,
114+ other => format ! ( "{other}" ) ,
115+ }
116+ }
117+
118+ /// Returns `true` if the symbol with the given mangled name should be included
119+ /// in completions/signatures.
120+ ///
121+ /// Top-level symbols (no `$` in mangled name) are always visible because they
122+ /// live in the same file scope. Module members (contain `$`) are only visible
123+ /// when declared `pub`.
124+ fn is_visible ( mangled : & str , visibility_map : & VisibilityMap ) -> bool {
125+ if !mangled. contains ( '$' ) {
126+ // Top-level: always available within the same file.
127+ return true ;
128+ }
129+ // Module member: only public ones are accessible outside the module.
130+ let sym = mimium_lang:: interner:: ToSymbol :: to_symbol ( & mangled) ;
131+ visibility_map. get ( & sym) . copied ( ) . unwrap_or ( false )
132+ }
133+
134+ /// Convert an internally mangled name (`mod$fn`) to mimium source syntax (`mod::fn`).
135+ fn demangle_name ( name : & str ) -> String {
136+ name. replace ( '$' , "::" )
137+ }
138+
139+ /// Walk the AST and collect all function signatures.
140+ fn collect_fn_signatures ( root : ExprNodeId , visibility_map : & VisibilityMap ) -> Vec < FnSignature > {
141+ let mut sigs = Vec :: new ( ) ;
142+ collect_fn_signatures_rec ( root, visibility_map, & mut sigs) ;
143+ // Deduplicate by name, keeping the first occurrence.
144+ let mut seen = std:: collections:: HashSet :: new ( ) ;
145+ sigs. retain ( |s| seen. insert ( s. name . clone ( ) ) ) ;
146+ sigs
147+ }
148+
149+ /// Recursively walk the AST to find function definitions (LetRec with Lambda).
150+ fn collect_fn_signatures_rec (
151+ expr_id : ExprNodeId ,
152+ visibility_map : & VisibilityMap ,
153+ sigs : & mut Vec < FnSignature > ,
154+ ) {
155+ match expr_id. to_expr ( ) {
156+ Expr :: LetRec ( typed_id, value, next) => {
157+ if let Expr :: Lambda ( params, ret_ty, _body) = value. to_expr ( ) {
158+ if is_visible ( typed_id. id . as_str ( ) , visibility_map) {
159+ let name = demangle_name ( typed_id. id . as_str ( ) ) ;
160+ let params_info = params
161+ . iter ( )
162+ . map ( |p| {
163+ let ty = p. ty . to_type ( ) ;
164+ ParamInfo {
165+ name : p. id . as_str ( ) . to_string ( ) ,
166+ type_str : format_type_for_display ( & ty) ,
167+ has_default : p. default_value . is_some ( ) ,
168+ }
169+ } )
170+ . collect ( ) ;
171+ let return_type = ret_ty
172+ . map ( |r| format_type_for_display ( & r. to_type ( ) ) )
173+ . unwrap_or_default ( ) ;
174+ sigs. push ( FnSignature {
175+ name,
176+ params : params_info,
177+ return_type,
178+ } ) ;
179+ }
180+ }
181+ if let Some ( next) = next {
182+ collect_fn_signatures_rec ( next, visibility_map, sigs) ;
183+ }
184+ }
185+ Expr :: Let ( _pat, _value, Some ( next) ) => {
186+ collect_fn_signatures_rec ( next, visibility_map, sigs) ;
187+ }
188+ Expr :: Then ( e1, e2) => {
189+ collect_fn_signatures_rec ( e1, visibility_map, sigs) ;
190+ if let Some ( e2) = e2 {
191+ collect_fn_signatures_rec ( e2, visibility_map, sigs) ;
192+ }
193+ }
194+ Expr :: Bracket ( inner) => {
195+ collect_fn_signatures_rec ( inner, visibility_map, sigs) ;
196+ }
197+ Expr :: Escape ( inner) => {
198+ collect_fn_signatures_rec ( inner, visibility_map, sigs) ;
199+ }
200+ _ => { }
201+ }
202+ }
203+
204+ /// Walk the AST and collect all symbols visible at the top level or as locals.
205+ fn collect_completion_symbols (
206+ root : ExprNodeId ,
207+ visibility_map : & VisibilityMap ,
208+ ) -> Vec < CompletionSymbol > {
209+ let mut syms = Vec :: new ( ) ;
210+ collect_completion_symbols_rec ( root, visibility_map, & mut syms) ;
211+ // Deduplicate by name, keeping the first occurrence.
212+ let mut seen = std:: collections:: HashSet :: new ( ) ;
213+ syms. retain ( |s| seen. insert ( s. name . clone ( ) ) ) ;
214+ syms
215+ }
216+
217+ /// Recursively walk the AST collecting bindings.
218+ ///
219+ /// Collects:
220+ /// - `LetRec` bindings: functions (Lambda) and plain values
221+ /// - `Let` bindings: variable patterns (including tuple destructuring)
222+ /// - Lambda parameter names from function bodies
223+ fn collect_completion_symbols_rec (
224+ expr_id : ExprNodeId ,
225+ visibility_map : & VisibilityMap ,
226+ syms : & mut Vec < CompletionSymbol > ,
227+ ) {
228+ match expr_id. to_expr ( ) {
229+ Expr :: LetRec ( typed_id, value, next) => {
230+ let type_str = format_type_for_display ( & typed_id. ty . to_type ( ) ) ;
231+ let ( kind, type_str) = if let Expr :: Lambda ( _params, ret_ty, body) = value. to_expr ( ) {
232+ // Do not collect lambda parameters here: they are local to the
233+ // function body and not visible in the surrounding scope.
234+ // Recurse into the body to pick up any nested let-bindings.
235+ collect_completion_symbols_rec ( body, visibility_map, syms) ;
236+ let ret = ret_ty
237+ . map ( |r| format_type_for_display ( & r. to_type ( ) ) )
238+ . unwrap_or_default ( ) ;
239+ ( CompletionKind :: Function , ret)
240+ } else {
241+ ( CompletionKind :: Variable , type_str)
242+ } ;
243+ if is_visible ( typed_id. id . as_str ( ) , visibility_map) {
244+ syms. push ( CompletionSymbol {
245+ name : demangle_name ( typed_id. id . as_str ( ) ) ,
246+ kind,
247+ type_str,
248+ } ) ;
249+ }
250+ if let Some ( next) = next {
251+ collect_completion_symbols_rec ( next, visibility_map, syms) ;
252+ }
253+ }
254+ Expr :: Let ( typed_pattern, _value, next) => {
255+ collect_pattern_symbols (
256+ & typed_pattern. pat ,
257+ & format_type_for_display ( & typed_pattern. ty . to_type ( ) ) ,
258+ syms,
259+ ) ;
260+ if let Some ( next) = next {
261+ collect_completion_symbols_rec ( next, visibility_map, syms) ;
262+ }
263+ }
264+ Expr :: Then ( e1, e2) => {
265+ collect_completion_symbols_rec ( e1, visibility_map, syms) ;
266+ if let Some ( e2) = e2 {
267+ collect_completion_symbols_rec ( e2, visibility_map, syms) ;
268+ }
269+ }
270+ Expr :: Bracket ( inner) | Expr :: Escape ( inner) => {
271+ collect_completion_symbols_rec ( inner, visibility_map, syms) ;
272+ }
273+ _ => { }
274+ }
275+ }
276+
277+ /// Recursively extract bound names from a pattern.
278+ fn collect_pattern_symbols ( pattern : & Pattern , type_str : & str , syms : & mut Vec < CompletionSymbol > ) {
279+ match pattern {
280+ Pattern :: Single ( sym) => {
281+ syms. push ( CompletionSymbol {
282+ name : sym. as_str ( ) . to_string ( ) ,
283+ kind : CompletionKind :: Variable ,
284+ type_str : type_str. to_string ( ) ,
285+ } ) ;
286+ }
287+ Pattern :: Tuple ( pats) => {
288+ for p in pats {
289+ collect_pattern_symbols ( p, "" , syms) ;
290+ }
291+ }
292+ Pattern :: Record ( fields) => {
293+ for ( name, pat) in fields {
294+ // Prefer the field name as label; recurse if it binds further.
295+ collect_pattern_symbols ( pat, "" , syms) ;
296+ // If the sub-pattern is just Placeholder, still expose the field name.
297+ if matches ! ( pat, Pattern :: Placeholder ) {
298+ syms. push ( CompletionSymbol {
299+ name : name. as_str ( ) . to_string ( ) ,
300+ kind : CompletionKind :: Variable ,
301+ type_str : String :: new ( ) ,
302+ } ) ;
303+ }
304+ }
305+ }
306+ Pattern :: Placeholder | Pattern :: Error => { }
60307 }
61308}
62309
0 commit comments