Skip to content

Commit b2b0e40

Browse files
committed
Resolve call sites in HIR
1 parent de24730 commit b2b0e40

5 files changed

Lines changed: 232 additions & 20 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ It currently implements:
1616
- Builtin signatures for the current fixture stdlib surface, including `Image`, `File`, `Map`, `ResourcePool`, `Json`, `Csv`, database/resource helpers, and cache/config helpers
1717
- HIR type and field tables for class/struct/resource declarations and handle fields
1818
- HIR constructor signatures derived from declared type fields
19+
- HIR call-site facts with resolved builtin, user function, constructor, enum variant, and unknown callees
1920
- AST-driven mode and call checks for local-only features, named arguments, data effects, and retaining APIs
2021
- AST-driven body checks for local moves, early-exit-aware `fresh` returns, resource escape, resolved handle-field `take`, and managed closure captures
2122
- Focused check modules for mode, calls, body semantics, and forbidden operator behavior

src/analyzer.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::checks;
2-
use crate::diagnostic::{Diagnostic, code};
3-
use crate::hir::{DuplicateSymbolKind, FunctionSig, Hir, HirTypeKind};
2+
use crate::diagnostic::{Diagnostic, Span, code};
3+
use crate::hir::{CallResolution, DuplicateSymbolKind, FunctionSig, Hir, HirTypeKind};
44
use crate::lexer::{Token, lex};
55
use crate::syntax::ast::{Callee, EffectDecl, Item};
66
use crate::syntax::parse_source;
@@ -176,6 +176,13 @@ impl Analyzer<'_> {
176176
}
177177
}
178178
}
179+
180+
pub(crate) fn resolve_call_site(&self, callee: &Callee, span: &Span) -> CallResolution {
181+
self.hir
182+
.call_resolution(span)
183+
.cloned()
184+
.unwrap_or_else(|| self.hir.resolve_call(callee))
185+
}
179186
}
180187

