Skip to content

Commit

Permalink
feat: reject singletons with non-'static lifetime parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
LukeMathWalker committed Jun 16, 2024
1 parent 57ac60a commit 6af94d6
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ERROR:
× `&app::B` can't be a singleton because its lifetime isn't `'static`.
│ Singletons must be available for as long as the application is running,
│ therefore they their lifetime must be `'static`.
│ therefore their lifetime must be `'static`.
│
│ ╭─[src/lib.rs:25:1]
│ 25 │ bp.constructor(f!(crate::a), Lifecycle::Singleton);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
description = """Singletons can only depend on other singletons, they can't depend on
request-scoped or transient components"""
description = """Singletons cannot be non-'static references"""

[expectations]
codegen = "fail"
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ERROR:
× `app::B<'a>` can't be a singleton because at least one of its lifetime
│ parameters isn't `'static`.
│ Singletons must be available for as long as the application is running,
│ therefore their lifetime must be `'static`.
│
│ ╭─[src/lib.rs:26:1]
│ 26 │ bp.singleton(f!(self::a));
│ 27 │ bp.singleton(f!(self::B::new)).clone_if_necessary();
│ ·  ────────┬───────
│ · ╰── The singleton was registered here
│ 28 │ bp.route(GET, "/", f!(self::handler));
│ ╰────
│  help: If your type holds a reference to data that's owned by another
│ singleton component, register its constructor as transient rather
│ than singleton.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use pavex::blueprint::{router::GET, Blueprint};
use pavex::f;
use pavex::http::StatusCode;

pub struct A;

#[derive(Clone)]
pub struct B<'a>(&'a A);

impl<'a> B<'a> {
pub fn new(a: &'a A) -> Self {
B(a)
}
}

pub fn a() -> A {
todo!()
}

pub fn handler<'a>(_b: B<'a>) -> StatusCode {
todo!()
}

pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.singleton(f!(self::a));
bp.singleton(f!(self::B::new)).clone_if_necessary();
bp.route(GET, "/", f!(self::handler));
bp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
description = """Singletons cannot have non-'static lifetime parameters"""

[expectations]
codegen = "fail"
34 changes: 33 additions & 1 deletion libs/pavexc/src/compiler/analyses/components/db/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,7 @@ impl ComponentDb {
let error = anyhow::anyhow!(
"`{output_type:?}` can't be a singleton because its lifetime isn't `'static`.\n\
Singletons must be available for as long as the application is running, \
therefore they their lifetime must be `'static`.",
therefore their lifetime must be `'static`.",
);
let d = CompilerDiagnostic::builder(error)
.optional_source(source)
Expand All @@ -1101,4 +1101,36 @@ impl ComponentDb {
.build();
diagnostics.push(d.into());
}

pub(super) fn non_static_lifetime_parameter_in_singleton(
output_type: &ResolvedType,
user_component_id: UserComponentId,
user_component_db: &UserComponentDb,
package_graph: &PackageGraph,
diagnostics: &mut Vec<miette::Error>,
) {
let location = user_component_db.get_location(user_component_id);
let source = try_source!(location, package_graph, diagnostics);
let label = source
.as_ref()
.map(|source| {
diagnostic::get_f_macro_invocation_span(&source, location)
.labeled("The singleton was registered here".into())
})
.flatten();
let error = anyhow::anyhow!(
"`{output_type:?}` can't be a singleton because at least one of its lifetime parameters isn't `'static`.\n\
Singletons must be available for as long as the application is running, \
therefore their lifetime must be `'static`.",
);
let d = CompilerDiagnostic::builder(error)
.optional_source(source)
.optional_label(label)
.help(
"If your type holds a reference to data that's owned by another singleton component, \
register its constructor as transient rather than singleton.".into(),
)
.build();
diagnostics.push(d.into());
}
}
12 changes: 12 additions & 0 deletions libs/pavexc/src/compiler/analyses/components/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,18 @@ impl ComponentDb {
diagnostics,
);
}
} else {
if output_type.has_implicit_lifetime_parameters()
|| !output_type.named_lifetime_parameters().is_empty()
{
Self::non_static_lifetime_parameter_in_singleton(
output_type,
user_component_id,
&self.user_component_db,
package_graph,
diagnostics,
);
}
}
}

Expand Down

0 comments on commit 6af94d6

Please sign in to comment.