Skip to content
← Back

src/syntax/process/argument_pack_sites.ghul

1
namespace Syntax.Process is
2
use Logging
3
use Source.LOCATION
4
use Ghul.Pipes
5
6
// What a `..` marker on a formal's declared type asks for.
7
enum PackSite is
8
NONE,
9
CALLABLE,
10
SPREAD,
11
si
12
13
// Collects every `..` argument-pack marker written inside one type
14
// expression, so a formal's declared type can be checked as a whole.
15
class ARGUMENT_PACK_MARKER_FINDER: Visitor is
16
marked: Collections.LIST[Trees.TypeExpressions.NAMED]
17
18
init() is
19
super.init()
20
21
marked = Collections.LIST[Trees.TypeExpressions.NAMED]()
22
si
23
24
visit(named: Trees.TypeExpressions.NAMED) is
25
if named.is_argument_pack then
26
marked.add(named)
27
fi
28
si
29
si
30
31
// Where a `..` marker is allowed on a formal argument's type, and
32
// what the formal it is written on means.
33
//
34
// `[T..]` declares that `T` stands for the arguments of a call. A
35
// formal opts into one of that parameter's behaviours by marking its
36
// own type: `f: T.. -> U` takes the arguments as an N-ary function,
37
// which the call-site adaptation then fits to it. A plain `T` is the
38
// tuple.
39
//
40
// The marker can sit on the parameter of any function type along the
41
// formal's return spine, not only the outermost - `f: X -> T.. -> U`
42
// asks for the N-ary function the caller's own lambda returns. The
43
// depth is how many returns in the marked function type sits, and is
44
// what tells the adaptation how far into the actual to reach.
45
class ARGUMENT_PACK_SITES(_logger: Logger) is
46
super()
47
48
// The formal's declared type opts into N-ary adaptation, and
49
// reports every marker written anywhere else in that type.
50
is_spread_formal(type_expression: Trees.TypeExpressions.TypeExpression) -> bool =>
51
classify(type_expression).site =~ PackSite.CALLABLE
52
53
// A declared return type opts into the same adaptation, with
54
// the marker read at its own spine's positions. The value
55
// reading has no return counterpart - nothing spreads a call's
56
// arguments into a result - so a marker on the type itself is
57
// reported rather than read.
58
classify_return(type_expression: Trees.TypeExpressions.TypeExpression) -> (site: PackSite, depth: int) =>
59
_classify(
60
type_expression,
61
"a return type can only spread an argument pack into the last parameter of a function type it returns",
62
false)
63
64
classify(type_expression: Trees.TypeExpressions.TypeExpression) -> (site: PackSite, depth: int) =>
65
_classify(
66
type_expression,
67
"an argument pack can only be spread as a formal argument's own type or into the last parameter of a function type it takes or returns",
68
true)
69
70
_classify(
71
type_expression: Trees.TypeExpressions.TypeExpression,
72
misplaced: string,
73
permit_value_marker: bool
74
) -> (site: PackSite, depth: int) is
75
let permitted_callable = Collections.LIST[Trees.TypeExpressions.NAMED?]()
76
77
// Parameters of a function type the marker could have been
78
// written on, had they been its last.
79
let not_last = Collections.LIST[Trees.TypeExpressions.TypeExpression]()
80
81
let spine mut = cast Trees.TypeExpressions.FUNCTION?(type_expression)
82
83
while spine? do
84
// The pack is the last parameter, whatever comes before it:
85
// `(A, T..) -> A` takes its first argument as it is and the
86
// rest as the pack.
87
let count = spine.arguments.elements.count
88
89
let parameter =
90
if count >= 1 then
91
cast Trees.TypeExpressions.NAMED?(spine.arguments.elements[count - 1])
92
else
93
null
94
fi
95
96
// A depth is a position in the spine whether or not its
97
// parameter can carry a marker, so an unmarkable one
98
// still occupies its place in the list.
99
permitted_callable.add(parameter)
100
101
for i in 0..count - 1 do
102
not_last.add(spine.arguments.elements[i])
103
od
104
105
spine = cast Trees.TypeExpressions.FUNCTION?(spine.result)
106
od
107
108
let permitted_value = cast Trees.TypeExpressions.NAMED?(type_expression)
109
110
let finder = ARGUMENT_PACK_MARKER_FINDER()
111
112
type_expression.walk(finder)
113
114
let accepted mut = PackSite.NONE
115
let accepted_depth mut = 0
116
117
for marked in finder.marked do
118
let depth = _depth_of(permitted_callable, marked)
119
120
if depth >= 0 then
121
if _names_a_pack(marked) then
122
accepted = PackSite.CALLABLE
123
accepted_depth = depth
124
fi
125
126
continue
127
fi
128
129
if permitted_value? /\ marked == permitted_value then
130
if permit_value_marker /\ _names_a_pack(marked) then
131
accepted = PackSite.SPREAD
132
accepted_depth = 0
133
elif !permit_value_marker then
134
_logger.error(marked.location, misplaced)
135
fi
136
137
continue
138
fi
139
140
if not_last |> any(parameter => parameter == marked) then
141
_logger.error(marked.location, "an argument pack has to be the last parameter of the function type it is spread into")
142
else
143
_logger.error(marked.location, misplaced)
144
fi
145
od
146
147
// A function type that takes an argument pack as its last
148
// parameter, with no marker written anywhere on it, takes the
149
// pack as one tuple. That is a legal thing to ask for, and
150
// almost never what was meant: every call written with the
151
// pack spread out then fails, far from this declaration.
152
if finder.marked.count == 0 then
153
for parameter in permitted_callable do
154
if let unmarked = parameter /\ unmarked.type? /\ unmarked.type.symbol.is_argument_pack then
155
_logger.warn(
156
unmarked.location,
157
"unmarked-pack-slot",
158
"{unmarked.type} is an argument pack but this function type takes it as one tuple",
159
unmarked.location,
160
"help: write {unmarked.type}.. to take it spread out"
161
)
162
fi
163
od
164
fi
165
166
return (site = accepted, depth = accepted_depth)
167
si
168
169
// How many returns into the formal's own type the marked
170
// parameter sits, and negative when it is not one of them.
171
_depth_of(
172
permitted: Collections.List[Trees.TypeExpressions.NAMED?],
173
marked: Trees.TypeExpressions.NAMED
174
) -> int is
175
for i in 0..permitted.count do
176
if let candidate = permitted[i] /\ candidate == marked then
177
return i
178
fi
179
od
180
181
return -1
182
si
183
184
// The marked name has to be a type parameter declared `[T..]`.
185
// Without that declaration the marker says nothing about what the
186
// parameter stands for, so the formal has nothing to spread.
187
_names_a_pack(marked: Trees.TypeExpressions.NAMED) -> bool is
188
if let type = marked.type then
189
if type.symbol.is_argument_pack then
190
return true
191
fi
192
193
_logger.error(
194
marked.location,
195
"{type} is not an argument pack: declare it [{type}..] to spread it here"
196
)
197
fi
198
199
return false
200
si
201
si
202
si