Skip to content
← Back

src/syntax/process/inferred_annotations.ghul

1
namespace Syntax.Process is
2
use Logging
3
use Trees
4
5
use Semantic.Types.Type
6
7
// Collects, from a tree every body of which has compiled, the type
8
// the build settled at each site that left it to inference: a
9
// function literal's parameters and return, and a `let` local. The
10
// built tree carries nodes the source never had - adapters spliced
11
// around literals, synthesised names - so nothing is recorded
12
// against a node: each type is keyed by the position the site was
13
// written at, and read back against a fresh parse of the file.
14
//
15
// A type is written only where it can be spelled at that scope: a
16
// placeholder that never settled, ERROR, a type parameter foreign
17
// to the body, and anything rendering with a synthesised name are
18
// all left as written and counted as skipped.
19
class INFERRED_ANNOTATIONS: DefaultVisitor, Printer.InferredAnnotations is
20
_parameters: Collections.MAP[string, string]
21
_returns: Collections.MAP[string, string]
22
_locals: Collections.MAP[string, string]
23
_type_arguments: Collections.MAP[string, string]
24
25
annotated: int
26
skipped: int
27
28
init(
29
logger: Logger,
30
symbol_table: Semantic.SYMBOL_TABLE,
31
namespaces: Semantic.NAMESPACES
32
)
33
is
34
super.init(logger, symbol_table, namespaces)
35
36
_parameters = Collections.MAP[string, string]()
37
_returns = Collections.MAP[string, string]()
38
_locals = Collections.MAP[string, string]()
39
_type_arguments = Collections.MAP[string, string]()
40
si
41
42
collect(node: Node) is
43
node.walk(self)
44
si
45
46
parameter_type(location: Source.LOCATION) -> string? => _lookup(_parameters, location)
47
return_type(location: Source.LOCATION) -> string? => _lookup(_returns, location)
48
local_type(location: Source.LOCATION) -> string? => _lookup(_locals, location)
49
50
type_arguments(callee: Source.LOCATION) -> string? is
51
let key = _span_key(callee)
52
53
if _type_arguments.contains_key(key) then
54
return _type_arguments[key]
55
fi
56
57
return null
58
si
59
60
visit_default(node: Node) is
61
si
62
63
// A call or construction whose callee is a plain or qualified
64
// name and whose type arguments the build bound rather than the
65
// source wrote. The callee's whole span is the key, since a call
66
// whose callee is itself a call starts where that inner call does.
67
visit(call: Expressions.CALL) is
68
let callee = call.function
69
70
let callee_name =
71
if let identifier: Expressions.IDENTIFIER = callee then
72
identifier.identifier.name
73
elif let member: Expressions.MEMBER = callee then
74
member.identifier.name
75
else
76
return
77
fi
78
79
let arguments = _bound_type_arguments(call.value, callee_name)
80
81
if !arguments? \/ arguments.count == 0 then
82
return
83
fi
84
85
let spelled = System.Text.StringBuilder()
86
87
for type in arguments do
88
if let text = _spell(type) then
89
if spelled.length > 0 then
90
spelled.append(", ")
91
fi
92
93
spelled.append(text)
94
else
95
skipped = skipped + 1
96
97
IO.Std.error.write_line("{callee.location}: type arguments left as written: {type} ({_why(type)})")
98
99
return
100
fi
101
od
102
103
_type_arguments[_span_key(callee.location)] = spelled.to_string()
104
annotated = annotated + 1
105
si
106
107
_bound_type_arguments(value: IR.Values.Value?, callee_name: string) -> Collections.List[Type]? is
108
if let wrapper: IR.Values.WRAPPER = value then
109
return _bound_type_arguments(wrapper.value, callee_name)
110
elif let global: IR.Values.Call.GLOBAL = value then
111
return global.function.generic_arguments
112
elif let static_call: IR.Values.Call.STATIC = value then
113
return static_call.function.generic_arguments
114
elif let instance: IR.Values.Call.INSTANCE = value then
115
return instance.function.generic_arguments
116
elif let struct_call: IR.Values.Call.STRUCT = value then
117
return struct_call.function.generic_arguments
118
elif let construction: IR.Values.NEW = value then
119
// Constructed through a type alias, the type arguments
120
// belong to the type the alias names, and the alias
121
// cannot take them.
122
// A MAYBE construction is left as written for the reason a
123
// MAYBE type is: written out, it is a different carrier.
124
if
125
isa Semantic.Types.GENERIC(construction.type) /\
126
!construction.type.is_maybe /\
127
callee_name =~ construction.type.symbol.name
128
then
129
return construction.type.arguments
130
fi
131
fi
132
133
return null
134
si
135
136
visit(function: Expressions.FUNCTION) is
137
if let closure: Semantic.Symbols.Closure = function.scope then
138
_collect_literal(function, closure)
139
fi
140
141
super.visit(function)
142
si
143
144
visit(variable: Variables.VARIABLE) is
145
if
146
isa TypeExpressions.INFER(variable.type_expression) /\
147
variable.left.is_simple_name /\
148
!variable.is_synthesized
149
then
150
if let name = variable.left.name then
151
if name.name !~ "_" then
152
if let symbol = find(name.name) then
153
_record(_locals, variable.location, symbol.type)
154
fi
155
fi
156
fi
157
fi
158
si
159
160
_collect_literal(function: Expressions.FUNCTION, closure: Semantic.Symbols.Closure) is
161
let index mut = 0
162
163
for a in function.arguments.expressions do
164
if let parameter: Expressions.VARIABLE = a then
165
// A literal compiled taking an argument pack's tuple has
166
// one physical argument for several parameters, so a
167
// parameter's type is its symbol's, not the argument's
168
// at its position.
169
if isa TypeExpressions.INFER(parameter.type_expression) then
170
if let symbol: Semantic.Symbols.Variable = closure.find_direct(parameter.name.name) then
171
_record(_parameters, parameter.location, symbol.type)
172
fi
173
fi
174
fi
175
176
index = index + 1
177
od
178
179
if isa TypeExpressions.INFER(function.type_expression) then
180
_record(_returns, function.location, _writable_return(function, closure.return_type))
181
fi
182
si
183
184
// The return type as the source can write it. A literal whose
185
// body evaluates to an N-ary literal at a pack-marked hop has
186
// that hop presented in the one-parameter shape the formal takes;
187
// written back, the hop is spelled as the N-ary type the source
188
// is licensed to declare there.
189
_writable_return(function: Expressions.FUNCTION, type: Type?) -> Type? is
190
if !type? then
191
return null
192
fi
193
194
let body = cast Bodies.EXPRESSION?(function.body)
195
196
if !body? then
197
return type
198
fi
199
200
let inner = cast Expressions.FUNCTION?(body.expression)
201
202
if !inner? then
203
return type
204
fi
205
206
if let inner_closure: Semantic.Symbols.Closure = inner.scope /\ inner_closure.packed_parameters? then
207
// The N-ary spelling has no place for the element names
208
// the presented tuple carries, so writing it would drop
209
// them; the site is left to inference.
210
if _carries_element_names(type) then
211
return null
212
fi
213
214
let types = Collections.LIST[Type]()
215
216
for i in 0..inner_closure.arguments.count - 1 do
217
types.add(inner_closure.arguments[i])
218
od
219
220
for parameter in inner_closure.packed_parameters! do
221
if let parameter_type = parameter.type then
222
types.add(parameter_type)
223
fi
224
od
225
226
if let returned = inner_closure.return_type then
227
types.add(returned)
228
fi
229
230
return IoC.CONTAINER.instance.innate_symbol_lookup.get_function_type(types, type.is_pure_function)
231
fi
232
233
if !type.is_function \/ type.is_action then
234
return type
235
fi
236
237
let count = type.arguments.count
238
239
let rewritten = _writable_return(inner, type.arguments[count - 1])
240
241
if !rewritten? \/ rewritten == type.arguments[count - 1] then
242
return type
243
fi
244
245
let types = Collections.LIST[Type]()
246
247
for i in 0..count - 1 do
248
types.add(type.arguments[i])
249
od
250
251
types.add(rewritten)
252
253
return IoC.CONTAINER.instance.innate_symbol_lookup.get_function_type(types, type.is_pure_function)
254
si
255
256
_carries_element_names(presented: Type) -> bool static is
257
if !presented.is_function \/ presented.arguments.count == 0 then
258
return false
259
fi
260
261
if let names = presented.arguments[0].tuple_element_names then
262
return names |> Ghul.Pipes.any(name => name?)
263
fi
264
265
return false
266
si
267
268
_record(into: Collections.MAP[string, string], location: Source.LOCATION, type: Type?) is
269
if let text = _spell(type) then
270
into[_key(location)] = text
271
annotated = annotated + 1
272
else
273
skipped = skipped + 1
274
275
IO.Std.error.write_line("{location}: left as written: {type} ({_why(type)})")
276
fi
277
si
278
279
// The type a site is written as. A closed-hierarchy narrowing
280
// displays its member set; what is written is the type it narrows,
281
// optional where the narrowing is.
282
writable_type(given: Type) -> Type static =>
283
if let one_of: Semantic.Types.ONE_OF = given then
284
if given.is_optional then one_of.underlying_type.as_optional() else one_of.underlying_type fi
285
else
286
given
287
fi
288
289
_spell(given: Type?) -> string? is
290
if !given? then
291
return null
292
fi
293
294
let type = writable_type(given)
295
296
if isa Semantic.Types.FUNCTION_GROUP(type) then
297
return null
298
fi
299
300
let scope = _symbol_table.current_scope
301
302
if
303
type.contains_inferred \/
304
type.is_error \/
305
type.has_function_generic_argument_foreign_to(scope)
306
then
307
return null
308
fi
309
310
// `T?` over an unconstrained type parameter is carried as MAYBE[T]
311
// and renders as `T?`, which written back is a different carrier;
312
// the site is left to inference.
313
if _carries_maybe(type) then
314
return null
315
fi
316
317
// A type parameter whose name another in scope shadows cannot be
318
// named: the written name would resolve to the other one.
319
if _has_shadowed_type_parameter(type, scope) then
320
return null
321
fi
322
323
let use render_scope = IoC.CONTAINER.instance.name_display.with_scope(scope)
324
325
let text = "{type}"
326
327
if text.contains("!!!") \/ text.contains("$") then
328
return null
329
fi
330
331
return text
332
si
333
334
_has_shadowed_type_parameter(type: Type, scope: Semantic.Scope?) -> bool is
335
let found mut = false
336
337
type.walk(
338
t => if t.is_type_variable /\ t.symbol? /\ scope? then
339
if scope.find_enclosing(t.symbol.name) != t.symbol then
340
found = true
341
fi
342
fi)
343
344
return found
345
si
346
347
_carries_maybe(type: Type) -> bool is
348
let found mut = false
349
350
type.walk(t => if t.is_maybe then found = true fi)
351
352
return found
353
si
354
355
_why(type: Type?) -> string is
356
if !type? then
357
return "no type"
358
fi
359
360
let scope = _symbol_table.current_scope
361
362
if type.contains_inferred then
363
return "placeholder"
364
elif type.is_error then
365
return "error"
366
elif type.has_function_generic_argument_foreign_to(scope) then
367
return "foreign type parameter"
368
elif _carries_maybe(type) then
369
return "maybe carrier"
370
elif _has_shadowed_type_parameter(type, scope) then
371
return "shadowed type parameter"
372
fi
373
374
return "synthesised name"
375
si
376
377
_lookup(from: Collections.MAP[string, string], location: Source.LOCATION) -> string? is
378
let key = _key(location)
379
380
if from.contains_key(key) then
381
return from[key]
382
fi
383
384
return null
385
si
386
387
_key(location: Source.LOCATION) -> string => "{location.start_line},{location.start_column}"
388
389
_span_key(location: Source.LOCATION) -> string =>
390
"{location.start_line},{location.start_column}-{location.end_line},{location.end_column}"
391
si
392
si