Skip to content

Commit 833ae67

Browse files
committed
Resolve handle field checks through local HIR analysis
1 parent 5e18788 commit 833ae67

4 files changed

Lines changed: 159 additions & 41 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ It currently implements:
2525
- Per-function HIR body views that group bindings, calls, fields, effects, and returns
2626
- AST-driven mode and call checks for local-only features, named arguments, data effects, and retaining APIs
2727
- AST-driven body checks for local moves, early-exit-aware `fresh` returns, resource escape, resolved handle-field `take`, and managed closure captures
28-
- Local ownership and resource-retain escape checks now index binding types and move/retain events from resolved HIR body trees
28+
- Local ownership, resource-retain escape, and handle-field `take` checks now index binding types, field accesses, and move/retain events from resolved HIR body trees
2929
- Focused check modules for mode, calls, body semantics, and forbidden operator behavior
3030
- Fixture-based pass/fail scenario tests under `tests/fixtures`
3131

src/checks/body.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ fn check_stmt_semantics(
9494
}
9595

9696
if let Some(value) = &stmt.value {
97-
check_take_of_handle_field(analyzer, value, state);
97+
check_take_of_handle_field(analyzer, local_analysis, value, state);
9898
}
9999
Flow::Fallthrough
100100
}
@@ -103,7 +103,7 @@ fn check_stmt_semantics(
103103
if function.returns_fresh {
104104
check_fresh_return(analyzer, function, value, state);
105105
}
106-
check_take_of_handle_field(analyzer, value, state);
106+
check_take_of_handle_field(analyzer, local_analysis, value, state);
107107
}
108108
Flow::Return
109109
}
@@ -112,7 +112,7 @@ fn check_stmt_semantics(
112112
check_block(analyzer, local_analysis, function, &stmt.body, state)
113113
}
114114
Stmt::If(stmt) => {
115-
check_take_of_handle_field(analyzer, &stmt.condition, state);
115+
check_take_of_handle_field(analyzer, local_analysis, &stmt.condition, state);
116116
apply_expr_effects(local_analysis, &stmt.condition, state);
117117

118118
let base_state = state.clone();
@@ -141,7 +141,7 @@ fn check_stmt_semantics(
141141
}
142142
Stmt::Loop(stmt) => {
143143
if let Some(condition) = &stmt.condition {
144-
check_take_of_handle_field(analyzer, condition, state);
144+
check_take_of_handle_field(analyzer, local_analysis, condition, state);
145145
apply_expr_effects(local_analysis, condition, state);
146146
}
147147

@@ -164,7 +164,7 @@ fn check_stmt_semantics(
164164
)
165165
}
166166
Stmt::Expr(expr) => {
167-
check_take_of_handle_field(analyzer, expr, state);
167+
check_take_of_handle_field(analyzer, local_analysis, expr, state);
168168
Flow::Fallthrough
169169
}
170170
Stmt::Break(_) => Flow::Break,
@@ -295,7 +295,12 @@ fn check_managed_closure_captures(analyzer: &mut Analyzer<'_>, body: &Block, sta
295295
}
296296
}
297297

