Skip to content

Commit f5a1f50

Browse files
committed
Extract clean local state machine
1 parent 021145f commit f5a1f50

3 files changed

Lines changed: 170 additions & 145 deletions

File tree

src/checks/body.rs

Lines changed: 9 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::collections::{HashMap, HashSet};
2-
31
use crate::analyzer::Analyzer;
42
use crate::diagnostic::{Diagnostic, code};
53
use crate::hir::{
@@ -8,6 +6,8 @@ use crate::hir::{
86
};
97
use crate::syntax::ast::{Block, Callee, DataEffect, Expr, FunctionDecl, Item, LetKind, Stmt};
108

9+
use super::local::{BodyState, merge_if_state, merge_loop_state};
10+
1111
pub(crate) fn check(analyzer: &mut Analyzer<'_>) {
1212
let functions: Vec<FunctionDecl> = analyzer
1313
.syntax_program
@@ -26,15 +26,6 @@ pub(crate) fn check(analyzer: &mut Analyzer<'_>) {
2626
}
2727
}
2828

29-
#[derive(Debug, Clone, Default)]
30-
struct BodyState {
31-
locals: HashSet<String>,
32-
clean_locals: HashSet<String>,
33-
managed: HashSet<String>,
34-
moved: HashMap<String, crate::diagnostic::Span>,
35-
value_types: HashMap<String, String>,
36-
}
37-
3829
fn seed_function_bindings(analyzer: &Analyzer<'_>, function: &FunctionDecl, state: &mut BodyState) {
3930
let Some(body) = analyzer.hir.function_body(&function.name) else {
4031
return;
@@ -44,15 +35,13 @@ fn seed_function_bindings(analyzer: &Analyzer<'_>, function: &FunctionDecl, stat
4435
continue;
4536
}
4637
if let Some(type_name) = &binding.type_name {
47-
state
48-
.value_types
49-
.insert(binding.name.clone(), type_name.clone());
38+
state.record_type(binding.name.clone(), type_name.clone());
5039
}
5140
}
5241
}
5342

5443
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55-
enum Flow {
44+
pub(crate) enum Flow {
5645
Fallthrough,
5746
Return,
5847
Break,
@@ -180,18 +169,17 @@ fn apply_stmt_effects(analyzer: &mut Analyzer<'_>, statement: &Stmt, state: &mut
180169
Stmt::Let(stmt) => {
181170
match stmt.kind {
182171
LetKind::Managed => {
183-
state.managed.insert(stmt.name.clone());
172+
state.bind_managed(stmt.name.clone());
184173
}
185174
LetKind::Local => {
186-
state.locals.insert(stmt.name.clone());
187-
state.clean_locals.insert(stmt.name.clone());
175+
state.bind_local(stmt.name.clone());
188176
}
189177
}
190178
if let Some(value) = &stmt.value {
191179
if let Some(type_name) = hir_binding_type(analyzer, &stmt.span)
192180
.or_else(|| infer_expr_type(analyzer, value, state))
193181
{
194-
state.value_types.insert(stmt.name.clone(), type_name);
182+
state.record_type(stmt.name.clone(), type_name);
195183
}
196184
apply_expr_effects(analyzer, value, state);
197185
}
@@ -219,127 +207,6 @@ fn hir_binding_type(analyzer: &Analyzer<'_>, span: &crate::diagnostic::Span) ->
219207
.and_then(|binding| binding.type_name.clone())
220208
}
221209

222-
fn merge_if_state(
223-
state: &mut BodyState,
224-
base: &BodyState,
225-
then_state: BodyState,
226-
then_flow: Flow,
227-
else_branch: Option<(BodyState, Flow)>,
228-
) -> Flow {
229-
let (else_state, else_flow) = else_branch.unwrap_or_else(|| (base.clone(), Flow::Fallthrough));
230-
let mut fallthrough_states = Vec::new();
231-
if then_flow == Flow::Fallthrough {
232-
fallthrough_states.push(then_state.clone());
233-
}
234-
if else_flow == Flow::Fallthrough {
235-
fallthrough_states.push(else_state.clone());
236-
}
237-
238-
match fallthrough_states.as_slice() {
239-
[] => {
240-
*state = base.clone();
241-
merge_non_fallthrough(then_flow, else_flow)
242-
}
243-
[only] => {
244-
*state = fallthrough_projection(base, only);
245-
Flow::Fallthrough
246-
}
247-
[left, right] => {
248-
*state = merge_fallthrough_states(base, left, right);
249-
Flow::Fallthrough
250-
}
251-
_ => unreachable!("if has at most two branches"),
252-
}
253-
}
254-
255-
fn merge_loop_state(
256-
state: &mut BodyState,
257-
base: &BodyState,
258-
body_state: BodyState,
259-
body_flow: Flow,
260-
may_skip: bool,
261-
) -> Flow {
262-
if !may_skip && body_flow == Flow::Return {
263-
*state = base.clone();
264-
return Flow::Return;
265-
}
266-
if !may_skip && body_flow == Flow::Break {
267-
*state = fallthrough_projection(base, &body_state);
268-
return Flow::Fallthrough;
269-
}
270-
271-
let mut moved = base.moved.clone();
272-
if body_flow != Flow::Return {
273-
for (name, span) in &body_state.moved {
274-
if base.locals.contains(name) || base.moved.contains_key(name) {
275-
moved.entry(name.clone()).or_insert_with(|| span.clone());
276-
}
277-
}
278-
}
279-
280-
state.locals = base.locals.clone();
281-
state.managed = base.managed.clone();
282-
state.value_types = base.value_types.clone();
283-
state.moved = moved;
284-
state.clean_locals = base
285-
.clean_locals
286-
.intersection(&body_state.clean_locals)
287-
.filter(|name| base.locals.contains(*name))
288-
.cloned()
289-
.collect();
290-
Flow::Fallthrough
291-
}
292-
293-
fn merge_non_fallthrough(left: Flow, right: Flow) -> Flow {
294-
if left == right { left } else { Flow::Return }
295-
}
296-
297-
fn fallthrough_projection(base: &BodyState, branch: &BodyState) -> BodyState {
298-
let mut moved = base.moved.clone();
299-
for (name, span) in &branch.moved {
300-
if base.locals.contains(name) || base.moved.contains_key(name) {
301-
moved.entry(name.clone()).or_insert_with(|| span.clone());
302-
}
303-
}
304-
305-
BodyState {
306-
locals: base.locals.clone(),
307-
managed: base.managed.clone(),
308-
value_types: base.value_types.clone(),
309-
moved,
310-
clean_locals: branch
311-
.clean_locals
312-
.intersection(&base.clean_locals)
313-
.filter(|name| base.locals.contains(*name))
314-
.cloned()
315-
.collect(),
316-
}
317-
}
318-
319-
fn merge_fallthrough_states(base: &BodyState, left: &BodyState, right: &BodyState) -> BodyState {
320-
let mut moved = base.moved.clone();
321-
for branch in [left, right] {
322-
for (name, span) in &branch.moved {
323-
if base.locals.contains(name) || base.moved.contains_key(name) {
324-
moved.entry(name.clone()).or_insert_with(|| span.clone());
325-
}
326-
}
327-
}
328-
329-
BodyState {
330-
locals: base.locals.clone(),
331-
managed: base.managed.clone(),
332-
value_types: base.value_types.clone(),
333-
moved,
334-
clean_locals: left
335-
.clean_locals
336-
.intersection(&right.clean_locals)
337-
.filter(|name| base.locals.contains(*name))
338-
.cloned()
339-
.collect(),
340-
}
341-
}
342-
343210
fn apply_expr_effects(analyzer: &mut Analyzer<'_>, expr: &Expr, state: &mut BodyState) {
344211
match expr {
345212
Expr::Manage { value, span } => {
@@ -383,10 +250,7 @@ fn apply_move_events(
383250
continue;
384251
}
385252
if state.locals.contains(&event.binding_name) {
386-
state
387-
.moved
388-
.insert(event.binding_name.clone(), event.span.clone());
389-
state.clean_locals.remove(&event.binding_name);
253+
state.mark_moved(&event.binding_name, event.span.clone());
390254
}
391255
}
392256
}
@@ -403,7 +267,7 @@ fn apply_retention_events(
403267
if !state.locals.contains(&event.binding_name) {
404268
continue;
405269
}
406-
state.clean_locals.remove(&event.binding_name);
270+
state.mark_retained(&event.binding_name);
407271
}
408272
}
409273

src/checks/local.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
use std::collections::{HashMap, HashSet};
2+
3+
use crate::diagnostic::Span;
4+
5+
use super::body::Flow;
6+
7+
#[derive(Debug, Clone, Default)]
8+
pub(crate) struct BodyState {
9+
pub(crate) locals: HashSet<String>,
10+
pub(crate) clean_locals: HashSet<String>,
11+
pub(crate) managed: HashSet<String>,
12+
pub(crate) moved: HashMap<String, Span>,
13+
pub(crate) value_types: HashMap<String, String>,
14+
}
15+
16+
impl BodyState {
17+
pub(crate) fn bind_managed(&mut self, name: impl Into<String>) {
18+
self.managed.insert(name.into());
19+
}
20+
21+
pub(crate) fn bind_local(&mut self, name: impl Into<String>) {
22+
let name = name.into();
23+
self.locals.insert(name.clone());
24+
self.clean_locals.insert(name);
25+
}
26+
27+
pub(crate) fn record_type(&mut self, name: impl Into<String>, type_name: impl Into<String>) {
28+
self.value_types.insert(name.into(), type_name.into());
29+
}
30+
31+
pub(crate) fn mark_moved(&mut self, name: &str, span: Span) {
32+
self.moved.insert(name.to_string(), span);
33+
self.clean_locals.remove(name);
34+
}
35+
36+
pub(crate) fn mark_retained(&mut self, name: &str) {
37+
self.clean_locals.remove(name);
38+
}
39+
}
40+
41+
pub(crate) fn merge_if_state(
42+
state: &mut BodyState,
43+
base: &BodyState,
44+
then_state: BodyState,
45+
then_flow: Flow,
46+
else_branch: Option<(BodyState, Flow)>,
47+
) -> Flow {
48+
let (else_state, else_flow) = else_branch.unwrap_or_else(|| (base.clone(), Flow::Fallthrough));
49+
let mut fallthrough_states = Vec::new();
50+
if then_flow == Flow::Fallthrough {
51+
fallthrough_states.push(then_state.clone());
52+
}
53+
if else_flow == Flow::Fallthrough {
54+
fallthrough_states.push(else_state.clone());
55+
}
56+
57+
match fallthrough_states.as_slice() {
58+
[] => {
59+
*state = base.clone();
60+
merge_non_fallthrough(then_flow, else_flow)
61+
}
62+
[only] => {
63+
*state = fallthrough_projection(base, only);
64+
Flow::Fallthrough
65+
}
66+
[left, right] => {
67+
*state = merge_fallthrough_states(base, left, right);
68+
Flow::Fallthrough
69+
}
70+
_ => unreachable!("if has at most two branches"),
71+
}
72+
}
73+
74+
pub(crate) fn merge_loop_state(
75+
state: &mut BodyState,
76+
base: &BodyState,
77+
body_state: BodyState,
78+
body_flow: Flow,
79+
may_skip: bool,
80+
) -> Flow {
81+
if !may_skip && body_flow == Flow::Return {
82+
*state = base.clone();
83+
return Flow::Return;
84+
}
85+
if !may_skip && body_flow == Flow::Break {
86+
*state = fallthrough_projection(base, &body_state);
87+
return Flow::Fallthrough;
88+
}
89+
90+
let mut moved = base.moved.clone();
91+
if body_flow != Flow::Return {
92+
for (name, span) in &body_state.moved {
93+
if base.locals.contains(name) || base.moved.contains_key(name) {
94+
moved.entry(name.clone()).or_insert_with(|| span.clone());
95+
}
96+
}
97+
}
98+
99+
state.locals = base.locals.clone();
100+
state.managed = base.managed.clone();
101+
state.value_types = base.value_types.clone();
102+
state.moved = moved;
103+
state.clean_locals = base
104+
.clean_locals
105+
.intersection(&body_state.clean_locals)
106+
.filter(|name| base.locals.contains(*name))
107+
.cloned()
108+
.collect();
109+
Flow::Fallthrough
110+
}
111+
112+
fn merge_non_fallthrough(left: Flow, right: Flow) -> Flow {
113+
if left == right { left } else { Flow::Return }
114+
}
115+
116+
fn fallthrough_projection(base: &BodyState, branch: &BodyState) -> BodyState {
117+
let mut moved = base.moved.clone();
118+
for (name, span) in &branch.moved {
119+
if base.locals.contains(name) || base.moved.contains_key(name) {
120+
moved.entry(name.clone()).or_insert_with(|| span.clone());
121+
}
122+
}
123+
124+
BodyState {
125+
locals: base.locals.clone(),
126+
managed: base.managed.clone(),
127+
value_types: base.value_types.clone(),
128+
moved,
129+
clean_locals: branch
130+
.clean_locals
131+
.intersection(&base.clean_locals)
132+
.filter(|name| base.locals.contains(*name))
133+
.cloned()
134+
.collect(),
135+
}
136+
}
137+
138+
fn merge_fallthrough_states(base: &BodyState, left: &BodyState, right: &BodyState) -> BodyState {
139+
let mut moved = base.moved.clone();
140+
for branch in [left, right] {
141+
for (name, span) in &branch.moved {
142+
if base.locals.contains(name) || base.moved.contains_key(name) {
143+
moved.entry(name.clone()).or_insert_with(|| span.clone());
144+
}
145+
}
146+
}
147+
148+
BodyState {
149+
locals: base.locals.clone(),
150+
managed: base.managed.clone(),
151+
value_types: base.value_types.clone(),
152+
moved,
153+
clean_locals: left
154+
.clean_locals
155+
.intersection(&right.clean_locals)
156+
.filter(|name| base.locals.contains(*name))
157+
.cloned()
158+
.collect(),
159+
}
160+
}

src/checks/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) mod body;
22
pub(crate) mod calls;
33
pub(crate) mod forbidden;
4+
pub(crate) mod local;
45
pub(crate) mod mode;

0 commit comments

Comments
 (0)