Skip to content

Commit 7b776a8

Browse files
committed
Require ResourcePool bindings to be local
1 parent 53b7c77 commit 7b776a8

3 files changed

Lines changed: 64 additions & 0 deletions

File tree

src/checks/body.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,29 @@ pub(crate) fn check(analyzer: &mut Analyzer<'_>) {
2929
check_retained_closure_captures(analyzer, &local_analysis);
3030
check_take_handle_fields(analyzer, &local_analysis);
3131
check_fresh_returns(analyzer, &local_analysis, &function);
32+
if let Some(body) = &hir_body {
33+
check_resource_pool_bindings(analyzer, body);
34+
}
3235
let mut state = local_analysis.initial_state();
3336
if let Some(block) = hir_body.as_ref().and_then(|body| body.block.as_ref()) {
3437
check_block(analyzer, &local_analysis, block, &mut state);
3538
}
3639
}
3740
}
3841

42+
fn check_resource_pool_bindings(analyzer: &mut Analyzer<'_>, body: &crate::hir::HirFunctionBody) {
43+
for binding in &body.bindings {
44+
if binding.kind == HirBindingKind::ManagedLet
45+
&& binding
46+
.type_name
47+
.as_deref()
48+
.is_some_and(is_resource_pool_type)
49+
{
50+
resource_pool_not_local_diagnostic(analyzer, &binding.name, binding.span.clone());
51+
}
52+
}
53+
}
54+
3955
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4056
pub(crate) enum Flow {
4157
Fallthrough,
@@ -549,6 +565,10 @@ fn type_root_name(type_name: &str) -> &str {
549565
.map_or(type_name, |(root, _)| root)
550566
}
551567

568+
fn is_resource_pool_type(type_name: &str) -> bool {
569+
type_root_name(type_name) == "ResourcePool"
570+
}
571+
552572
fn resource_is_active_at(
553573
local_analysis: &LocalAnalysis,
554574
binding: &str,
@@ -595,6 +615,27 @@ fn resource_pool_lease_escape_diagnostic(
595615
);
596616
}
597617

618+
fn resource_pool_not_local_diagnostic(
619+
analyzer: &mut Analyzer<'_>,
620+
binding: &str,
621+
span: crate::diagnostic::Span,
622+
) {
623+
analyzer.diagnostics.push(
624+
Diagnostic::error(
625+
code::RESOURCE_POOL_NOT_LOCAL,
626+
format!("ResourcePool binding `{binding}` must be local."),
627+
span,
628+
"ResourcePool must be local",
629+
)
630+
.with_cause("ResourcePool owns long-lived resources and must not be hidden behind an ordinary managed binding.")
631+
.with_fix(
632+
"make_resource_pool_local",
633+
format!("Declare `{binding}` with `local` instead of `let`."),
634+
"machine-applicable",
635+
),
636+
);
637+
}
638+
598639
fn resource_capture_diagnostic(
599640
analyzer: &mut Analyzer<'_>,
600641
binding: &str,

src/diagnostic.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod code {
2323
pub const RESOURCE_ESCAPE: &str = "RS0702";
2424
pub const INVALID_RESOURCE_POOL_TYPE: &str = "RS0703";
2525
pub const RESOURCE_GENERIC_ARGUMENT: &str = "RS0704";
26+
pub const RESOURCE_POOL_NOT_LOCAL: &str = "RS0705";
2627
pub const LOCAL_CAPTURED_BY_MANAGED_CLOSURE: &str = "RS0801";
2728
pub const TAKE_HANDLE_FIELD: &str = "RS0901";
2829
pub const OPERATOR_OVERLOAD_ATTEMPT: &str = "RS1001";
@@ -302,6 +303,11 @@ static DIAGNOSTIC_EXPLANATIONS: &[DiagnosticExplanation] = &[
302303
title: "resource in ordinary generic type",
303304
explanation: "Resource values may only be held by explicit resource APIs such as `ResourcePool<T: Resource>`; ordinary generic containers must not be instantiated with resource types.",
304305
},
306+
DiagnosticExplanation {
307+
code: code::RESOURCE_POOL_NOT_LOCAL,
308+
title: "ResourcePool must be local",
309+
explanation: "`ResourcePool<T>` must be held in a local binding so its resource lifetime and pool ownership stay explicit.",
310+
},
305311
DiagnosticExplanation {
306312
code: code::LOCAL_CAPTURED_BY_MANAGED_CLOSURE,
307313
title: "local captured by managed closure",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// expect: RS0705
2+
mode: uses-local
3+
4+
resource DbConnection {
5+
fd: Int
6+
7+
drop {
8+
Db.close(fd: fd)
9+
}
10+
}
11+
12+
fn bad_pool(url: read Url) -> Unit {
13+
let pool = ResourcePool<DbConnection>.new(
14+
create: || DbConnection.open(url: read url),
15+
max_size: 4,
16+
)
17+
}

0 commit comments

Comments
 (0)