Skip to content

Commit 53b7c77

Browse files
committed
Reject resources in ordinary generic containers
1 parent ae6dc62 commit 53b7c77

4 files changed

Lines changed: 193 additions & 0 deletions

File tree

src/analyzer.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ impl Analyzer<'_> {
3333
self.check_signature_explicitness();
3434
self.check_resource_fields();
3535
self.check_resource_pool_type_arguments();
36+
self.check_resource_generic_arguments();
3637
checks::mode::check(self);
3738
checks::calls::check(self);
3839
checks::body::check(self);
@@ -191,6 +192,28 @@ impl Analyzer<'_> {
191192
}
192193
}
193194

195+
fn check_resource_generic_arguments(&mut self) {
196+
let items = self.syntax_program.items.clone();
197+
for item in &items {
198+
match item {
199+
Item::Type(decl) => {
200+
for field in &decl.fields {
201+
self.check_resource_generic_type_ref(&field.ty);
202+
}
203+
}
204+
Item::Function(function) => {
205+
for param in &function.params {
206+
self.check_resource_generic_type_ref(&param.ty);
207+
}
208+
if let Some(return_ty) = &function.return_ty {
209+
self.check_resource_generic_type_ref(return_ty);
210+
}
211+
self.check_resource_generic_calls_in_block(&function.body);
212+
}
213+
}
214+
}
215+
}
216+
194217
fn check_resource_pool_type_ref(&mut self, ty: &TypeRef) {
195218
if ty.name == "ResourcePool" {
196219
match ty.args.first() {
@@ -212,6 +235,19 @@ impl Analyzer<'_> {
212235
}
213236
}
214237

238+
fn check_resource_generic_type_ref(&mut self, ty: &TypeRef) {
239+
if ty.name != "ResourcePool" {
240+
for arg in &ty.args {
241+
if self.hir.type_kind(&arg.name) == Some(HirTypeKind::Resource) {
242+
self.resource_generic_argument_diagnostic(&ty.name, &arg.name, &arg.span);
243+
}
244+
}
245+
}
246+
for arg in &ty.args {
247+
self.check_resource_generic_type_ref(arg);
248+
}
249+
}
250+
215251
fn check_resource_pool_calls_in_stmt(&mut self, statement: &Stmt) {
216252
match statement {
217253
Stmt::Let(stmt) => {
@@ -279,6 +315,76 @@ impl Analyzer<'_> {
279315
}
280316
}
281317

318+
fn check_resource_generic_calls_in_block(&mut self, block: &crate::syntax::ast::Block) {
319+
for statement in &block.statements {
320+
self.check_resource_generic_calls_in_stmt(statement);
321+
}
322+
}
323+
324+
fn check_resource_generic_calls_in_stmt(&mut self, statement: &Stmt) {
325+
match statement {
326+
Stmt::Let(stmt) => {
327+
if let Some(value) = &stmt.value {
328+
self.check_resource_generic_calls_in_expr(value);
329+
}
330+
}
331+
Stmt::Return(stmt) => {
332+
if let Some(value) = &stmt.value {
333+
self.check_resource_generic_calls_in_expr(value);
334+
}
335+
}
336+
Stmt::Expr(value) => self.check_resource_generic_calls_in_expr(value),
337+
Stmt::With(stmt) => {
338+
self.check_resource_generic_calls_in_expr(&stmt.resource);
339+
self.check_resource_generic_calls_in_block(&stmt.body);
340+
}
341+
Stmt::If(stmt) => {
342+
self.check_resource_generic_calls_in_expr(&stmt.condition);
343+
self.check_resource_generic_calls_in_block(&stmt.then_body);
344+
if let Some(else_body) = &stmt.else_body {
345+
self.check_resource_generic_calls_in_block(else_body);
346+
}
347+
}
348+
Stmt::Loop(stmt) => {
349+
if let Some(condition) = &stmt.condition {
350+
self.check_resource_generic_calls_in_expr(condition);
351+
}
352+
self.check_resource_generic_calls_in_block(&stmt.body);
353+
}
354+
Stmt::Break(_) | Stmt::Continue(_) | Stmt::Unknown(_) => {}
355+
}
356+
}
357+
358+
fn check_resource_generic_calls_in_expr(&mut self, expr: &Expr) {
359+
match expr {
360+
Expr::Call { callee, args, span } => {
361+
if let Callee::Qualified { namespace, .. } = callee
362+
&& let Some((root, args)) = generic_namespace_args(namespace)
363+
&& root != "ResourcePool"
364+
{
365+
for arg in args {
366+
if self.hir.type_kind(arg) == Some(HirTypeKind::Resource) {
367+
self.resource_generic_argument_diagnostic(root, arg, span);
368+
}
369+
}
370+
}
371+
for arg in args {
372+
self.check_resource_generic_calls_in_expr(&arg.value);
373+
}
374+
}
375+
Expr::Binary { left, right, .. } => {
376+
self.check_resource_generic_calls_in_expr(left);
377+
self.check_resource_generic_calls_in_expr(right);
378+
}
379+
Expr::Effect { value, .. } | Expr::Manage { value, .. } => {
380+
self.check_resource_generic_calls_in_expr(value);
381+
}
382+
Expr::Field { base, .. } => self.check_resource_generic_calls_in_expr(base),
383+
Expr::Closure { body, .. } => self.check_resource_generic_calls_in_block(body),
384+
Expr::Ident(_, _) | Expr::Number(_, _) | Expr::String(_, _) | Expr::Unknown(_) => {}
385+
}
386+
}
387+
282388
fn check_resource_pool_arg(&mut self, type_name: &str, span: &crate::diagnostic::Span) {
283389
match self.hir.type_kind(type_name) {
284390
Some(HirTypeKind::Resource) | None => {}
@@ -313,6 +419,30 @@ impl Analyzer<'_> {
313419
),
314420
);
315421
}
422+
423+
fn resource_generic_argument_diagnostic(
424+
&mut self,
425+
generic_name: &str,
426+
resource_name: &str,
427+
span: &crate::diagnostic::Span,
428+
) {
429+
self.diagnostics.push(
430+
Diagnostic::error(
431+
code::RESOURCE_GENERIC_ARGUMENT,
432+
format!(
433+
"generic type `{generic_name}` cannot be instantiated with resource `{resource_name}`."
434+
),
435+
span.clone(),
436+
"resource generic argument",
437+
)
438+
.with_cause("Only explicit resource APIs such as `ResourcePool<T: Resource>` may hold resources.")
439+
.with_fix(
440+
"use_resource_api",
441+
"Use `with`, `ResourcePool<T: Resource>`, or a non-resource value type.",
442+
"manual",
443+
),
444+
);
445+
}
316446
}
317447