298-
fn check_take_of_handle_field(analyzer: &mut Analyzer<'_>, expr: &Expr, state: &BodyState) {
298+
fn check_take_of_handle_field(
299+
analyzer: &mut Analyzer<'_>,
300+
local_analysis: &LocalAnalysis,
301+
expr: &Expr,
302+
state: &BodyState,
303+
) {
299304
match expr {
300305
Expr::Effect {
301306
effect: DataEffect::Take,
@@ -307,7 +312,7 @@ fn check_take_of_handle_field(analyzer: &mut Analyzer<'_>, expr: &Expr, state: &
307312
name,
308313
span: field_span,
309314
} = value.as_ref()
310-
&& is_handle_field(analyzer, state, base, name, field_span)
315+
&& is_handle_field(analyzer, local_analysis, state, base, name, field_span)
311316
{
312317
analyzer.diagnostics.push(
313318
Diagnostic::error(
@@ -319,63 +324,74 @@ fn check_take_of_handle_field(analyzer: &mut Analyzer<'_>, expr: &Expr, state: &
319324
.with_cause("Handle fields are managed references and cannot be consumed as local inline values."),
320325
);
321326
}
322-
check_take_of_handle_field(analyzer, value, state);
327+
check_take_of_handle_field(analyzer, local_analysis, value, state);
328+
}
329+
Expr::Effect { value, .. } => {
330+
check_take_of_handle_field(analyzer, local_analysis, value, state);
331+
}
332+
Expr::Manage { value, .. } => {
333+
check_take_of_handle_field(analyzer, local_analysis, value, state);
323334
}
324-
Expr::Effect { value, .. } => check_take_of_handle_field(analyzer, value, state),
325-
Expr::Manage { value, .. } => check_take_of_handle_field(analyzer, value, state),
326335
Expr::Call { args, .. } => {
327336
for arg in args {
328-
check_take_of_handle_field(analyzer, &arg.value, state);
337+
check_take_of_handle_field(analyzer, local_analysis, &arg.value, state);
329338
}
330339
}
331-
Expr::Field { base, .. } => check_take_of_handle_field(analyzer, base, state),
340+
Expr::Field { base, .. } => {
341+
check_take_of_handle_field(analyzer, local_analysis, base, state);
342+
}
332343
Expr::Closure { body, .. } => {
333344
for statement in &body.statements {
334-
check_take_of_handle_in_stmt(analyzer, statement, state);
345+
check_take_of_handle_in_stmt(analyzer, local_analysis, statement, state);
335346
}
336347
}
337348
Expr::Ident(_, _) | Expr::Number(_, _) | Expr::String(_, _) | Expr::Unknown(_) => {}
338349
}
339350
}
340351

341-
fn check_take_of_handle_in_stmt(analyzer: &mut Analyzer<'_>, statement: &Stmt, state: &BodyState) {
352+
fn check_take_of_handle_in_stmt(
353+
analyzer: &mut Analyzer<'_>,
354+
local_analysis: &LocalAnalysis,
355+
statement: &Stmt,
356+
state: &BodyState,
357+
) {
342358
match statement {
343359
Stmt::Let(stmt) => {
344360
if let Some(value) = &stmt.value {
345-
check_take_of_handle_field(analyzer, value, state);
361+
check_take_of_handle_field(analyzer, local_analysis, value, state);
346362
}
347363
}
348364
Stmt::Return(stmt) => {
349365
if let Some(value) = &stmt.value {
350-
check_take_of_handle_field(analyzer, value, state);
366+
check_take_of_handle_field(analyzer, local_analysis, value, state);
351367
}
352368
}
353369
Stmt::With(stmt) => {
354-
check_take_of_handle_field(analyzer, &stmt.resource, state);
370+
check_take_of_handle_field(analyzer, local_analysis, &stmt.resource, state);
355371
for statement in &stmt.body.statements {
356-
check_take_of_handle_in_stmt(analyzer, statement, state);
372+
check_take_of_handle_in_stmt(analyzer, local_analysis, statement, state);
357373
}
358374
}
359375
Stmt::If(stmt) => {
360-
check_take_of_handle_field(analyzer, &stmt.condition, state);
376+
check_take_of_handle_field(analyzer, local_analysis, &stmt.condition, state);
361377
for statement in &stmt.then_body.statements {
362-
check_take_of_handle_in_stmt(analyzer, statement, state);
378+
check_take_of_handle_in_stmt(analyzer, local_analysis, statement, state);
363379
}
364380
if let Some(else_body) = &stmt.else_body {
365381
for statement in &else_body.statements {
366-
check_take_of_handle_in_stmt(analyzer, statement, state);
382+
check_take_of_handle_in_stmt(analyzer, local_analysis, statement, state);
367383
}
368384
}
369385
}
370386
Stmt::Loop(stmt) => {
371387
if let Some(condition) = &stmt.condition {
372-
check_take_of_handle_field(analyzer, condition, state);
388+
check_take_of_handle_field(analyzer, local_analysis, condition, state);
373389
}
374390
for statement in &stmt.body.statements {
375-
check_take_of_handle_in_stmt(analyzer, statement, state);
391+
check_take_of_handle_in_stmt(analyzer, local_analysis, statement, state);
376392
}
377393
}
378-
Stmt::Expr(expr) => check_take_of_handle_field(analyzer, expr, state),
394+
Stmt::Expr(expr) => check_take_of_handle_field(analyzer, local_analysis, expr, state),
379395
Stmt::Break(_) | Stmt::Continue(_) => {}
380396
Stmt::Unknown(_) => {}
381397
}
@@ -676,12 +692,13 @@ fn infer_call_type(
676692

677693
fn is_handle_field(
678694
analyzer: &Analyzer<'_>,
695+
local_analysis: &LocalAnalysis,
679696
state: &BodyState,
680697
base: &Expr,
681698
field_name: &str,
682699
field_span: &crate::diagnostic::Span,
683700
) -> bool {
684-
if let Some(field) = analyzer.hir.field_access(field_span) {
701+
if let Some(field) = local_analysis.field_access(field_span) {
685702
return field.is_handle;
686703
}
687704

src/checks/local.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
33
use crate::diagnostic::Span;
44
use crate::hir::{
55
HirBinding, HirBindingKind, HirBlock, HirEffectEvent, HirEffectEventKind, HirExpr,
6-
HirFunctionBody, HirStmt,
6+
HirFieldAccess, HirFunctionBody, HirStmt,
77
};
88

99
use super::body::Flow;
@@ -21,6 +21,7 @@ pub(crate) struct LocalAnalysis {
2121
body: Option<HirFunctionBody>,
2222
events_by_span: HashMap<Span, Vec<HirEffectEvent>>,
2323
binding_types_by_span: HashMap<Span, String>,
24+
field_accesses_by_span: HashMap<Span, HirFieldAccess>,
2425
}
2526

2627
impl LocalAnalysis {
@@ -34,11 +35,16 @@ impl LocalAnalysis {
3435
.as_ref()
3536
.and_then(|body| body.block.as_ref())
3637
.map_or_else(HashMap::new, index_binding_types_from_block);
38+
let field_accesses_by_span = body
39+
.as_ref()
40+
.and_then(|body| body.block.as_ref())
41+
.map_or_else(HashMap::new, index_field_accesses_from_block);
3742

3843
Self {
3944
body,
4045
events_by_span,
4146
binding_types_by_span,
47+
field_accesses_by_span,
4248
}
4349
}
4450

@@ -77,6 +83,10 @@ impl LocalAnalysis {
7783
self.binding_types_by_span.get(span).map(String::as_str)
7884
}
7985

86+
pub(crate) fn field_access(&self, span: &Span) -> Option<&HirFieldAccess> {
87+
self.field_accesses_by_span.get(span)
88+
}
89+
8090
fn effect_events(&self, span: &Span) -> &[HirEffectEvent] {
8191
self.events_by_span.get(span).map_or(&[], Vec::as_slice)
8292
}
@@ -230,6 +240,83 @@ fn collect_stmt_binding_types(statement: &HirStmt, types: &mut HashMap<Span, Str
230240
}
231241
}
232242

243+
fn index_field_accesses_from_block(block: &HirBlock) -> HashMap<Span, HirFieldAccess> {
244+
let mut accesses = HashMap::new();
245+
collect_block_field_accesses(block, &mut accesses);
246+
accesses
247+
}
248+
249+
fn collect_block_field_accesses(block: &HirBlock, accesses: &mut HashMap<Span, HirFieldAccess>) {
250+
for statement in &block.statements {
251+
collect_stmt_field_accesses(statement, accesses);
252+
}
253+
}
254+
255+
fn collect_stmt_field_accesses(statement: &HirStmt, accesses: &mut HashMap<Span, HirFieldAccess>) {
256+
match statement {
257+
HirStmt::Let {
258+
value: Some(value), ..
259+
}
260+
| HirStmt::Return {
261+
value: Some(value), ..
262+
}
263+
| HirStmt::Expr(value) => collect_expr_field_accesses(value, accesses),
264+
HirStmt::With { resource, body, .. } => {
265+
collect_expr_field_accesses(resource, accesses);
266+
collect_block_field_accesses(body, accesses);
267+
}
268+
HirStmt::If {
269+
condition,
270+
then_body,
271+
else_body,
272+
..
273+
} => {
274+
collect_expr_field_accesses(condition, accesses);
275+
collect_block_field_accesses(then_body, accesses);
276+
if let Some(else_body) = else_body {
277+
collect_block_field_accesses(else_body, accesses);
278+
}
279+
}
280+
HirStmt::Loop {
281+
condition, body, ..
282+
} => {
283+
if let Some(condition) = condition {
284+
collect_expr_field_accesses(condition, accesses);
285+
}
286+
collect_block_field_accesses(body, accesses);
287+
}
288+
HirStmt::Let { value: None, .. }
289+
| HirStmt::Return { value: None, .. }
290+
| HirStmt::Break(_)
291+
| HirStmt::Continue(_)
292+
| HirStmt::Unknown(_) => {}
293+
}
294+
}
295+
296+
fn collect_expr_field_accesses(expr: &HirExpr, accesses: &mut HashMap<Span, HirFieldAccess>) {
297+
match expr {
298+
HirExpr::Field {
299+
base, access, span, ..
300+
} => {
301+
accesses.insert(span.clone(), access.clone());
302+
collect_expr_field_accesses(base, accesses);
303+
}
304+
HirExpr::Call { args, .. } => {
305+
for arg in args {
306+
collect_expr_field_accesses(&arg.value, accesses);
307+
}
308+
}
309+
HirExpr::Effect { value, .. } | HirExpr::Manage { value, .. } => {
310+
collect_expr_field_accesses(value, accesses);
311+
}
312+
HirExpr::Closure { body, .. } => collect_block_field_accesses(body, accesses),
313+
HirExpr::Ident { .. }
314+
| HirExpr::Number { .. }
315+
| HirExpr::String { .. }
316+
| HirExpr::Unknown(_) => {}
317+
}
318+
}
319+
233320
impl BodyState {
234321
pub(crate) fn seed_params(&mut self, bindings: &[HirBinding]) {
235322
for binding in bindings {
@@ -435,7 +522,7 @@ fn merge_fallthrough_states(base: &BodyState, left: &BodyState, right: &BodyStat
435522
#[cfg(test)]
436523
mod tests {
437524
use super::*;
438-
use crate::hir::{CallResolution, HirBlock, HirExpr, HirStmt};
525+
use crate::hir::{CallResolution, HirBlock, HirExpr, HirFieldAccess, HirStmt};
439526
use crate::syntax::ast::Callee;
440527

441528
fn span(line: usize) -> Span {
@@ -531,6 +618,23 @@ mod tests {
531618
type_name: Some("Image".to_string()),
532619
span: span(21),
533620
}),
621+
HirStmt::Expr(HirExpr::Field {
622+
base: Box::new(HirExpr::Ident {
623+
name: "config".to_string(),
624+
type_name: Some("Config".to_string()),
625+
span: span(22),
626+
}),
627+
name: "rules".to_string(),
628+
access: HirFieldAccess {
629+
function_name: "run".to_string(),
630+
name: "rules".to_string(),
631+
span: span(22),
632+
base_type: Some("Config".to_string()),
633+
type_name: Some("Rules".to_string()),
634+
is_handle: true,
635+
},
636+
span: span(22),
637+
}),
534638
],
535639
span: span(1),
536640
}),
@@ -547,6 +651,11 @@ mod tests {
547651
local_analysis.retained_value_spans(&span(20), "cached"),
548652
vec![span(20)]
549653
);
654+
assert!(
655+
local_analysis
656+
.field_access(&span(22))
657+
.is_some_and(|access| access.is_handle)
658+
);
550659

551660
local_analysis.apply_retention_events(&span(20), &mut state);
552661
local_analysis.apply_move_events(&span(21), &mut state);

0 commit comments

Comments
 (0)