Skip to content
← Back

src/semantic/inference_trace.ghul

1
namespace Semantic is
2
use Source.LOCATION
3
4
use Collections.LIST
5
6
// A line-oriented trace of what type inference does inside each
7
// function body, written when --trace-inference names stderr,
8
// --trace-inference-file names a file, or GHUL_TRACE_INFERENCE is set
9
// (to a path, or to `-` or nothing for stderr). With tracing off
10
// nothing is formatted and nothing is written.
11
//
12
// Each line is an event name followed by tab-separated fields. The
13
// second field places the event: `<body>.<walk>` inside a body's
14
// outer walk, `<body>` for a body boundary, `-` outside any body.
15
// Body numbers count bodies in the order compile-expressions reaches
16
// them, so two compilers given the same input produce traces that
17
// can be diffed line by line.
18
//
19
// body-begin a function body's walks start
20
// walk-begin one outer walk of the body starts
21
// mint a placeholder is created for a type argument slot
22
// lower-bound a lower bound is offered to an inference variable
23
// upper-bound an upper bound is offered to an inference variable
24
// constraint an operation constraint is offered to a placeholder
25
// rewalk a local site rolls back and walks a subtree again
26
// error an error is reported, rolled back later or not
27
// obligation a rule waits on a placeholder instead of firing
28
// walk-end the walk finished: clean, and whether it made progress
29
// body-end the body's walks stopped, and why
30
class INFERENCE_TRACE is
31
enabled: bool static
32
33
_writer: IO.TextWriter? static
34
35
_bodies: LIST[int] static
36
_walks: LIST[int] static
37
_next_body: int static
38
39
init() static is
40
_bodies = LIST[int]()
41
_walks = LIST[int]()
42
43
let target = System.Environment.get_environment_variable("GHUL_TRACE_INFERENCE")
44
45
if target? then
46
enable(target)
47
fi
48
si
49
50
// `-` or an empty target writes to stderr; anything else is a
51
// file path, truncated so one build gives one trace.
52
enable(target: string) static is
53
if target =~ "" \/ target =~ "-" then
54
_writer = IO.Std.error
55
else
56
let writer = IO.StreamWriter(target, false)
57
writer.auto_flush = true
58
_writer = writer
59
fi
60
61
Logging.DIAGNOSTIC_TRACE.on_error = (location: LOCATION, message: string) -> void => _write("error\t{_where()}\tat={location}\t{message}")
62
63
enabled = true
64
si
65
66
begin_body(function: Symbols.Function?, location: LOCATION) -> int static is
67
if !enabled then
68
return -1
69
fi
70
71
let id = _next_body
72
_next_body = _next_body + 1
73
74
_bodies.add(id)
75
_walks.add(0)
76
77
_write("body-begin\t{id}\tfunction={_function_name(function)}\tat={location}")
78
79
return id
80
si
81
82
begin_walk(walk: int) static is
83
if !enabled \/ _walks.count == 0 then
84
return
85
fi
86
87
_walks[_walks.count - 1] = walk
88
89
_write("walk-begin\t{_where()}")
90
si
91
92
end_walk(clean: bool, progress: bool) static is
93
if !enabled \/ _walks.count == 0 then
94
return
95
fi
96
97
_write("walk-end\t{_where()}\tclean={clean}\tprogress={progress}")
98
si
99
100
// `limit` is the most walks a body is allowed: stopping there
101
// while still making progress is giving up rather than settling.
102
end_body(id: int, walks: int, limit: int, clean: bool, progress: bool) static is
103
if !enabled \/ _bodies.count == 0 then
104
return
105
fi
106
107
// A body whose walk threw leaves its entry behind; drop it.
108
while _bodies.count > 1 /\ _bodies[_bodies.count - 1] != id do
109
_pop()
110
od
111
112
let outcome =
113
if clean then "clean"
114
elif !progress then "errors-without-progress"
115
elif walks >= limit then "gave-up"
116
else "stopped"
117
fi
118
119
_write("body-end\t{id}\twalks={walks}\toutcome={outcome}")
120
121
_pop()
122
si
123
124
mint(container: Symbols.Symbol, slot: string, phantom: Symbols.Variable, location: LOCATION, function: Symbols.Function?) static is
125
if !enabled then
126
return
127
fi
128
129
_write("mint\t{_where()}\tcontainer={container.qualified_name}\tslot={slot}\tphantom={phantom.name}\tat={location}\tin={_function_name(function)}")
130
si
131
132
rewalk(site: string, kind: string) static is
133
if !enabled then
134
return
135
fi
136
137
_write("rewalk\t{_where()}\tsite={site}\tkind={kind}")
138
si
139
140
obligation(rule: string, awaiting: Types.Type, location: Source.LOCATION?) static is
141
if !enabled then
142
return
143
fi
144
145
let target =
146
if let placeholder: Types.INFERRED_VARIABLE_TYPE = awaiting then
147
placeholder.origin.name
148
else
149
awaiting.to_string()
150
fi
151
152
_write("obligation\t{_where()}\trule={rule}\ttarget={target}\tat={location}")
153
si
154
155
add_lower_bound(site: string, target: Symbols.Variable, bound: Types.Type?) -> bool static is
156
let added = target.add_lower_bound(bound)
157
158
if added then
159
OBLIGATIONS.note_learned()
160
fi
161
162
if enabled then
163
_offered("lower-bound", site, target, _type_name(bound), added)
164
fi
165
166
return added
167
si
168
169
add_upper_bound(site: string, target: Symbols.Variable, bound: Types.Type?) -> bool static is
170
let added = target.add_upper_bound(bound)
171
172
if added then
173
OBLIGATIONS.note_learned()
174
fi
175
176
if enabled then
177
_offered("upper-bound", site, target, _type_name(bound), added)
178
fi
179
180
return added
181
si
182
183
add_constraint(site: string, target: Symbols.Variable, constraint: Constraint?) -> bool static is
184
let added = target.add_constraint(constraint)
185
186
if added then
187
OBLIGATIONS.note_learned()
188
fi
189
190
if enabled then
191
_offered("constraint", site, target, if constraint? then constraint.to_string() else "null" fi, added)
192
fi
193
194
return added
195
si
196
197
_offered(event: string, site: string, target: Symbols.Variable, what: string, added: bool) static is
198
_write("{event}\t{_where()}\tsite={site}\ttarget={target.name}\ttarget-at={target.location}\twhat={what}\tadded={added}")
199
si
200
201
_where() -> string static =>
202
if _bodies.count == 0 then
203
"-"
204
else
205
"{_bodies[_bodies.count - 1]}.{_walks[_walks.count - 1]}"
206
fi
207
208
_pop() static is
209
_bodies.remove_at(_bodies.count - 1)
210
_walks.remove_at(_walks.count - 1)
211
si
212
213
_function_name(function: Symbols.Function?) -> string static =>
214
if function? then function.qualified_name else "-" fi
215
216
_type_name(type: Types.Type?) -> string static =>
217
if type? then "{type}" else "null" fi
218
219
_write(line: string) static is
220
if let writer = _writer then
221
writer.write_line(line)
222
fi
223
si
224
si
225
si