181188
fn effect_name(effect: &EffectDecl) -> &str {

src/checks/calls.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
22

33
use crate::analyzer::Analyzer;
44
use crate::diagnostic::{Diagnostic, Span, code};
5+
use crate::hir::CallResolution;
56
use crate::syntax::ast::{Block, CallArg, Callee, DataEffect, Expr, Item, LetKind, Stmt};
67

78
pub(crate) fn check(analyzer: &mut Analyzer<'_>) {
@@ -78,7 +79,8 @@ fn check_call_args(
7879
locals: &HashSet<String>,
7980
) {
8081
let call_name = callee_name(callee);
81-
if is_enum_variant_call(&call_name) {
82+
let resolution = analyzer.resolve_call_site(callee, call_span);
83+
if matches!(resolution, CallResolution::EnumVariant) {
8284
return;
8385
}
8486

@@ -101,8 +103,9 @@ fn check_call_args(
101103
}
102104
}
103105

104-
let Some(signature) = analyzer.resolve_callee(callee) else {
105-
if !is_allowed_unresolved_callee(analyzer, callee) {
106+
let signature = match resolution {
107+
CallResolution::Resolved { signature, .. } => signature,
108+
CallResolution::Unknown => {
106109
analyzer.diagnostics.push(
107110
Diagnostic::error(
108111
code::UNKNOWN_CALLEE,
@@ -119,8 +122,9 @@ fn check_call_args(
119122
"manual",
120123
),
121124
);
125+
return;
122126
}
123-
return;
127+
CallResolution::EnumVariant => return,
124128
};
125129
let signature_params = signature.params.clone();
126130
let param_effects: HashMap<String, &'static str> = signature
@@ -270,17 +274,6 @@ fn check_call_args(
270274
}
271275
}
272276

273-
fn is_enum_variant_call(name: &str) -> bool {
274-
matches!(name, "Ok" | "Err" | "Some" | "None" | "Result" | "Option")
275-
}
276-
277-
fn is_allowed_unresolved_callee(analyzer: &Analyzer<'_>, callee: &Callee) -> bool {
278-
match callee {
279-
Callee::Name(name) => is_enum_variant_call(name) || analyzer.hir.type_info(name).is_some(),
280-
Callee::Qualified { .. } => false,
281-
}
282-
}
283-
284277
fn join_param_names(params: &[crate::hir::ParamSig]) -> String {
285278
params
286279
.iter()

src/diagnostic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl Severity {
5656
}
5757
}
5858

59-
#[derive(Debug, Clone, PartialEq, Eq)]
59+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6060
pub struct Span {
6161
pub file: String,
6262
pub line: usize,

src/hir.rs

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use std::collections::{HashMap, HashSet};
22

33
use crate::diagnostic::Span;
44
use crate::syntax::ast::{
5-
DataEffect, EffectDecl, FieldDecl, FunctionDecl, Item, Param, Program as SyntaxProgram,
6-
TypeDecl, TypeKind,
5+
Block, Callee, DataEffect, EffectDecl, Expr, FieldDecl, FunctionDecl, Item, Param,
6+
Program as SyntaxProgram, Stmt, TypeDecl, TypeKind,
77
};
88

99
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -78,12 +78,39 @@ pub struct DuplicateSymbol {
7878
pub duplicate_span: Span,
7979
}
8080

81+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82+
pub enum ResolvedCalleeKind {
83+
UserFunction,
84+
BuiltinFunction,
85+
Constructor { type_kind: HirTypeKind },
86+
}
87+
88+
#[derive(Debug, Clone, PartialEq, Eq)]
89+
pub enum CallResolution {
90+
Resolved {
91+
signature: FunctionSig,
92+
kind: ResolvedCalleeKind,
93+
},
94+
EnumVariant,
95+
Unknown,
96+
}
97+
98+
#[derive(Debug, Clone, PartialEq, Eq)]
99+
pub struct HirCallSite {
100+
pub function_name: String,
101+
pub callee: Callee,
102+
pub span: Span,
103+
pub resolution: CallResolution,
104+
}
105+
81106
#[derive(Debug, Default)]
82107
pub struct Hir {
83108
signatures: HashMap<String, FunctionSig>,
84109
types: HashMap<String, TypeInfo>,
85110
fields_by_name: HashMap<String, Vec<FieldInfo>>,
86111
duplicate_symbols: Vec<DuplicateSymbol>,
112+
call_sites: Vec<HirCallSite>,
113+
call_resolutions_by_span: HashMap<Span, CallResolution>,
87114
}
88115

89116
impl Hir {
@@ -124,6 +151,7 @@ impl Hir {
124151
}
125152
}
126153
}
154+
hir.collect_call_sites(program);
127155
hir
128156
}
129157

@@ -159,6 +187,37 @@ impl Hir {
159187
&self.duplicate_symbols
160188
}
161189

190+
pub fn call_resolution(&self, span: &Span) -> Option<&CallResolution> {
191+
self.call_resolutions_by_span.get(span)
192+
}
193+
194+
pub fn resolve_call(&self, callee: &Callee) -> CallResolution {
195+
let call_name = callee_name(callee);
196+
if is_enum_variant_call(call_name) {
197+
return CallResolution::EnumVariant;
198+
}
199+
200+
let signature = match callee {
201+
Callee::Name(name) => self.resolve_function(None, name),
202+
Callee::Qualified { namespace, name } => self.resolve_function(Some(namespace), name),
203+
};
204+
let Some(signature) = signature else {
205+
return CallResolution::Unknown;
206+
};
207+
let kind = match callee {
208+
Callee::Name(name) => self.type_kind(name).map_or_else(
209+
|| function_kind(signature),
210+
|type_kind| ResolvedCalleeKind::Constructor { type_kind },
211+
),
212+
Callee::Qualified { .. } => function_kind(signature),
213+
};
214+
215+
CallResolution::Resolved {
216+
signature: signature.clone(),
217+
kind,
218+
}
219+
}
220+
162221
fn insert_function(&mut self, signature: FunctionSig) {
163222
let key = match &signature.namespace {
164223
Some(namespace) => qualified_key(namespace, &signature.name),
@@ -184,6 +243,117 @@ impl Hir {
184243
self.insert_function(signature);
185244
}
186245
}
246+
247+
fn collect_call_sites(&mut self, program: &SyntaxProgram) {
248+
let mut sites = Vec::new();
249+
for item in &program.items {
250+
let Item::Function(function) = item else {
251+
continue;
252+
};
253+
collect_call_sites_in_block(self, &function.name, &function.body, &mut sites);
254+
}
255+
256+
self.call_resolutions_by_span = sites
257+
.iter()
258+
.map(|site| (site.span.clone(), site.resolution.clone()))
259+
.collect();
260+
self.call_sites = sites;
261+
}
262+
}
263+
264+
fn collect_call_sites_in_block(
265+
hir: &Hir,
266+
function_name: &str,
267+
block: &Block,
268+
sites: &mut Vec<HirCallSite>,
269+
) {
270+
for statement in &block.statements {
271+
collect_call_sites_in_stmt(hir, function_name, statement, sites);
272+
}
273+
}
274+
275+
fn collect_call_sites_in_stmt(
276+
hir: &Hir,
277+
function_name: &str,
278+
statement: &Stmt,
279+
sites: &mut Vec<HirCallSite>,
280+
) {
281+
match statement {
282+
Stmt::Let(stmt) => {
283+
if let Some(value) = &stmt.value {
284+
collect_call_sites_in_expr(hir, function_name, value, sites);
285+
}
286+
}
287+
Stmt::Return(stmt) => {
288+
if let Some(value) = &stmt.value {
289+
collect_call_sites_in_expr(hir, function_name, value, sites);
290+
}
291+
}
292+
Stmt::With(stmt) => {
293+
collect_call_sites_in_expr(hir, function_name, &stmt.resource, sites);
294+
collect_call_sites_in_block(hir, function_name, &stmt.body, sites);
295+
}
296+
Stmt::If(stmt) => {
297+
collect_call_sites_in_expr(hir, function_name, &stmt.condition, sites);
298+
collect_call_sites_in_block(hir, function_name, &stmt.then_body, sites);
299+
if let Some(else_body) = &stmt.else_body {
300+
collect_call_sites_in_block(hir, function_name, else_body, sites);
301+
}
302+
}
303+
Stmt::Loop(stmt) => {
304+
if let Some(condition) = &stmt.condition {
305+
collect_call_sites_in_expr(hir, function_name, condition, sites);
306+
}
307+
collect_call_sites_in_block(hir, function_name, &stmt.body, sites);
308+
}
309+
Stmt::Expr(expr) => collect_call_sites_in_expr(hir, function_name, expr, sites),
310+
Stmt::Break(_) | Stmt::Continue(_) | Stmt::Unknown(_) => {}
311+
}
312+
}
313+
314+
fn collect_call_sites_in_expr(
315+
hir: &Hir,
316+
function_name: &str,
317+
expr: &Expr,
318+
sites: &mut Vec<HirCallSite>,
319+
) {
320+
match expr {
321+
Expr::Call { callee, args, span } => {
322+
sites.push(HirCallSite {
323+
function_name: function_name.to_string(),
324+
callee: callee.clone(),
325+
span: span.clone(),
326+
resolution: hir.resolve_call(callee),
327+
});
328+
for arg in args {
329+
collect_call_sites_in_expr(hir, function_name, &arg.value, sites);
330+
}
331+
}
332+
Expr::Effect { value, .. } | Expr::Manage { value, .. } => {
333+
collect_call_sites_in_expr(hir, function_name, value, sites);
334+
}
335+
Expr::Field { base, .. } => collect_call_sites_in_expr(hir, function_name, base, sites),
336+
Expr::Closure { body, .. } => collect_call_sites_in_block(hir, function_name, body, sites),
337+
Expr::Ident(_, _) | Expr::Number(_, _) | Expr::String(_, _) | Expr::Unknown(_) => {}
338+
}
339+
}
340+
341+
fn function_kind(signature: &FunctionSig) -> ResolvedCalleeKind {
342+
if signature.is_builtin {
343+
ResolvedCalleeKind::BuiltinFunction
344+
} else {
345+
ResolvedCalleeKind::UserFunction
346+
}
347+
}
348+
349+
fn is_enum_variant_call(name: &str) -> bool {
350+
matches!(name, "Ok" | "Err" | "Some" | "None" | "Result" | "Option")
351+
}
352+
353+
fn callee_name(callee: &Callee) -> &str {
354+
match callee {
355+
Callee::Name(name) | Callee::Qualified { name, .. } => name,
356+
}
187357
}
188358

189359
fn function_sig_from_decl(function: &FunctionDecl) -> FunctionSig {
@@ -826,4 +996,45 @@ struct Response {
826996
assert_eq!(duplicate.first_span.line, 5);
827997
assert_eq!(duplicate.duplicate_span.line, 6);
828998
}
999+
1000+
#[test]
1001+
fn resolves_body_call_sites() {
1002+
let source = r#"
1003+
mode: managed
1004+
1005+
struct Response {
1006+
status: Int
1007+
body: String
1008+
}
1009+
1010+
fn render(body: read String) -> Result<fresh Response, HttpError> {
1011+
Log.write(message: read body)
1012+
Missing.call(value: read body)
1013+
return Response(status: 200, body: read body)
1014+
}
1015+
"#;
1016+
1017+
let program = parse_source("test.rss", source);
1018+
let hir = Hir::from_syntax(&program);
1019+
let sites = &hir.call_sites;
1020+
1021+
assert_eq!(sites.len(), 3);
1022+
assert!(matches!(
1023+
sites[0].resolution,
1024+
CallResolution::Resolved {
1025+
kind: ResolvedCalleeKind::BuiltinFunction,
1026+
..
1027+
}
1028+
));
1029+
assert!(matches!(sites[1].resolution, CallResolution::Unknown));
1030+
assert!(matches!(
1031+
sites[2].resolution,
1032+
CallResolution::Resolved {
1033+
kind: ResolvedCalleeKind::Constructor {
1034+
type_kind: HirTypeKind::Struct
1035+
},
1036+
..
1037+
}
1038+
));
1039+
}
8291040
}

0 commit comments

Comments
 (0)