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 !_results? then
74
if !call.arguments.location.contains(_target_line, _target_column) /\
75
(!call.location.contains(_target_line, _target_column) \/
76
call.function.location.contains(_target_line, _target_column))
77
then
78
return
79
fi
80
81
_results = help_for(call)
82
83
if let results = _results then
84
// A `|>` call carries its subject as arguments[0], but the
85
// subject sits to the left of the call's own location and
86
// is never the argument being edited. Locate the cursor
87
// among the written arguments alone, then shift past the
88
// parameter the subject occupies.
89
if call.is_thread_first /\ call.arguments.count > 0 then
90
results.current_parameter_index =
91
get_current_parameter_index(
92
call.location,
93
Collections.LIST[Trees.Expressions.Expression](call.arguments |> skip(1)),
94
call.arguments.has_trailing_comma
95
) + 1
96
else
97
results.current_parameter_index =
98
get_current_parameter_index(
99
call.location,
100
Collections.LIST[Trees.Expressions.Expression](call.arguments),
101
call.arguments.has_trailing_comma
102
)
103
fi
104
fi
105
fi
106
si
107
108
visit(index: Trees.Expressions.INDEX) is
109
if !_results? then
110
let brackets_location =
111
LOCATION(
112
index.location.file_name,
113
index.left.location.end_line,
114
index.left.location.end_column,
115
index.location.end_line,
116
index.location.end_column
117
)
118
119
if !brackets_location.contains(_target_line, _target_column) \/
120
index.left.location.contains(_target_line, _target_column)
121
then
122
return
123
fi
124
125
_results = help_for(index)
126
fi
127
si
128
129
// Slot the cursor falls in, counting from the start of the call.
130
// Each slot runs from the end of the argument before it to the end
131
// of its own argument, so the comma introducing an argument belongs
132
// to that argument. A trailing comma adds one more slot, empty of
133
// any argument, running to the end of the call: that is where the
134
// cursor sits with the closing bracket already in place.
135
get_current_parameter_index(
136
full_location: LOCATION,
137
arguments: Collections.LIST[Trees.Expressions.Expression],
138
has_trailing_comma: bool
139
) -> int is
140
let slots =
141
if has_trailing_comma then
142
arguments.count + 1
143
else
144
arguments.count
145
fi
146
147
if slots <= 1 then
148
return 0
149
fi
150
151
let last = slots - 1
152
153
for i in 0..slots do
154
let location =
155
LOCATION(
156
full_location.file_name,
157
if i == 0 then full_location.start_line else arguments[i-1].location.end_line fi,
158
if i == 0 then full_location.start_column else arguments[i-1].location.end_column fi,
159
if i == last then full_location.end_line else arguments[i].location.end_line fi,
160
if i == last then full_location.end_column else arguments[i].location.end_column fi
161
)
162
163
if location.contains(_target_line, _target_column) then
164
return i
165
fi
166
od
167
168
return 0
169
si
170
171
help_for(call: Trees.Expressions.CALL) -> SIGNATURE_HELP_RESULT? is
172
if _results? then
173
return null
174
fi
175
176
let callee = call.function.value
177
178
if !callee? then
179
return null
180
fi
181
182
let function_group = function_group_of(callee)
183
184
if function_group? then
185
return help_for_function_group(function_group, argument_types_of(call.arguments))
186
fi
187
188
if callee.type? /\ callee.type.is_function then
189
return help_for_function_type(callee.type)
190
fi
191
192
return null
193
si
194
195
// Signature help for `x[i]` / `x[i] = v`: resolve the indexer
196
// as the read accessor (`get_Item`) on the left's type. The
197
// assign-side `set_Item` shares its leading parameters, so the
198
// read form's signature gives a useful surface for the index
199
// arguments the user is typing.
200
help_for(index: Trees.Expressions.INDEX) -> SIGNATURE_HELP_RESULT? is
201
if _results? then
202
return null
203
fi
204
205
let left_value = index.left.value
206
207
if !left_value? \/ !left_value.type? then
208
return null
209
fi
210
211
let type = left_value.type
212
213
if type.is_error then
214
return null
215
fi
216
217
let named_type = cast Semantic.Types.NAMED?(type)
218
219
if !named_type? then
220
return null
221
fi
222
223
let symbol = named_type.find_member(Semantic.Symbols.INDEXER_NAMES.read)
224
225
if !symbol? \/ !symbol.is_function_group then
226
return null
227
fi
228
229
let argument_types = Collections.LIST[Type]()
230
231
if let value = index.index?.value /\ value.type? then
232
argument_types.add(value.type)
233
else
234
argument_types.add(Semantic.Types.ERROR())
235
fi
236
237
return help_for_function_group(
238
cast Semantic.Symbols.FUNCTION_GROUP?(symbol)!,
239
argument_types
240
)
241
si
242
243
// The callee's IR value resolves to a named function only when
244
// it is a direct load of a FUNCTION_GROUP symbol. A load of a
245
// variable / parameter / field of function type does not — its
246
// signature comes from the function type instead.
247
function_group_of(callee: IR.Values.Value) -> Semantic.Symbols.FUNCTION_GROUP? is
248
let load = cast IR.Values.Load.SYMBOL?(callee)
249
250
if !load? then
251
return null
252
fi
253
254
return cast Semantic.Symbols.FUNCTION_GROUP?(load.symbol)
255
si
256
257
argument_types_of(arguments: Trees.Expressions.LIST) -> Collections.LIST[Type] is
258
let argument_types = Collections.LIST[Type]()
259
260
for a in arguments do
261
if let value = a?.value /\ value.type? then
262
argument_types.add(value.type)
263
else
264
argument_types.add(Semantic.Types.ERROR())
265
fi
266
od
267
268
return argument_types
269
si
270
271
help_for_function_group(
272
function_group: Semantic.Symbols.FUNCTION_GROUP,
273
argument_types: Collections.LIST[Type]
274
) -> SIGNATURE_HELP_RESULT? is
275
let overload_results = _overload_resolver.find_matches(function_group, argument_types)
276
277
if !overload_results? \/ overload_results.results.count == 0 then
278
return null
279
fi
280
281
let signatures = Collections.LIST[SIGNATURE]()
282
283
for f in overload_results.results do
284
signatures.add(signature_for_function(f))
285
od
286
287
return SIGNATURE_HELP_RESULT(signatures, overload_results.best_result_index)
288
si
289
290
signature_for_function(function: Semantic.Symbols.Function) -> SIGNATURE is
291
let parameter_descriptions = Collections.LIST[string]()
292
293
for i in 0..function.arguments.count do
294
parameter_descriptions.add(function.get_short_argument_description(i))
295
od
296
297
return SIGNATURE(function.short_description, parameter_descriptions)
298
si
299
300
// Signature for a callee whose type is a function type. Function
301
// types carry no parameter names, so the label and parameter
302
// descriptions are the formal types alone: `(int, string) -> bool`.
303
help_for_function_type(function_type: Type) -> SIGNATURE_HELP_RESULT? is
304
let arguments = function_type.arguments
305
306
let formal_count =
307
if function_type.is_action then
308
arguments.count
309
else
310
arguments.count - 1
311
fi
312
313
if formal_count < 0 then
314
return null
315
fi
316
317
let parameter_descriptions = Collections.LIST[string]()
318
319
for i in 0..formal_count do
320
parameter_descriptions.add(arguments[i].short_description)
321
od
322
323
let label = System.Text.StringBuilder()
324
325
label.append('(')
326
parameter_descriptions |> append_to(label, ", ")
327
label.append(") -> ")
328
329
if function_type.is_action then
330
label.append("void")
331
else
332
label.append(arguments[arguments.count - 1].short_description)
333
fi
334
335
return SIGNATURE_HELP_RESULT(
336
Collections.LIST[SIGNATURE]([SIGNATURE(label.to_string(), parameter_descriptions)]),
337
0
338
)
339
si
340
si
341
si