Skip to content

Commit 96c4a96

Browse files
committed
Record return facts in HIR
1 parent 9862190 commit 96c4a96

3 files changed

Lines changed: 146 additions & 16 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ It currently implements:
2020
- HIR body binding facts for parameters, managed lets, local lets, and best-effort initial value types
2121
- HIR field-access facts with resolved base type, field type, and handle status where known
2222
- HIR effect events for `manage`, `take`, and retaining calls
23+
- HIR return facts with initial freshness proof classification
2324
- AST-driven mode and call checks for local-only features, named arguments, data effects, and retaining APIs
2425
- AST-driven body checks for local moves, early-exit-aware `fresh` returns, resource escape, resolved handle-field `take`, and managed closure captures
2526
- Focused check modules for mode, calls, body semantics, and forbidden operator behavior

src/checks/body.rs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use std::collections::{HashMap, HashSet};
33
use crate::analyzer::Analyzer;
44
use crate::diagnostic::{Diagnostic, code};
55
use crate::hir::{
6-
CallResolution, HirBindingKind, HirEffectEventKind, HirTypeKind, ResolvedCalleeKind,
6+
CallResolution, HirBindingKind, HirEffectEventKind, HirReturnProof, HirTypeKind,
7+
ResolvedCalleeKind,
78
};
89
use crate::syntax::ast::{Block, Callee, DataEffect, Expr, FunctionDecl, Item, LetKind, Stmt};
910