318448
fn resource_pool_namespace_arg(namespace: &str) -> Option<&str> {
@@ -321,6 +451,31 @@ fn resource_pool_namespace_arg(namespace: &str) -> Option<&str> {
321451
.and_then(|rest| rest.strip_suffix('>'))
322452
}
323453

454+
fn generic_namespace_args(namespace: &str) -> Option<(&str, Vec<&str>)> {
455+
let (root, rest) = namespace.split_once('<')?;
456+
let args = rest.strip_suffix('>')?;
457+
Some((root, split_top_level_type_args(args)))
458+
}
459+
460+
fn split_top_level_type_args(args: &str) -> Vec<&str> {
461+
let mut result = Vec::new();
462+
let mut depth = 0usize;
463+
let mut start = 0usize;
464+
for (index, ch) in args.char_indices() {
465+
match ch {
466+
'<' => depth += 1,
467+
'>' => depth = depth.saturating_sub(1),
468+
',' if depth == 0 => {
469+
result.push(args[start..index].trim());
470+
start = index + 1;
471+
}
472+
_ => {}
473+
}
474+
}
475+
result.push(args[start..].trim());
476+
result
477+
}
478+
324479
fn effect_name(effect: &EffectDecl) -> &str {
325480
match effect {
326481
EffectDecl::Name(name) | EffectDecl::Retains(name) => name,

src/diagnostic.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod code {
2222
pub const RESOURCE_FIELD: &str = "RS0701";
2323
pub const RESOURCE_ESCAPE: &str = "RS0702";
2424
pub const INVALID_RESOURCE_POOL_TYPE: &str = "RS0703";
25+
pub const RESOURCE_GENERIC_ARGUMENT: &str = "RS0704";
2526
pub const LOCAL_CAPTURED_BY_MANAGED_CLOSURE: &str = "RS0801";
2627
pub const TAKE_HANDLE_FIELD: &str = "RS0901";
2728
pub const OPERATOR_OVERLOAD_ATTEMPT: &str = "RS1001";
@@ -296,6 +297,11 @@ static DIAGNOSTIC_EXPLANATIONS: &[DiagnosticExplanation] = &[
296297
title: "invalid ResourcePool type",
297298
explanation: "`ResourcePool<T>` is only valid when `T` is a resource type.",
298299
},
300+
DiagnosticExplanation {
301+
code: code::RESOURCE_GENERIC_ARGUMENT,
302+
title: "resource in ordinary generic type",
303+
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.",
304+
},
299305
DiagnosticExplanation {
300306
code: code::LOCAL_CAPTURED_BY_MANAGED_CLOSURE,
301307
title: "local captured by managed closure",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// expect: RS0704
2+
mode: managed
3+
4+
resource File {
5+
fd: Int
6+
7+
drop {
8+
OS.close(fd: fd)
9+
}
10+
}
11+
12+
struct BadArchive {
13+
files: List<File>
14+
}
15+
16+
fn build_archive() -> Unit {
17+
return Unit
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// expect: RS0704
2+
mode: managed
3+
4+
resource File {
5+
fd: Int
6+
7+
drop {
8+
OS.close(fd: fd)
9+
}
10+
}
11+
12+
fn bad_list(files: read List<File>) -> Unit {
13+
return Unit
14+
}

0 commit comments

Comments
 (0)