Skip to content
← Back

src/syntax/process/location_refresh.ghul

1
namespace Syntax.Process is
2
use Collections.LIST
3
4
use Source
5
use Trees
6
7
// Reconciles the retained AST of an interface-preserving incremental
8
// EDIT against the freshly-parsed donor. An interface-preserving EDIT
9
// keeps the retained interface nodes (their symbols stay valid) but
10
// those nodes carry pre-edit locations — a body edit, or reformatting
11
// around it, moves them. This visitor copies the donor parse's correct
12
// locations onto the retained nodes, and records the pre-edit ->
13
// post-edit correspondence of every node it touches so the analyser's
14
// location side-tables can be reconciled the same way.
15
//
16
// It harvests from the donor in walk order, then applies to the
17
// retained tree in the same walk order. Body interiors are not
18
// descended (`pre` returns true for the body node types): after the
19
// splice the retained AST's bodies *are* the donor's body nodes,
20
// already current, and skipping them keeps the harvest and apply walks
21
// counting the same nodes. `is_consistent` is false if the two walks
22
// disagreed — on node count, or on a node's type — and the caller must
23
// then fall back to a full rebuild.
24
25
// One harvested node: its location and runtime type, plus the other
26
// locations that node owns and that the analyser's side tables key on.
27
// The type (held as `object` — the result of `get_type()`, compared by
28
// identity) is checked against the retained node on apply, so a
29
// structural divergence between the two parses outside an edited body —
30
// meaning the interface classification was wrong — is caught at the
31
// first mismatching node, not just by an end-of-walk count.
32
//
33
// Reconciling `location` alone is not enough. A symbol use is recorded
34
// at the location of the *name* it occurs at, which for a qualified name
35
// is only its rightmost segment (`right_location`), and for a named or
36
// generic type expression belongs to the identifier the type expression
37
// holds rather than to the type expression itself. A generic type
38
// expression does not walk that identifier at all, so it is never
39
// transferred on its own. Any such location left untranslated keeps its
40
// pre-edit position for as long as the retained tree lives.
41
struct TRANSFER_POINT is
42
location: LOCATION public
43
right_location: LOCATION? public
44
name_location: LOCATION? public
45
name_right_location: LOCATION? public
46
kind: object public
47
48
init(
49
location: LOCATION,
50
right_location: LOCATION?,
51
name_location: LOCATION?,
52
name_right_location: LOCATION?,
53
kind: object
54
) is
55
self.location = location
56
self.right_location = right_location
57
self.name_location = name_location
58
self.name_right_location = name_right_location
59
self.kind = kind
60
si
61
si
62
63
class LOCATION_TRANSFER: Visitor is
64
_harvesting: bool
65
_points: LIST[TRANSFER_POINT]
66
_index: int
67
_structurally_consistent: bool
68
_correspondence: LOCATION_CORRESPONDENCE
69
70
init() is
71
super.init()
72
_points = LIST[TRANSFER_POINT]()
73
_correspondence = LOCATION_CORRESPONDENCE()
74
si
75
76
// The pre-edit -> post-edit location of every retained interface
77
// node, built during apply_to. Valid only when is_consistent.
78
correspondence: LOCATION_CORRESPONDENCE => _correspondence
79
80
harvest(root: Node) is
81
_harvesting = true
82
_points = LIST[TRANSFER_POINT]()
83
root.walk(self)
84
si
85
86
apply_to(root: Node) is
87
_harvesting = false
88
_index = 0
89
_structurally_consistent = true
90
_correspondence = LOCATION_CORRESPONDENCE()
91
root.walk(self)
92
si
93
94
is_consistent: bool =>
95
!_harvesting /\ _index == _points.count /\ _structurally_consistent
96
97
transfer(node: Node) is
98
if _harvesting then
99
_points.add(
100
TRANSFER_POINT(
101
node.location,
102
_right_location_of(node),
103
_name_of(node)?.location,
104
_name_of(node)?.right_location,
105
node.get_type()
106
)
107
)
108
109
return
110
fi
111
112
if _index >= _points.count then
113
_structurally_consistent = false
114
_index = _index + 1
115
return
116
fi
117
118
let point = _points[_index]
119
_index = _index + 1
120
121
if node.get_type() != point.kind then
122
_structurally_consistent = false
123
fi
124
125
// record old -> new before overwriting the location
126
_correspondence.add(node.location, point.location)
127
128
// Each of these is recorded and then written back, so the next
129
// edit's correspondence is keyed on where this one left the entry.
130
// Recording without writing back keys every edit on the original
131
// parse position, which matches the side table once and misses
132
// from the second edit on.
133
if let new_right = point.right_location then
134
if isa Identifiers.QUALIFIED(node) then
135
let qualified = cast Identifiers.QUALIFIED(node)
136
137
_correspondence.add(qualified.right_location, new_right)
138
139
qualified.right_location = new_right
140
fi
141
fi
142
143
if let name = _name_of(node) then
144
if let new_name_location = point.name_location then
145
_correspondence.add(name.location, new_name_location)
146
147
if let new_name_right = point.name_right_location then
148
if isa Identifiers.QUALIFIED(name) then
149
let qualified_name = cast Identifiers.QUALIFIED(name)
150
151
_correspondence.add(qualified_name.right_location, new_name_right)
152
153
qualified_name.right_location = new_name_right
154
fi
155
fi
156
157
name.location = new_name_location
158
fi
159
fi
160
161
node.location = point.location
162
si
163
164
// The rightmost segment of a qualified name, which is where a use of
165
// the name it resolves to is recorded. Null for every other node.
166
_right_location_of(node: Node) -> LOCATION? is
167
if isa Identifiers.QUALIFIED(node) then
168
return (cast Identifiers.QUALIFIED(node)).right_location
169
fi
170
171
return null
172
si
173
174
// The identifier a named or generic type expression is written with.
175
// A generic type expression does not walk it, so it reaches this
176
// reconciliation only through the type expression that holds it.
177
_name_of(node: Node) -> Identifiers.Identifier? is
178
if isa TypeExpressions.NAMED(node) then
179
return (cast TypeExpressions.NAMED(node)).name
180
fi
181
182
return null
183
si
184
185
// Body interiors are donor nodes already in current coordinates —
186
// skip them so harvest and apply walk the same node set.
187
pre(block: Bodies.BLOCK) -> bool => true
188
pre(expression: Bodies.EXPRESSION) -> bool => true
189
pre(innate_body: Bodies.INNATE) -> bool => true
190
pre(null_body: Bodies.NULL) -> bool => true
191
192
visit(identifier: Identifiers.Identifier) is transfer(identifier); si
193
visit(identifier: Identifiers.QUALIFIED) is transfer(identifier); si
194
195
visit(modifier: Modifiers.Modifier) is transfer(modifier); si
196
visit(modifiers: Modifiers.LIST) is transfer(modifiers); si
197
visit(pragma: Pragmas.PRAGMA) is transfer(pragma); si
198
199
visit(definition: Definitions.Definition) is transfer(definition); si
200
visit(definitions: Definitions.LIST) is transfer(definitions); si
201
visit(pragma: Definitions.PRAGMA) is transfer(pragma); si
202
visit(`namespace: Definitions.NAMESPACE) is transfer(`namespace); si
203
visit(`use: Definitions.USE) is transfer(`use); si
204
visit(`class: Definitions.CLASS) is transfer(`class); si
205
visit(`trait: Definitions.TRAIT) is transfer(`trait); si
206
visit(`struct: Definitions.STRUCT) is transfer(`struct); si
207
visit(`union: Definitions.UNION) is transfer(`union); si
208
visit(variant: Definitions.VARIANT) is transfer(variant); si
209
visit(`enum: Definitions.ENUM) is transfer(`enum); si
210
visit(enum_member: Definitions.ENUM_MEMBER) is transfer(enum_member); si
211
visit(function: Definitions.FUNCTION) is transfer(function); si
212
visit(functions: Definitions.FUNCTION_GROUP) is transfer(functions); si
213
visit(property: Definitions.PROPERTY) is transfer(property); si
214
visit(indexer: Definitions.INDEXER) is transfer(indexer); si
215
216
visit(variable: Variables.VARIABLE) is transfer(variable); si
217
visit(variables: Variables.LIST) is transfer(variables); si
218
visit(left: Trees.Variables.SIMPLE_VARIABLE_LEFT) is transfer(left); si
219
visit(destructure_left: Trees.Variables.DESTRUCTURING_VARIABLE_LEFT) is transfer(destructure_left); si
220
visit(literal_leaf: Trees.Variables.LITERAL_VARIABLE_LEFT) is transfer(literal_leaf); si
221
222
visit(type_expression: TypeExpressions.TypeExpression) is transfer(type_expression); si
223
visit(type_expression: TypeExpressions.INFER) is transfer(type_expression); si
224
visit(structured: TypeExpressions.Structured) is transfer(structured); si
225
visit(array: TypeExpressions.ARRAY_) is transfer(array); si
226
visit(pointer: TypeExpressions.POINTER) is transfer(pointer); si
227
visit(optional: TypeExpressions.OPTIONAL) is transfer(optional); si
228
visit(reference: TypeExpressions.REFERENCE) is transfer(reference); si
229
visit(member: TypeExpressions.MEMBER) is transfer(member); si
230
visit(named: TypeExpressions.NAMED) is transfer(named); si
231
visit(types: TypeExpressions.LIST) is transfer(types); si
232
visit(generic: TypeExpressions.GENERIC) is transfer(generic); si
233
visit(function: TypeExpressions.FUNCTION) is transfer(function); si
234
visit(functions: TypeExpressions.FUNCTION_GROUP) is transfer(functions); si
235
visit(tuple: TypeExpressions.TUPLE) is transfer(tuple); si
236
visit(element: TypeExpressions.NAMED_TUPLE_ELEMENT) is transfer(element); si
237
visit(constraint: TypeExpressions.TYPE_PARAMETER_CONSTRAINT) is transfer(constraint); si
238
visit(element: TypeExpressions.UNDEFINED) is transfer(element); si
239
240
visit(expression: Expressions.Expression) is transfer(expression); si
241
visit(identifier: Expressions.IDENTIFIER) is transfer(identifier); si
242
visit(literal: Expressions.Literals.Literal) is transfer(literal); si
243
visit(`string: Expressions.Literals.STRING) is transfer(`string); si
244
visit(interpolation: Expressions.STRING_INTERPOLATION) is transfer(interpolation); si
245
visit(integer: Expressions.Literals.INTEGER) is transfer(integer); si
246
visit(float: Expressions.Literals.FLOAT) is transfer(float); si
247
visit(character: Expressions.Literals.CHARACTER) is transfer(character); si
248
visit(boolean: Expressions.Literals.BOOLEAN) is transfer(boolean); si
249
visit(variable: Expressions.VARIABLE) is transfer(variable); si
250
visit(variable: Expressions.TUPLE_ELEMENT) is transfer(variable); si
251
visit(none: Expressions.Literals.NONE) is transfer(none); si
252
visit(`null: Expressions.NULL) is transfer(`null); si
253
visit(`self: Expressions.SELF) is transfer(`self); si
254
visit(`super: Expressions.SUPER) is transfer(`super); si
255
visit(construct: Expressions.CONSTRUCT) is transfer(construct); si
256
visit(`cast: Expressions.CAST) is transfer(`cast); si
257
visit(`isa: Expressions.ISA) is transfer(`isa); si
258
visit(`isa: Expressions.TYPEOF) is transfer(`isa); si
259
visit(`default: Expressions.DEFAULT) is transfer(`default); si
260
visit(function: Expressions.FUNCTION) is transfer(function); si
261
visit(recurse: Expressions.RECURSE) is transfer(recurse); si
262
visit(tuple: Expressions.TUPLE) is transfer(tuple); si
263
visit(sequence: Expressions.SEQUENCE) is transfer(sequence); si
264
visit(list: Expressions.LIST) is transfer(list); si
265
visit(call: Expressions.CALL) is transfer(call); si
266
visit(member: Expressions.MEMBER) is transfer(member); si
267
visit(ambiguous_expression: Expressions.AMBIGUOUS_EXPRESSION) is transfer(ambiguous_expression); si
268
visit(ambiguous_expression: Expressions.GENERIC_APPLICATION) is transfer(ambiguous_expression); si
269
visit(index: Expressions.INDEX) is transfer(index); si
270
visit(has_value: Expressions.HAS_VALUE) is transfer(has_value); si
271
visit(unwrap: Expressions.UNWRAP) is transfer(unwrap); si
272
visit(reference: Expressions.REFERENCE) is transfer(reference); si
273
visit(unary: Expressions.UNARY) is transfer(unary); si
274
visit(binary: Expressions.BINARY) is transfer(binary); si
275
visit(statement: Expressions.STATEMENT) is transfer(statement); si
276
visit(statement: Expressions.LET_IN) is transfer(statement); si
277
visit(block: Expressions.VAL_BLOCK) is transfer(block); si
278
visit(assert_in: Expressions.ASSERT_IN) is transfer(assert_in); si
279
280
visit(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is transfer(left); si
281
visit(destructure_left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) is transfer(destructure_left); si
282
283
visit(statement: Statements.Statement) is transfer(statement); si
284
visit(statements: Statements.LIST) is transfer(statements); si
285
visit(f: Statements.FUNCTION) is transfer(f); si
286
visit(l: Statements.LET) is transfer(l); si
287
visit(assign: Statements.ASSIGNMENT) is transfer(assign); si
288
visit(expression: Statements.EXPRESSION) is transfer(expression); si
289
visit(`return: Statements.RETURN) is transfer(`return); si
290
visit(`throw: Statements.THROW) is transfer(`throw); si
291
visit(assert__: Statements.ASSERT) is transfer(assert__); si
292
visit(`if: Statements.IF) is transfer(`if); si
293
visit(if_branch: Statements.IF_BRANCH) is transfer(if_branch); si
294
visit(`case: Statements.CASE) is transfer(`case); si
295
visit(case_match: Statements.CASE_MATCH) is transfer(case_match); si
296
visit(`try: Statements.TRY) is transfer(`try); si
297
visit(`catch: Statements.CATCH) is transfer(`catch); si
298
visit(`do: Statements.DO) is transfer(`do); si
299
visit(`for: Statements.FOR) is transfer(`for); si
300
visit(labelled: Statements.LABELLED) is transfer(labelled); si
301
visit(`break: Statements.BREAK) is transfer(`break); si
302
visit(`continue: Statements.CONTINUE) is transfer(`continue); si
303
visit(pragma: Statements.PRAGMA) is transfer(pragma); si
304
305
visit(expression: Bodies.EXPRESSION) is transfer(expression); si
306
visit(block: Bodies.BLOCK) is transfer(block); si
307
visit(block: Bodies.NULL) is transfer(block); si
308
visit(block: Bodies.INNATE) is transfer(block); si
309
si
310
311
// Copies the donor parse's locations onto the structurally-identical
312
// retained AST and returns the pre-edit -> post-edit correspondence of
313
// every retained interface node. Returns null if the two walks
314
// disagreed — on node count or node type — and the caller must then
315
// fall back to a full rebuild.
316
class LOCATION_REFRESH is
317
apply(retained_root: Trees.Node, donor_root: Trees.Node) -> Source.LOCATION_CORRESPONDENCE? static is
318
let transfer = LOCATION_TRANSFER()
319
320
transfer.harvest(donor_root)
321
transfer.apply_to(retained_root)
322
323
if !transfer.is_consistent then
324
return null
325
fi
326
327
return transfer.correspondence
328
si
329
si
330
si