Skip to content
← Back

src/syntax/process/signature_help.ghul

1
namespace Syntax.Process is
2
3
use Logging;
4
use Source;
5
6
use Semantic.Types.Type;
7
8
use Ghul.Pipes;
9
10
// One overload presented to the IDE's signature-help popup: a label
11
// for the whole call and a description per parameter. Built either
12
// from an overload-resolved FUNCTION_GROUP member or, when the callee
13
// is a value of function type (a let-bound lambda, a function-typed
14
// parameter, a directly-invoked anonymous function), from the
15
// function type itself.
16
class SIGNATURE is
17
description: string public;
18
parameter_descriptions: Collections.List[string] public;
19
20
init(description: string, parameter_descriptions: Collections.List[string]) is
21
self.description = description;
22
self.parameter_descriptions = parameter_descriptions;
23
si
24
si
25
26
class SIGNATURE_HELP_RESULT is
27
signatures: Collections.List[SIGNATURE] public;
28
best_signature_index: int public;
29
current_parameter_index: int public;
30
31
init(
32
signatures: Collections.List[SIGNATURE],
33
best_signature_index: int
34
) is
35
self.signatures = signatures;
36
self.best_signature_index = best_signature_index;
37
self.current_parameter_index = 0;
38
si
39
si
40
41
class SIGNATURE_HELP: ScopedVisitor is
42
_overload_resolver: Semantic.OVERLOAD_RESOLVER;
43
44
_target_line: int;
45
_target_column: int;
46
47
_results: SIGNATURE_HELP_RESULT?;
48
49
init(
50
logger: Logger,
51
symbol_table: Semantic.SYMBOL_TABLE,
52
namespaces: Semantic.NAMESPACES,
53
overload_resolver: Semantic.OVERLOAD_RESOLVER
54
)
55
is
56
super.init(logger, symbol_table, namespaces);
57
58
_overload_resolver = overload_resolver;
59
si
60
61
find_signatures(root: Trees.Node, target_line: int, target_column: int) -> SIGNATURE_HELP_RESULT? is
62
_results = null;
63
64
_target_line = target_line;
65
_target_column = target_column;
66
67
root.walk(self);
68
69
return _results;
70
si
71
72
visit(call: Trees.Expressions.CALL) is
73
if call.arguments == null then
74
return;
75
fi
76
77
if !_results? then
78
if !call.arguments.location.contains(_target_line, _target_column) /\
79
(!call.location.contains(_target_line, _target_column) \/
80
call.function.location.contains(_target_line, _target_column))
81
then
82
return;
83
fi
84
85
_results = help_for(call);
86
87
if _results? then
88
// A `|>` call carries its subject as arguments[0], but the
89
// subject sits to the left of the call's own location and
90
// is never the argument being edited. Locate the cursor
91
// among the written arguments alone, then shift past the
92
// parameter the subject occupies.
93
if call.is_thread_first /\ call.arguments.count > 0 then
94
_results.current_parameter_index =
95
get_current_parameter_index(
96
call.location,
97
Collections.LIST[Trees.Expressions.Expression](call.arguments |> skip(1)),
98
call.arguments.has_trailing_comma
99
) + 1;
100
else
101
_results.current_parameter_index =
102
get_current_parameter_index(
103
call.location,
104
Collections.LIST[Trees.Expressions.Expression](call.arguments),
105
call.arguments.has_trailing_comma
106
);
107
fi
108
fi
109
fi
110
si
111
112
visit(index: Trees.Expressions.INDEX) is
113
if index.index == null then
114
return;
115
fi
116
117
if !_results? then
118
let brackets_location =
119
LOCATION(
120
index.location.file_name,
121
index.left.location.end_line,
122
index.left.location.end_column,
123
index.location.end_line,
124
index.location.end_column
125
);
126
127
if !brackets_location.contains(_target_line, _target_column) \/
128
index.left.location.contains(_target_line, _target_column)
129
then
130
return;
131
fi
132
133
_results = help_for(index);
134
fi
135
si
136
137
visit(`new: Trees.Expressions.NEW) is
138
if `new.arguments == null then
139
return;
140
fi
141
142
if !(_results?) then
143
if
144
!`new.location.contains(_target_line, _target_column) \/
145
LOCATION(
146
`new.location.file_name,
147
`new.location.start_line,
148
`new.location.start_column,
149
`new.type_expression!.location.end_line,
150
`new.type_expression!.location.end_column
151
).contains(_target_line, _target_column)
152
then
153
return;
154
fi
155
156
_results = help_for(`new);
157
158
if _results? then
159
_results.current_parameter_index =
160
get_current_parameter_index(
161
`new.location,
162
Collections.LIST[Trees.Expressions.Expression](`new.arguments),
163
`new.arguments.has_trailing_comma
164
);
165
fi
166
fi
167
si
168
169
// Slot the cursor falls in, counting from the start of the call.
170
// Each slot runs from the end of the argument before it to the end
171
// of its own argument, so the comma introducing an argument belongs
172
// to that argument. A trailing comma adds one more slot, empty of
173
// any argument, running to the end of the call: that is where the
174
// cursor sits with the closing bracket already in place.
175
get_current_parameter_index(
176
full_location: LOCATION,
177
arguments: Collections.LIST[Trees.Expressions.Expression],
178
has_trailing_comma: bool
179
) -> int is
180
let slots =
181
if has_trailing_comma then
182
arguments.count + 1;
183
else
184
arguments.count;
185
fi;
186
187
if slots <= 1 then
188
return 0;
189
fi
190
191
let last = slots - 1;
192
193
for i in 0..slots do
194
let location =
195
LOCATION(
196
full_location.file_name,
197
if i == 0 then full_location.start_line else arguments[i-1].location.end_line fi,
198
if i == 0 then full_location.start_column else arguments[i-1].location.end_column fi,
199
if i == last then full_location.end_line else arguments[i].location.end_line fi,
200
if i == last then full_location.end_column else arguments[i].location.end_column fi
201
);
202
203
if location.contains(_target_line, _target_column) then
204
return i;
205
fi
206
od
207
208
return 0;
209
si
210
211
help_for(call: Trees.Expressions.CALL) -> SIGNATURE_HELP_RESULT? is
212
if _results? then
213
return null;
214
fi
215
216
let callee = call.function.value;
217
218
if !callee? then
219
return null;
220
fi
221
222
let function_group = function_group_of(callee);
223
224
if function_group? then
225
return help_for_function_group(function_group, argument_types_of(call.arguments));
226
fi
227
228
if callee.type? /\ callee.type!.is_function then
229
return help_for_function_type(callee.type!);
230
fi
231
232
return null;
233
si
234
235
// Signature help for `x[i]` / `x[i] = v`: resolve the indexer
236
// as the read accessor (`get_Item`) on the left's type. The
237
// assign-side `set_Item` shares its leading parameters, so the
238
// read form's signature gives a useful surface for the index
239
// arguments the user is typing.
240
help_for(index: Trees.Expressions.INDEX) -> SIGNATURE_HELP_RESULT? is
241
if _results? then
242
return null;
243
fi
244
245
let left_value = index.left.value;
246
247
if !left_value? \/ !left_value.type? then
248
return null;
249
fi
250
251
let type = left_value.type;
252
253
if type!.is_error then
254
return null;
255
fi
256
257
let named_type = cast Semantic.Types.NAMED?(type);
258
259
if !named_type? then
260
return null;
261
fi
262
263
let symbol = named_type.find_member("get_Item");
264
265
if !symbol? \/ !symbol.is_function_group then
266
return null;
267
fi
268
269
let argument_types = Collections.LIST[Type]();
270
271
if let value = index.index?.value /\ value.type? then
272
argument_types.add(value.type!);
273
else
274
argument_types.add(Semantic.Types.ERROR());
275
fi
276
277
return help_for_function_group(
278
cast Semantic.Symbols.FUNCTION_GROUP?(symbol)!,
279
argument_types
280
);
281
si
282
283
help_for(`new: Trees.Expressions.NEW) -> SIGNATURE_HELP_RESULT? is
284
if _results? then
285
return null;
286
fi
287
288
if !`new.type_expression? then
289
return null;
290
fi
291
292
let type = `new.type_expression.type;
293
294
if type == null then
295
return null;
296
fi
297
298
let symbol = type.find_member("init");
299
300
if !symbol? \/ !symbol.is_function_group then
301
return null;
302
fi
303
304
return help_for_function_group(
305
cast Semantic.Symbols.FUNCTION_GROUP?(symbol)!,
306
argument_types_of(`new.arguments)
307
);
308
si
309
310
// The callee's IR value resolves to a named function only when
311
// it is a direct load of a FUNCTION_GROUP symbol. A load of a
312
// variable / parameter / field of function type does not — its
313
// signature comes from the function type instead.
314
function_group_of(callee: IR.Values.Value) -> Semantic.Symbols.FUNCTION_GROUP? is
315
let load = cast IR.Values.Load.SYMBOL?(callee);
316
317
if !load? then
318
return null;
319
fi
320
321
return cast Semantic.Symbols.FUNCTION_GROUP?(load.symbol);
322
si
323
324
argument_types_of(arguments: Trees.Expressions.LIST) -> Collections.LIST[Type] is
325
let argument_types = Collections.LIST[Type]();
326
327
for a in arguments do
328
if let value = a?.value /\ value.type? then
329
argument_types.add(value.type!);
330
else
331
argument_types.add(Semantic.Types.ERROR());
332
fi
333
od
334
335
return argument_types;
336
si
337
338
help_for_function_group(
339
function_group: Semantic.Symbols.FUNCTION_GROUP,
340
argument_types: Collections.LIST[Type]
341
) -> SIGNATURE_HELP_RESULT? is
342
let overload_results = _overload_resolver.find_matches(function_group, argument_types);
343
344
if !overload_results? \/ overload_results.results.count == 0 then
345
return null;
346
fi
347
348
let signatures = Collections.LIST[SIGNATURE]();
349
350
for f in overload_results.results do
351
signatures.add(signature_for_function(f));
352
od
353
354
return SIGNATURE_HELP_RESULT(signatures, overload_results.best_result_index);
355
si
356
357
signature_for_function(function: Semantic.Symbols.Function) -> SIGNATURE is
358
let parameter_descriptions = Collections.LIST[string]();
359
360
for i in 0..function.arguments.count do
361
parameter_descriptions.add(function.get_short_argument_description(i));
362
od
363
364
return SIGNATURE(function.short_description, parameter_descriptions);
365
si
366
367
// Signature for a callee whose type is a function type. Function
368
// types carry no parameter names, so the label and parameter
369
// descriptions are the formal types alone: `(int, string) -> bool`.
370
help_for_function_type(function_type: Type) -> SIGNATURE_HELP_RESULT? is
371
let arguments = function_type.arguments;
372
373
let formal_count =
374
if function_type.is_action then
375
arguments.count;
376
else
377
arguments.count - 1;
378
fi;
379
380
if formal_count < 0 then
381
return null;
382
fi
383
384
let parameter_descriptions = Collections.LIST[string]();
385
386
for i in 0..formal_count do
387
parameter_descriptions.add(arguments[i].short_description);
388
od
389
390
let label = System.Text.StringBuilder();
391
392
label.append('(');
393
parameter_descriptions |> append_to(label, ", ");
394
label.append(") -> ");
395
396
if function_type.is_action then
397
label.append("void");
398
else
399
label.append(arguments[arguments.count - 1].short_description);
400
fi
401
402
return SIGNATURE_HELP_RESULT(
403
Collections.LIST[SIGNATURE]([SIGNATURE(label.to_string(), parameter_descriptions)]),
404
0
405
);
406
si
407
si
408
si