@@ -704,21 +705,7 @@ fn check_fresh_return(
704705
);
705706
}
706707
Expr::Call { callee, span, .. } => {
707-
let resolution = analyzer.resolve_call_site(callee, span);
708-
let constructor_is_struct = matches!(
709-
resolution,
710-
CallResolution::Resolved {
711-
kind: ResolvedCalleeKind::Constructor {
712-
type_kind: HirTypeKind::Struct
713-
},
714-
..
715-
}
716-
);
717-
let call_returns_fresh = match resolution {
718-
CallResolution::Resolved { signature, .. } => signature.returns_fresh,
719-
CallResolution::EnumVariant | CallResolution::Unknown => false,
720-
};
721-
if !constructor_is_struct && !call_returns_fresh {
708+
if !hir_return_proves_fresh_call(analyzer, callee, span) {
722709
analyzer.diagnostics.push(
723710
Diagnostic::warning(
724711
code::FRESHNESS_UNKNOWN,
@@ -755,6 +742,35 @@ fn check_fresh_return(
755742
}
756743
}
757744

745+
fn hir_return_proves_fresh_call(
746+
analyzer: &Analyzer<'_>,
747+
callee: &Callee,
748+
span: &crate::diagnostic::Span,
749+
) -> bool {
750+
if let Some(return_fact) = analyzer.hir.return_fact(span) {
751+
return matches!(
752+
return_fact.proof,
753+
HirReturnProof::StructConstructor | HirReturnProof::FreshCall
754+
);
755+
}
756+
757+
let resolution = analyzer.resolve_call_site(callee, span);
758+
let constructor_is_struct = matches!(
759+
resolution,
760+
CallResolution::Resolved {
761+
kind: ResolvedCalleeKind::Constructor {
762+
type_kind: HirTypeKind::Struct
763+
},
764+
..
765+
}
766+
);
767+
let call_returns_fresh = match resolution {
768+
CallResolution::Resolved { signature, .. } => signature.returns_fresh,
769+
CallResolution::EnumVariant | CallResolution::Unknown => false,
770+
};
771+
constructor_is_struct || call_returns_fresh
772+
}
773+
758774
fn fresh_return_diagnostic(
759775
analyzer: &mut Analyzer<'_>,
760776
function: &FunctionDecl,

src/hir.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,22 @@ pub struct HirEffectEvent {
145145
pub value_span: Span,
146146
}
147147

148+
#[derive(Debug, Clone, PartialEq, Eq)]
149+
pub enum HirReturnProof {
150+
NoValue,
151+
Ident { name: String },
152+
StructConstructor,
153+
FreshCall,
154+
Unknown,
155+
}
156+
157+
#[derive(Debug, Clone, PartialEq, Eq)]
158+
pub struct HirReturn {
159+
pub function_name: String,
160+
pub span: Span,
161+
pub proof: HirReturnProof,
162+
}
163+
148164
#[derive(Debug, Default)]
149165
pub struct Hir {
150166
signatures: HashMap<String, FunctionSig>,
@@ -160,6 +176,8 @@ pub struct Hir {
160176
field_accesses_by_span: HashMap<Span, HirFieldAccess>,
161177
effect_events: Vec<HirEffectEvent>,
162178
effect_events_by_span: HashMap<Span, Vec<HirEffectEvent>>,
179+
returns: Vec<HirReturn>,
180+
returns_by_span: HashMap<Span, HirReturn>,
163181
}
164182

165183
impl Hir {
@@ -260,6 +278,10 @@ impl Hir {
260278
.map_or(&[], Vec::as_slice)
261279
}
262280

281+
pub fn return_fact(&self, span: &Span) -> Option<&HirReturn> {
282+
self.returns_by_span.get(span)
283+
}
284+
263285
pub fn resolve_call(&self, callee: &Callee) -> CallResolution {
264286
let call_name = callee_name(callee);
265287
if is_enum_variant_call(call_name) {
@@ -357,10 +379,16 @@ impl Hir {
357379
by_span
358380
},
359381
);
382+
self.returns_by_span = facts
383+
.returns
384+
.iter()
385+
.map(|return_fact| (return_fact.span.clone(), return_fact.clone()))
386+
.collect();
360387
self.call_sites = facts.call_sites;
361388
self.bindings = facts.bindings;
362389
self.field_accesses = facts.field_accesses;
363390
self.effect_events = facts.effect_events;
391+
self.returns = facts.returns;
364392
}
365393
}
366394

@@ -370,6 +398,7 @@ struct BodyFacts {
370398
bindings: Vec<HirBinding>,
371399
field_accesses: Vec<HirFieldAccess>,
372400
effect_events: Vec<HirEffectEvent>,
401+
returns: Vec<HirReturn>,
373402
}
374403

375404
fn collect_function_body_facts(hir: &Hir, function: &FunctionDecl, facts: &mut BodyFacts) {
@@ -428,7 +457,18 @@ fn collect_body_facts_in_stmt(
428457
}
429458
Stmt::Return(stmt) => {
430459
if let Some(value) = &stmt.value {
460+
facts.returns.push(HirReturn {
461+
function_name: function_name.to_string(),
462+
span: value.span().clone(),
463+
proof: classify_return_expr(hir, value),
464+
});
431465
collect_body_facts_in_expr(hir, function_name, value, value_types, facts);
466+
} else {
467+
facts.returns.push(HirReturn {
468+
function_name: function_name.to_string(),
469+
span: stmt.span.clone(),
470+
proof: HirReturnProof::NoValue,
471+
});
432472
}
433473
}
434474
Stmt::With(stmt) => {
@@ -617,6 +657,40 @@ fn infer_hir_expr_type(
617657
}
618658
}
619659

660+
fn classify_return_expr(hir: &Hir, expr: &Expr) -> HirReturnProof {
661+
match expr {
662+
Expr::Ident(name, _) => HirReturnProof::Ident { name: name.clone() },
663+
Expr::Call { callee, .. } => match hir.resolve_call(callee) {
664+
CallResolution::Resolved {
665+
signature,
666+
kind:
667+
ResolvedCalleeKind::Constructor {
668+
type_kind: HirTypeKind::Struct,
669+
},
670+
} if signature.returns_fresh => HirReturnProof::StructConstructor,
671+
CallResolution::Resolved { signature, .. } if signature.returns_fresh => {
672+
HirReturnProof::FreshCall
673+
}
674+
CallResolution::Resolved {
675+
kind:
676+
ResolvedCalleeKind::Constructor {
677+
type_kind: HirTypeKind::Struct,
678+
},
679+
..
680+
} => HirReturnProof::StructConstructor,
681+
CallResolution::Resolved { .. }
682+
| CallResolution::EnumVariant
683+
| CallResolution::Unknown => HirReturnProof::Unknown,
684+
},
685+
Expr::Effect { value, .. } | Expr::Manage { value, .. } => classify_return_expr(hir, value),
686+
Expr::Field { .. }
687+
| Expr::Closure { .. }
688+
| Expr::Number(_, _)
689+
| Expr::String(_, _)
690+
| Expr::Unknown(_) => HirReturnProof::Unknown,
691+
}
692+
}
693+
620694
fn function_kind(signature: &FunctionSig) -> ResolvedCalleeKind {
621695
if signature.is_builtin {
622696
ResolvedCalleeKind::BuiltinFunction
@@ -1338,6 +1412,20 @@ fn render(body: read String) -> Result<fresh Response, HttpError> {
13381412
.kind,
13391413
HirBindingKind::ManagedLet
13401414
));
1415+
1416+
let returns = &hir.returns;
1417+
assert_eq!(returns.len(), 1);
1418+
assert_eq!(returns[0].function_name, "render");
1419+
assert!(matches!(
1420+
returns[0].proof,
1421+
HirReturnProof::Ident { ref name } if name == "response"
1422+
));
1423+
assert!(matches!(
1424+
hir.return_fact(&returns[0].span)
1425+
.expect("return lookup by span works")
1426+
.proof,
1427+
HirReturnProof::Ident { .. }
1428+
));
13411429
}
13421430

13431431
#[test]
@@ -1436,4 +1524,29 @@ fn publish(cache: mut ImageCache, path: read Path) -> Unit {
14361524
assert_eq!(hir.effect_events[2].binding_name, "image");
14371525
assert_eq!(hir.effect_events(&hir.effect_events[0].span).len(), 1);
14381526
}
1527+
1528+
#[test]
1529+
fn classifies_fresh_return_facts() {
1530+
let source = r#"
1531+
mode: managed
1532+
1533+
struct Response {
1534+
status: Int
1535+
}
1536+
1537+
fn make_response() -> fresh Response {
1538+
return Response(status: 200)
1539+
}
1540+
"#;
1541+
1542+
let program = parse_source("test.rss", source);
1543+
let hir = Hir::from_syntax(&program);
1544+
let return_fact = hir.returns.first().expect("return fact exists");
1545+
1546+
assert_eq!(return_fact.function_name, "make_response");
1547+
assert!(matches!(
1548+
return_fact.proof,
1549+
HirReturnProof::StructConstructor
1550+
));
1551+
}
14391552
}

0 commit comments

Comments
 (0)