Skip to content
← Back

src/syntax/process/mark_boxed_locals.ghul

1
namespace Syntax.Process is
2
use IO.Std
3
4
use Logging
5
6
use Semantic.Symbols.LOCAL_VARIABLE
7
8
// Pre-`compile-expressions` analysis pass: walks the AST,
9
// resolves every IDENTIFIER reference and ASSIGNMENT LHS,
10
// marks each `LOCAL_VARIABLE` it sees as captured (if
11
// referenced from inside an enclosing function — i.e. from
12
// a closure body) and/or reassigned (if it appears on the
13
// LHS of an assignment). At end of apply, sweeps the set of
14
// touched locals and sets `is_boxed = is_captured /\
15
// is_reassigned`, so that subsequent `symbol_loader`-driven
16
// IR emission switches to `Ghul.BOX[T]`-backed storage.
17
//
18
// The capture and reassignment marking runs on every build:
19
// flow narrowing forms no facts on a local a closure body can
20
// rewrite, and that question has to be answered the same way
21
// in the editor as in a batch build. Deriving `is_boxed` from
22
// those flags is IL-bound, because boxing is a code-generation
23
// strategy and assignability of a captured local is keyed on
24
// its mut marker alone.
25
class MARK_BOXED_LOCALS: ScopedVisitor is
26
_logger: Logger
27
_touched: Collections.SET[LOCAL_VARIABLE]
28
29
init(
30
logger: Logger,
31
symbol_table: Semantic.SYMBOL_TABLE,
32
namespaces: Semantic.NAMESPACES
33
)
34
is
35
super.init(logger, symbol_table, namespaces)
36
37
_logger = logger
38
_touched = Collections.SET[LOCAL_VARIABLE]()
39
si
40
41
apply(root: Trees.Node, want_boxing: bool) is
42
begin_marking()
43
44
root.walk(self)
45
46
end_marking(want_boxing)
47
si
48
49
// The marking split from `apply` so the incremental analysis
50
// paths, which drive a pass over an edited subtree rather than
51
// calling its `apply`, can bracket their own walk with it.
52
begin_marking() is
53
_touched.clear()
54
si
55
56
end_marking(want_boxing: bool) is
57
// Sweep: a local is boxed iff (a) it was declared
58
// with the `mut` modifier, (b) it's captured from
59
// inside an enclosing closure body, and (c) it's
60
// assigned anywhere. Plain captured immutable
61
// locals stay value-captured (and any rogue
62
// assignment hits the captured-assignment diagnostic
63
// upstream). Plain mutable-but-not-captured locals
64
// stay on the stack.
65
if want_boxing then
66
for local in _touched do
67
if
68
local.is_mutable_marked /\
69
local.is_captured /\
70
local.is_reassigned
71
then
72
local.is_boxed = true
73
fi
74
od
75
fi
76
77
_touched.clear()
78
si
79
80
// Identifier-as-expression reference. If it resolves to
81
// a LOCAL_VARIABLE owned by a function outside our
82
// current function context, this is a capture.
83
visit(identifier: Trees.Expressions.IDENTIFIER) is
84
85
let symbol = try_find(identifier.identifier)
86
87
if !symbol? \/ !isa LOCAL_VARIABLE(symbol) then
88
return
89
fi
90
91
let local = cast LOCAL_VARIABLE(symbol)
92
let function = current_function
93
94
if !function? then
95
return
96
fi
97
98
_touched.add(local)
99
100
if local.owner != function then
101
local.is_captured = true
102
fi
103
si
104
105
// Reassignment of a local: LHS resolves to a
106
// LOCAL_VARIABLE. Field assignments (`obj.field = v`)
107
// and the like get filtered by the LOCAL_VARIABLE cast.
108
visit(assignment: Trees.Statements.ASSIGNMENT) is
109
let targets = Collections.LIST[Trees.Expressions.Expression]()
110
assignment.left.get_names_into(targets)
111
112
for target in targets do
113
if !isa Trees.Expressions.IDENTIFIER(target) then
114
continue
115
fi
116
117
let symbol = try_find(target.identifier)
118
119
if !symbol? \/ !isa LOCAL_VARIABLE(symbol) then
120
continue
121
fi
122
123
_touched.add(symbol)
124
symbol.is_reassigned = true
125
126
// The assignment sits inside a function literal
127
// that did not declare the local, so it writes the
128
// enclosing scope's variable rather than one of its
129
// own. Asking for the enclosing closure rather than
130
// the enclosing function keeps a property accessor
131
// body - whose scope shape differs from the one the
132
// local was declared into - out of the question.
133
let closure = current_closure
134
135
if closure? /\ symbol.owner != closure then
136
symbol.is_closure_assigned = true
137
fi
138
od
139
si
140
si
141
si