Skip to content

Commit d8c52d3

Browse files
committed
Use local HIR return proofs for freshness checks
1 parent 833ae67 commit d8c52d3

4 files changed

Lines changed: 124 additions & 23 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, resource-retain escape, and handle-field `take` checks now index binding types, field accesses, and move/retain events from resolved HIR body trees
28+
- Local ownership, `fresh` return, resource-retain escape, and handle-field `take` checks now index binding types, return proofs, 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: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn check_stmt_semantics(
101101
Stmt::Return(stmt) => {
102102
if let Some(value) = &stmt.value {
103103
if function.returns_fresh {
104-
check_fresh_return(analyzer, function, value, state);
104+
check_fresh_return(analyzer, local_analysis, function, value, state);
105105
}
106106
check_take_of_handle_field(analyzer, local_analysis, value, state);
107107
}
@@ -534,6 +534,7 @@ fn resource_capture_diagnostic(
534534

535535
fn check_fresh_return(
536536
analyzer: &mut Analyzer<'_>,
537+
local_analysis: &LocalAnalysis,
537538
function: &FunctionDecl,
538539
value: &Expr,
539540
state: &BodyState,
@@ -568,7 +569,7 @@ fn check_fresh_return(
568569
);
569570
}
570571
Expr::Call { callee, span, .. } => {
571-
if !hir_return_proves_fresh_call(analyzer, callee, span) {
572+
if !hir_return_proves_fresh_call(analyzer, local_analysis, callee, span) {
572573
analyzer.diagnostics.push(
573574
Diagnostic::warning(
574575
code::FRESHNESS_UNKNOWN,
@@ -584,7 +585,7 @@ fn check_fresh_return(
584585
}
585586
}
586587
Expr::Effect { value, .. } | Expr::Manage { value, .. } => {
587-
check_fresh_return(analyzer, function, value, state);
588+
check_fresh_return(analyzer, local_analysis, function, value, state);
588589
}
589590
Expr::Field { span, .. } | Expr::Closure { span, .. } | Expr::Unknown(span) => {
590591
analyzer.diagnostics.push(
@@ -607,12 +608,13 @@ fn check_fresh_return(
607608

608609
fn hir_return_proves_fresh_call(
609610
analyzer: &Analyzer<'_>,
611+
local_analysis: &LocalAnalysis,
610612
callee: &Callee,
611613
span: &crate::diagnostic::Span,
612614
) -> bool {
613-
if let Some(return_fact) = analyzer.hir.return_fact(span) {
615+
if let Some(return_proof) = local_analysis.return_proof(span) {
614616
return matches!(
615-
return_fact.proof,
617+
return_proof,
616618
HirReturnProof::StructConstructor | HirReturnProof::FreshCall
617619
);
618620
}

src/checks/local.rs

Lines changed: 116 additions & 1 deletion
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-
HirFieldAccess, HirFunctionBody, HirStmt,
6+
HirFieldAccess, HirFunctionBody, HirReturnProof, HirStmt,
77
};
88

99
use super::body::Flow;
@@ -22,6 +22,7 @@ pub(crate) struct LocalAnalysis {
2222
events_by_span: HashMap<Span, Vec<HirEffectEvent>>,
2323
binding_types_by_span: HashMap<Span, String>,
2424
field_accesses_by_span: HashMap<Span, HirFieldAccess>,
25+
return_proofs_by_span: HashMap<Span, HirReturnProof>,
2526
}
2627

2728
impl LocalAnalysis {
@@ -39,12 +40,17 @@ impl LocalAnalysis {
3940
.as_ref()
4041
.and_then(|body| body.block.as_ref())
4142
.map_or_else(HashMap::new, index_field_accesses_from_block);
43+
let return_proofs_by_span = body
44+
.as_ref()
45+
.and_then(|body| body.block.as_ref())
46+
.map_or_else(HashMap::new, index_return_proofs_from_block);
4247

4348
Self {
4449
body,
4550
events_by_span,
4651
binding_types_by_span,
4752
field_accesses_by_span,
53+
return_proofs_by_span,
4854
}
4955
}
5056

@@ -87,6 +93,10 @@ impl LocalAnalysis {
8793
self.field_accesses_by_span.get(span)
8894
}
8995

96+
pub(crate) fn return_proof(&self, span: &Span) -> Option<&HirReturnProof> {
97+
self.return_proofs_by_span.get(span)
98+
}
99+
90100
fn effect_events(&self, span: &Span) -> &[HirEffectEvent] {
91101
self.events_by_span.get(span).map_or(&[], Vec::as_slice)
92102
}
@@ -317,6 +327,95 @@ fn collect_expr_field_accesses(expr: &HirExpr, accesses: &mut HashMap<Span, HirF
317327
}
318328
}
319329

330+
fn index_return_proofs_from_block(block: &HirBlock) -> HashMap<Span, HirReturnProof> {
331+
let mut proofs = HashMap::new();
332+
collect_block_return_proofs(block, &mut proofs);
333+
proofs
334+
}
335+
336+
fn collect_block_return_proofs(block: &HirBlock, proofs: &mut HashMap<Span, HirReturnProof>) {
337+
for statement in &block.statements {
338+
collect_stmt_return_proofs(statement, proofs);
339+
}
340+
}
341+
342+
fn collect_stmt_return_proofs(statement: &HirStmt, proofs: &mut HashMap<Span, HirReturnProof>) {
343+
match statement {
344+
HirStmt::Return { value, proof, span } => {
345+
let proof_span = value.as_ref().map_or(span, hir_expr_span);
346+
proofs.insert(proof_span.clone(), proof.clone());
347+
if let Some(value) = value {
348+
collect_expr_return_proofs(value, proofs);
349+
}
350+
}
351+
HirStmt::Let {
352+
value: Some(value), ..
353+
}
354+
| HirStmt::Expr(value) => collect_expr_return_proofs(value, proofs),
355+
HirStmt::With { resource, body, .. } => {
356+
collect_expr_return_proofs(resource, proofs);
357+
collect_block_return_proofs(body, proofs);
358+
}
359+
HirStmt::If {
360+
condition,
361+
then_body,
362+
else_body,
363+
..
364+
} => {
365+
collect_expr_return_proofs(condition, proofs);
366+
collect_block_return_proofs(then_body, proofs);
367+
if let Some(else_body) = else_body {
368+
collect_block_return_proofs(else_body, proofs);
369+
}
370+
}
371+
HirStmt::Loop {
372+
condition, body, ..
373+
} => {
374+
if let Some(condition) = condition {
375+
collect_expr_return_proofs(condition, proofs);
376+
}
377+
collect_block_return_proofs(body, proofs);
378+
}
379+
HirStmt::Let { value: None, .. }
380+
| HirStmt::Break(_)
381+
| HirStmt::Continue(_)
382+
| HirStmt::Unknown(_) => {}
383+
}
384+
}
385+
386+
fn collect_expr_return_proofs(expr: &HirExpr, proofs: &mut HashMap<Span, HirReturnProof>) {
387+
match expr {
388+
HirExpr::Call { args, .. } => {
389+
for arg in args {
390+
collect_expr_return_proofs(&arg.value, proofs);
391+
}
392+
}
393+
HirExpr::Effect { value, .. } | HirExpr::Manage { value, .. } => {
394+
collect_expr_return_proofs(value, proofs);
395+
}
396+
HirExpr::Field { base, .. } => collect_expr_return_proofs(base, proofs),
397+
HirExpr::Closure { body, .. } => collect_block_return_proofs(body, proofs),
398+
HirExpr::Ident { .. }
399+
| HirExpr::Number { .. }
400+
| HirExpr::String { .. }
401+
| HirExpr::Unknown(_) => {}
402+
}
403+
}
404+
405+
fn hir_expr_span(expr: &HirExpr) -> &Span {
406+
match expr {
407+
HirExpr::Ident { span, .. }
408+
| HirExpr::Number { span, .. }
409+
| HirExpr::String { span, .. }
410+
| HirExpr::Field { span, .. }
411+
| HirExpr::Call { span, .. }
412+
| HirExpr::Effect { span, .. }
413+
| HirExpr::Manage { span, .. }
414+
| HirExpr::Closure { span, .. }
415+
| HirExpr::Unknown(span) => span,
416+
}
417+
}
418+
320419
impl BodyState {
321420
pub(crate) fn seed_params(&mut self, bindings: &[HirBinding]) {
322421
for binding in bindings {
@@ -635,6 +734,18 @@ mod tests {
635734
},
636735
span: span(22),
637736
}),
737+
HirStmt::Return {
738+
value: Some(HirExpr::Call {
739+
callee: Callee::Name("Image.load".to_string()),
740+
args: Vec::new(),
741+
resolution: CallResolution::Unknown,
742+
events: Vec::new(),
743+
type_name: Some("Image".to_string()),
744+
span: span(23),
745+
}),
746+
proof: HirReturnProof::FreshCall,
747+
span: span(23),
748+
},
638749
],
639750
span: span(1),
640751
}),
@@ -656,6 +767,10 @@ mod tests {
656767
.field_access(&span(22))
657768
.is_some_and(|access| access.is_handle)
658769
);
770+
assert!(matches!(
771+
local_analysis.return_proof(&span(23)),
772+
Some(HirReturnProof::FreshCall)
773+
));
659774

660775
local_analysis.apply_retention_events(&span(20), &mut state);
661776
local_analysis.apply_move_events(&span(21), &mut state);

src/hir.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ pub struct Hir {
283283
field_accesses: Vec<HirFieldAccess>,
284284
effect_events: Vec<HirEffectEvent>,
285285
returns: Vec<HirReturn>,
286-
returns_by_span: HashMap<Span, HirReturn>,
287286
function_bodies: HashMap<String, HirFunctionBody>,
288287
}
289288

@@ -369,10 +368,6 @@ impl Hir {
369368
self.function_bodies.get(function_name)
370369
}
371370

372-
pub fn return_fact(&self, span: &Span) -> Option<&HirReturn> {
373-
self.returns_by_span.get(span)
374-
}
375-
376371
pub fn resolve_call(&self, callee: &Callee) -> CallResolution {
377372
let call_name = callee_name(callee);
378373
if is_enum_variant_call(call_name) {
@@ -440,11 +435,6 @@ impl Hir {
440435
.iter()
441436
.map(|site| (site.span.clone(), site.resolution.clone()))
442437
.collect();
443-
self.returns_by_span = facts
444-
.returns
445-
.iter()
446-
.map(|return_fact| (return_fact.span.clone(), return_fact.clone()))
447-
.collect();
448438
self.function_bodies = build_function_bodies(&facts);
449439
self.call_sites = facts.call_sites;
450440
self.bindings = facts.bindings;
@@ -1791,12 +1781,6 @@ fn render(body: read String) -> Result<fresh Response, HttpError> {
17911781
returns[0].proof,
17921782
HirReturnProof::Ident { ref name } if name == "response"
17931783
));
1794-
assert!(matches!(
1795-
hir.return_fact(&returns[0].span)
1796-
.expect("return lookup by span works")
1797-
.proof,
1798-
HirReturnProof::Ident { .. }
1799-
));
18001784

18011785
let body = hir.function_body("render").expect("function body exists");
18021786
assert_eq!(body.function_name, "render");

0 commit comments

Comments
 (0)