Skip to content
← Back

src/semantic/overload_resolver.ghul

1
namespace Semantic is
2
use System.Exception;
3
4
use Ghul.Pipes;
5
6
use Logging;
7
use Source;
8
9
use Types.Type;
10
11
// `score` is the summed per-argument match quality, kept for
12
// ranking candidates against each other. `needs_retry` is the
13
// explicit signal that a function-literal actual with implicit
14
// parameter types matched without its real signature, so a
15
// constraint-push re-walk of the arguments could improve the
16
// result. Callers must consult `needs_retry` rather than testing
17
// `score` against a MATCH constant: the sum of several coercion
18
// scores can collide with any single MATCH value.
19
class OVERLOAD_RESOLVE_RESULT(function: Symbols.Function, score: Types.MATCH, needs_retry: bool) is
20
si
21
22
class OVERLOAD_MATCHES_RESULT(
23
results: Collections.List[Symbols.Function],
24
best_result_index: int,
25
current_parameter_index: int public
26
) is
27
si
28
29
class OVERLOAD_RESOLVER(_logger: Logger) is
30
match_propagator: MATCH_PROPAGATOR public;
31
_delegate_shape: DELEGATE_SHAPE;
32
33
super();
34
35
init(..) is
36
match_propagator = MATCH_PROPAGATOR(_logger);
37
_delegate_shape = DELEGATE_SHAPE();
38
si
39
40
resolve(
41
location: LOCATION,
42
group: Symbols.FUNCTION_GROUP,
43
arguments: Collections.List[Type],
44
want_infer: bool,
45
want_instance: bool,
46
is_constructor_call: bool
47
) -> OVERLOAD_RESOLVE_RESULT? =>
48
resolve(location, group, arguments, want_infer, want_instance, is_constructor_call, null, null);
49
50
// `restrict_to`, when non-null, limits resolution to the given
51
// candidates instead of the whole group - used by named-
52
// argument calls, which have already culled the group to the
53
// overloads whose parameter names match.
54
resolve(
55
location: LOCATION,
56
group: Symbols.FUNCTION_GROUP,
57
arguments: Collections.List[Type],
58
want_infer: bool,
59
want_instance: bool,
60
is_constructor_call: bool,
61
restrict_to: Collections.List[Symbols.Function]?
62
) -> OVERLOAD_RESOLVE_RESULT? =>
63
resolve(location, group, arguments, want_infer, want_instance, is_constructor_call, restrict_to, null);
64
65
// `return_constraint`, when non-null, supplies the return-type
66
// context (typically `function.return_type` of the enclosing
67
// function for a call in return-position, or the LHS slot type
68
// for an assignment-position call). Used as a tie-breaker when
69
// arg-side scoring leaves multiple candidates tied: a candidate
70
// whose return type is assignable to the constraint wins over
71
// one that isn't.
72
resolve(
73
location: LOCATION,
74
group: Symbols.FUNCTION_GROUP,
75
arguments: Collections.List[Type],
76
want_infer: bool,
77
want_instance: bool,
78
is_constructor_call: bool,
79
restrict_to: Collections.List[Symbols.Function]?,
80
return_constraint: Type?
81
) -> OVERLOAD_RESOLVE_RESULT?
82
is
83
let mark = _logger.mark();
84
85
try
86
return _resolve(location, group, arguments, want_infer, want_instance, is_constructor_call, restrict_to, return_constraint);
87
catch e: Exception
88
_logger.release(mark);
89
90
_logger.exception(location, e, "exception resolving overload: {group} arguments {arguments}");
91
return null;
92
93
finally
94
_logger.release(mark);
95
yrt
96
si
97
98
find_matches(
99
group: Symbols.FUNCTION_GROUP,
100
arguments: Collections.List[Type]
101
) -> OVERLOAD_MATCHES_RESULT?
102
is
103
let mark = _logger.mark();
104
105
try
106
return _find_matches(group, arguments);
107
catch e: Exception
108
_logger.release(mark);
109
110
_logger.exception(null, e, "exception resolving overload: {group} arguments {arguments}");
111
return null;
112
113
finally
114
_logger.release(mark);
115
yrt
116
si
117
118
_resolve(
119
location: LOCATION,
120
group: Symbols.FUNCTION_GROUP,
121
arguments: Collections.List[Type],
122
want_infer: bool,
123
want_instance: bool,
124
is_constructor_call: bool,
125
restrict_to: Collections.List[Symbols.Function]?,
126
return_constraint: Type?
127
) -> OVERLOAD_RESOLVE_RESULT?
128
is
129
if group == null \/ group.functions == null \/ arguments == null then
130
return null;
131
fi
132
133
// FIXME: this could just as well be applied to any parameters of generic type, not just anon functions
134
// an argument entry may be null
135
@suppress("presence-test-non-optional")
136
let needs_second_call = arguments |> any(a => a? /\ a.is_function_with_any_implicit_argument_types);
137
138
let saw_delegate_target mut = false;
139
140
let is_ambiguous mut = false;
141
142
let best_score mut = cast int (Types.MATCH.DIFFERENT);
143
let result: Symbols.Function? mut = _;
144
145
let ambiguous_matches: Collections.LIST[Symbols.Function]? mut = null;
146
147
let search_functions = if restrict_to? then restrict_to else group.functions fi;
148
149
// A static constructor is invoked by the CLR, never by an
150
// explicit call or `T(...)` construction, so it must not be
151
// an overload candidate even though it shares the `init`
152
// group with the instance constructors.
153
let functions_to_search = search_functions |> filter(f => (want_instance \/ !f.is_instance) /\ !f.is_static_constructor);
154
155
// We need to return PARTIAL if any actual argument types are wild. 'PARTIAL' provides
156
// the caller with the best match we can find, and the caller is expected to use that
157
// to bind any unknown types in the actual arguments and then try overload resolution
158
// again
159
160
for f in functions_to_search do
161
let actual: Symbols.Function? mut = f;
162
163
if f.arguments == null then
164
return OVERLOAD_RESOLVE_RESULT(f, Types.MATCH.DIFFERENT, false);
165
elif f.arguments.count == 0 /\ arguments.count == 0 then
166
return OVERLOAD_RESOLVE_RESULT(f, Types.MATCH.SAME, false);
167
elif f.arguments.count == arguments.count then
168
let want_try_bind_owner_generic_arguments mut = false;
169
let want_try_bind_function_generic_arguments mut = false;
170
171
if is_constructor_call then
172
want_try_bind_owner_generic_arguments = true;
173
want_try_bind_function_generic_arguments = false;
174
elif f.is_instance then
175
// Iterative-inference: an instance method's
176
// owner type-args may be unbound when the
177
// receiver was constructed without explicit
178
// type-args and without args binding T (e.g.
179
// `let m = Box(); m.set(42)`). Allow owner-
180
// generic-arg binding here so the resolver can
181
// pick T = int from the actual argument and
182
// produce the specialized owner. For receivers
183
// whose type-args are already bound, the
184
// specialization is a no-op.
185
want_try_bind_owner_generic_arguments = true;
186
want_try_bind_function_generic_arguments = true;
187
elif f.is_generic then
188
want_try_bind_owner_generic_arguments = true;
189
want_try_bind_function_generic_arguments = true;
190
else
191
want_try_bind_owner_generic_arguments = true;
192
want_try_bind_function_generic_arguments = false;
193
fi
194
195
let try_bind_generic_arguments mut = false;
196
let score mut = cast int(Types.MATCH.SAME);
197
198
let owner_symbol = cast Symbols.Classy?(f.owner)!;
199
200
for i in 0..f.arguments.count do
201
let match: Types.MATCH mut;
202
203
let f_arg = f.arguments[i];
204
let arg = arguments[i];
205
206
// an argument may be null
207
@suppress("presence-test-non-optional")
208
if arg? then
209
match = f_arg.compare(arg);
210
211
// A function literal can be compiled as the
212
// delegate this formal asks for, but it has
213
// been walked as a plain function type and so
214
// compares DIFFERENT. Report a partial match
215
// instead: the retry pass pushes this formal
216
// down and re-walks the argument, which either
217
// produces the delegate or fails the second
218
// resolve on its own terms.
219
if
220
match == Types.MATCH.DIFFERENT /\
221
arg.is_function /\
222
_delegate_shape.is_named_delegate(f_arg)
223
then
224
match = Types.MATCH.PARTIAL;
225
saw_delegate_target = true;
226
fi
227
228
if match == Types.MATCH.DIFFERENT then
229
// if any argument type compare returns DIFFERENT then
230
// this overload cannot match the supplied arguments
231
// even allowing for type argument inference of any
232
// type arguments in the formal arguments or of
233
// unknown types in the actual arguments, so
234
// bail on this overload immediately:
235
score = cast int(Types.MATCH.DIFFERENT);
236
break;
237
elif match == Types.MATCH.WILD then
238
// either or both of the following has occurred:
239
// 1. the formal argument type is 'wild', i.e. its type expression
240
// includes at least one generic type argument that could be free
241
// to be bound to the type in corresponding position in the actual
242
// argument type. The type argument could be appear inside the type
243
// expression at any depth, for example `List[T]` or `int -> T`
244
// 2. the actual argument type is 'any', i.e. its type expression
245
// includes at least one unknown type where the actual type can
246
// potentially be inferred based on the formal argument type
247
248
// we need first to figure out which it is. If it's both then type
249
// inference probably isn't possible, but we will still attempt it
250
251
if f_arg.is_wild then
252
// this formal argument is wild, we need to figure out if any
253
// type arguments in it could be free in this context
254
255
// if the overload is an instance method we then can't supply
256
// actual type parameters to its owning class/struct either explicitly
257
// or via inference - they're already applied to the instance we're
258
// calling the method on
259
260
// if the overload is a static method, we can potentially supply
261
// actual type arguments for its owning class
262
263
// and in either case we can supply actual type arguments for the
264
// method itself
265
266
// if the function is a global function then we can supply actual
267
// type arguments for it
268
269
// if the function is a constructor, and we're calling it for a
270
// constructor expression, it cannot have generic arguments but
271
// its owning type can, and we do want to supply them if we
272
// can infer them from this overload
273
274
match = Types.MATCH.SAME;
275
276
try_bind_generic_arguments = true;
277
fi
278
elif arg.is_error \/ arg.contains_inferred then
279
// The actual argument is an ERROR/placeholder sentinel
280
// or a composite carrying one inside. Treat as a
281
// matching argument and see if that produces an
282
// unambiguous overload result — if so the caller
283
// uses the chosen overload's formal type to infer
284
// back into the placeholder via match propagation.
285
286
// FIXME not sure it makes sense to be setting score here - should be match
287
score = cast int(Types.MATCH.PARTIAL);
288
elif f_arg.is_wild /\ isa Types.NULL(arg) then
289
// A wild optional formal (`T?`) trivially accepts a
290
// bare null without pinning its type variable, so
291
// compare returns a plain assignable match rather than
292
// WILD. Still route through generic-argument binding:
293
// the type variable is left free otherwise, and the
294
// selected overload would emit with an unresolved `!!N`
295
// that fails to load at run time. Errors and inference
296
// sentinels are handled by the branch above; this is
297
// only the genuine null literal.
298
try_bind_generic_arguments = true;
299
fi
300
else
301
// An actual or formal argument type is unresolved
302
// (null) - reachable in analysis mode when an
303
// expression's type has not yet been established.
304
// Treat as a moderate-quality match so resolution
305
// degrades gracefully instead of dereferencing null.
306
match = Types.MATCH.ASSIGNABLE;
307
fi
308
309
score = cast int(score) + cast int(match);
310
od
311
312
if try_bind_generic_arguments /\ score <= best_score /\ score < cast int(Types.MATCH.DIFFERENT) then
313
// if we saw any generic argument types in any of the function formal argument types
314
// then we need to try to bind them to concrete types from the corresponding actual
315
// argument types
316
317
let function_generic_argument_bindings =
318
if want_try_bind_function_generic_arguments then
319
f.try_bind_generic_arguments(location, arguments);
320
else
321
null;
322
fi;
323
324
let owner_generic_argument_bindings =
325
if want_try_bind_owner_generic_arguments then
326
f.try_bind_owner_generic_arguments(location, arguments);
327
else
328
null
329
fi;
330
331
if function_generic_argument_bindings? then
332
if function_generic_argument_bindings.is_bound then
333
actual = f.specialize_function(function_generic_argument_bindings.map, null);
334
elif needs_second_call then
335
actual = f.specialize_function(function_generic_argument_bindings.map, null);
336
score = cast int(Types.MATCH.PARTIAL);
337
else
338
score = cast int(Types.MATCH.DIFFERENT);
339
fi
340
elif owner_generic_argument_bindings? then
341
if owner_generic_argument_bindings.is_bound then
342
let specialized_owner = Symbols.GENERIC.try_create_from(location, owner_symbol, owner_generic_argument_bindings.map);
343
344
if specialized_owner? then
345
actual = specialized_owner.find_specialized_function(f);
346
else
347
score = cast int(Types.MATCH.DIFFERENT);
348
fi
349
elif needs_second_call then
350
let specialized_owner = Symbols.GENERIC.try_create_from(location, owner_symbol, owner_generic_argument_bindings.map);
351
352
if specialized_owner? then
353
actual = specialized_owner.find_specialized_function(f);
354
score = cast int(Types.MATCH.PARTIAL);
355
else
356
score = cast int(Types.MATCH.DIFFERENT);
357
fi
358
else
359
score = cast int(Types.MATCH.DIFFERENT);
360
fi
361
else
362
score = cast int(Types.MATCH.DIFFERENT);
363
fi
364
fi
365
366
if score == best_score /\ score != cast int(Types.MATCH.DIFFERENT) /\ actual? then
367
if !ambiguous_matches? then
368
ambiguous_matches = Collections.LIST[Symbols.Function]();
369
fi
370
371
if ambiguous_matches.count == 0 /\ result? then
372
ambiguous_matches.add(result);
373
fi
374
375
ambiguous_matches.add(actual);
376
377
is_ambiguous = true;
378
elif score < best_score /\ actual? then
379
if ambiguous_matches? then
380
ambiguous_matches.clear();
381
fi
382
383
is_ambiguous = false;
384
best_score = score;
385
result = actual;
386
fi
387
fi
388
od
389
390
if is_ambiguous then
391
let non_object_matches =
392
ambiguous_matches |>
393
filter(f => !f.arguments |> any(a => a.is_object));
394
395
let count = non_object_matches |> count();
396
397
if count == 1 then
398
result = non_object_matches |> only();
399
is_ambiguous = false;
400
elif count > 1 then
401
ambiguous_matches = Collections.LIST(non_object_matches);
402
fi
403
fi
404
405
// Return-type-context filter: when the caller supplied a
406
// return-type constraint (e.g. a return statement whose
407
// function returns `Tasks.TASK[int]`, or an assignment-
408
// position call's LHS type), prefer candidates whose
409
// return type is assignable to that constraint. Picks
410
// between `from_exception(ex) -> Tasks.TASK` vs
411
// `from_exception[T](ex) -> Tasks.TASK[T]` where T can
412
// bind from the constraint — the latter is the
413
// user's intent and the former would fail the return-
414
// statement's own assignability check. Runs BEFORE the
415
// non-generic filter so a generic candidate matching the
416
// constraint wins over a non-generic one that doesn't.
417
//
418
// For a generic candidate whose return type carries type
419
// variables that the constraint can pin (e.g.
420
// `Tasks.TASK[T]` against `Tasks.TASK[int]`), specialize
421
// the chosen function so its emitted return type binds T
422
// from the constraint. Without this the caller would
423
// accept the candidate as the right overload but reject
424
// its result against the constraint at the next
425
// assignability check.
426
if is_ambiguous /\ return_constraint? /\ !return_constraint.is_sentinel then
427
let constraint_matches =
428
ambiguous_matches |>
429
filter(f => RETURN_CONSTRAINT_FILTER.matches(f, return_constraint));
430
431
let count = constraint_matches |> count();
432
433
if count == 1 then
434
result = constraint_matches |> only();
435
is_ambiguous = false;
436
437
let specialized = RETURN_CONSTRAINT_FILTER.try_specialize(location, result, return_constraint);
438
if specialized? then
439
result = specialized;
440
fi
441
elif count > 1 then
442
ambiguous_matches = Collections.LIST(constraint_matches);
443
fi
444
fi
445
446
if is_ambiguous then
447
// Prefer non-generic candidates over function-generic
448
// ones (concrete `<>(int, int)` beats specialized
449
// `<>[T: struct](T, T)` for `int <> int`). A concrete
450
// candidate has no `specialized_from` link AND no
451
// function-level type-arguments; a specialized form
452
// of a function-generic has `specialized_from` set.
453
let non_generic_matches =
454
ambiguous_matches |>
455
filter(f =>
456
!f.specialized_from?
457
/\ f.generic_arguments.count == 0);
458
459
let count = non_generic_matches |> count();
460
461
if count == 1 then
462
result = non_generic_matches |> only();
463
is_ambiguous = false;
464
elif count > 1 then
465
ambiguous_matches = Collections.LIST(non_generic_matches);
466
fi
467
fi
468
469
if result? /\ !is_ambiguous then
470
// Back-feed the chosen overload's formal types as
471
// constraints to any actual whose type is an
472
// INFERRED_VARIABLE_TYPE placeholder. This is the
473
// single primitive that drives iterative inference:
474
// when a placeholder participates in overload
475
// resolution and a concrete formal wins on the other
476
// side, the formal becomes a constraint on the
477
// placeholder's origin symbol. The retry loop in
478
// COMPILE_EXPRESSIONS.visit(FUNCTION) then re-walks
479
// the body with the narrowed type. Operators (which
480
// are method calls in ghūl) flow through this path
481
// for free.
482
_propagate_chosen_match_args(result, arguments);
483
484
if needs_second_call then
485
best_score = cast int(Types.MATCH.PARTIAL);
486
fi
487
488
return OVERLOAD_RESOLVE_RESULT(result, cast Types.MATCH(best_score), needs_second_call \/ saw_delegate_target);
489
fi
490
491
if
492
arguments |> any(a => a.is_error \/ (!want_infer /\ a.is_sentinel))
493
then
494
return null;
495
fi
496
497
let tried = Collections.LIST[Symbols.Function](20);
498
499
for f in functions_to_search do
500
if f.arguments.count == arguments.count then
501
tried.add(f);
502
fi
503
od
504
505
let maybe_static mut = "";
506
507
if !want_instance then
508
maybe_static = "static ";
509
fi
510
511
if is_ambiguous then
512
_logger.error(
513
location,
514
"call is ambiguous {group.name}({arguments|}), tried {get_sorted_function_list_as_string(ambiguous_matches!)}"
515
);
516
elif tried.count > 0 then
517
_logger.error(
518
location,
519
"no {maybe_static}overload found for {group.name}({arguments|}), tried {get_sorted_function_list_as_string(tried)}"
520
);
521
else
522
_logger.error(location, "no {maybe_static}overload found for {group.name}({arguments|})");
523
fi
524
525
return null;
526
si
527
528
get_sorted_function_list_as_string(functions: Collections.Iterable[Symbols.Function]) -> string static =>
529
(functions |>
530
map(f => f.to_string()) |>
531
sort())
532
.to_string() ?? "";
533
534
// For each (formal, actual) pair on the chosen overload, push
535
// type-arg constraints into any INFERRED_VARIABLE_TYPE
536
// placeholders found on either side. The accumulator is
537
// retrieved on the next iteration's lambda arg-compile / con-
538
// structor re-walk to resolve the placeholder to a concrete
539
// type. When add_constraint returns true (a real new constraint
540
// landed), signal progress to the retry loop via
541
// mark_consumed_any.
542
_propagate_chosen_match_args(
543
chosen: Symbols.Function,
544
actual_types: Collections.List[Type]
545
) is
546
match_propagator.propagate_matches(chosen.arguments, actual_types);
547
si
548
549
_find_matches(
550
group: Symbols.FUNCTION_GROUP,
551
arguments: Collections.List[Type]
552
) -> OVERLOAD_MATCHES_RESULT?
553
is
554
if group == null \/ group.functions == null \/ arguments == null then
555
return null;
556
fi
557
558
if group.functions.count == 0 then
559
return null;
560
fi
561
562
if group.functions.count == 1 \/ arguments.count == 0 then
563
return OVERLOAD_MATCHES_RESULT(group.functions, 0, -1);
564
fi
565
566
let results = Collections.LIST[Symbols.Function]();
567
568
let best_score mut = cast int(Types.MATCH.DIFFERENT) * arguments.count;
569
let best_index mut = -1;
570
571
for f in group.functions do
572
if f.arguments.count >= arguments.count then
573
let score mut = cast int(Types.MATCH.SAME);
574
575
for i in 0..arguments.count do
576
let match: Types.MATCH mut;
577
578
match = f.arguments[i].compare(arguments[i]);
579
580
if match == Types.MATCH.DIFFERENT then
581
score = cast int(Types.MATCH.DIFFERENT);
582
fi
583
584
score = score + cast int(match);
585
od
586
587
results.add(f);
588
589
if score < best_score then
590
best_score = score;
591
best_index = results.count - 1;
592
fi
593
fi
594
od
595
596
return
597
OVERLOAD_MATCHES_RESULT(
598
results,
599
best_index,
600
-1
601
);
602
si
603
si
604
si