Skip to content
← Back

src/semantic/candidate_specificity.ghul

1
namespace Semantic is
2
use Ghul.Pipes
3
4
use Types.Type
5
6
// Last-resort tie-break for candidates the earlier filters left
7
// tied: prefer the one whose formals sit furthest down the
8
// inheritance graph, which is the one that widened the actual
9
// least. `Type.depth` is the same metric the least-upper-bound
10
// map ranks by, memoised per symbol.
11
class CANDIDATE_SPECIFICITY is
12
pick(
13
candidates: Collections.List[Symbols.Function],
14
arguments: Collections.List[Type]
15
) -> Symbols.Function? static is
16
if candidates.count < 2 \/ !can_rank(arguments) then
17
return null
18
fi
19
20
let best mut = candidates[0]
21
22
for i in 1..candidates.count do
23
if dominates(candidates[i].arguments, best.arguments) then
24
best = candidates[i]
25
fi
26
od
27
28
for c in candidates do
29
if c !~ best /\ !dominates(best.arguments, c.arguments) then
30
return null
31
fi
32
od
33
34
return best
35
si
36
37
// An ambiguity whose cause is an actual with no type yet - a
38
// bare `_`, an untargeted `cast(..)`, an inference placeholder
39
// - is not one specificity can settle: every candidate matched
40
// that position vacuously, and choosing between them would
41
// answer a question the call has not asked yet.
42
@suppress("presence-test-non-optional")
43
can_rank(arguments: Collections.List[Type]) -> bool static =>
44
!(arguments |>
45
any(a => !a? \/ a.is_sentinel \/ a.is_error \/ a.contains_inferred))
46
47
// Dominance rather than a summed score: `a` has to be at least
48
// as deep in every position and strictly deeper in one, so
49
// being nearer in one argument never pays for being further
50
// away in another. Candidates that trade off against each
51
// other stay ambiguous, as they were before this step existed.
52
dominates(a: Collections.List[Type], b: Collections.List[Type]) -> bool static is
53
if a.count != b.count \/ a.count == 0 then
54
return false
55
fi
56
57
let saw_deeper mut = false
58
59
for i in 0..a.count do
60
let a_arg = a[i]
61
let b_arg = b[i]
62
63
@suppress("presence-test-non-optional")
64
if !a_arg? \/ !b_arg? \/ a_arg.is_wild \/ b_arg.is_wild then
65
return false
66
fi
67
68
let ranked = rank(a_arg, b_arg)
69
70
if ranked < 0 then
71
return false
72
elif ranked > 0 then
73
saw_deeper = true
74
fi
75
od
76
77
return saw_deeper
78
si
79
80
// How `a` ranks against `b`: positive when `a` sits further
81
// down, negative when `b` does, zero when neither does or the
82
// two cannot be ranked against each other.
83
//
84
// Two instantiations of one generic have the same depth - the
85
// depth of the type they instantiate - so a formal taking
86
// `Iterable[CAT]` ranks level with one taking `Iterable[Animal]`
87
// until their arguments are compared. Comparing them is what
88
// lets a candidate whose formals differ only inside a generic
89
// be picked, and a function type is the case that reaches this
90
// most often, since every one of them is an instantiation of
91
// the same type.
92
rank(a: Type, b: Type) -> int static is
93
if a.depth != b.depth then
94
return if a.depth > b.depth then 1 else -1 fi
95
fi
96
97
// The instantiations are distinct symbols, so what has to
98
// agree is the type they instantiate.
99
if a.symbol.root_unspecialized_symbol != b.symbol.root_unspecialized_symbol then
100
return 0
101
fi
102
103
let a_arguments = a.arguments
104
let b_arguments = b.arguments
105
106
if
107
a_arguments.count == 0 \/
108
a_arguments.count != b_arguments.count
109
then
110
return 0
111
fi
112
113
// The arguments have to agree on a direction, for the same
114
// reason the formals do: an argument that ranks deeper does
115
// not pay for a sibling that ranks shallower.
116
let result mut = 0
117
118
for i in 0..a_arguments.count do
119
let a_argument = a_arguments[i]
120
let b_argument = b_arguments[i]
121
122
@suppress("presence-test-non-optional")
123
if
124
!a_argument? \/ !b_argument? \/
125
a_argument.is_wild \/ b_argument.is_wild
126
then
127
return 0
128
fi
129
130
let ranked = rank(a_argument, b_argument)
131
132
if ranked != 0 then
133
if result != 0 /\ result != ranked then
134
return 0
135
fi
136
137
result = ranked
138
fi
139
od
140
141
return result
142
si
143
si
144
si