From ddc9c04f45d3e344bf308d42bb1c29c009dda572 Mon Sep 17 00:00:00 2001 From: donmai-me <71143298+donmai-me@users.noreply.github.com> Date: Sat, 10 May 2025 22:15:31 +0800 Subject: [PATCH] feat: add warning on Copyable types marked NeverClone --- libs/pavexc/src/compiler/analyses/cloning.rs | 91 ++++++ libs/pavexc/src/compiler/app.rs | 10 +- libs/ui_tests/Cargo.toml | 2 + .../Cargo.toml | 17 ++ .../diagnostics.dot | 75 +++++ .../expectations/app.rs | 265 ++++++++++++++++++ .../expectations/diagnostics.dot | 69 +++++ .../expectations/stderr.txt | 52 ++++ .../src/lib.rs | 58 ++++ .../src/main.rs | 24 ++ .../test_config.toml | 5 + 11 files changed, 667 insertions(+), 1 deletion(-) create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/Cargo.toml create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/diagnostics.dot create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/app.rs create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/diagnostics.dot create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/stderr.txt create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/main.rs create mode 100644 libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/test_config.toml diff --git a/libs/pavexc/src/compiler/analyses/cloning.rs b/libs/pavexc/src/compiler/analyses/cloning.rs index 05ff64631..d9a948d8e 100644 --- a/libs/pavexc/src/compiler/analyses/cloning.rs +++ b/libs/pavexc/src/compiler/analyses/cloning.rs @@ -110,3 +110,94 @@ fn must_be_clonable( .build(); diagnostics.push(diagnostic); } + +/// Check all types whose cloning strategy is set to "NeverClone" are not Copy. + +#[tracing::instrument( + name = "If cloning is not allowed, types should not be copyable", + skip_all +)] + +pub(crate) fn never_clones_should_not_be_copyable<'a>( + component_db: &ComponentDb, + computation_db: &ComputationDb, + krate_collection: &CrateCollection, + diagnostics: &mut crate::diagnostic::DiagnosticSink, +) { + let copy = process_framework_path("core::marker::Copy", krate_collection); + + let ResolvedType::ResolvedPath(copy) = copy else { + unreachable!() + }; + + for (id, _) in component_db.iter() { + let hydrated = component_db.hydrated_component(id, computation_db); + match hydrated { + HydratedComponent::Constructor(_) | HydratedComponent::PrebuiltType(_) => { + if component_db.cloning_strategy(id) != CloningStrategy::NeverClone { + continue; + } + } + _ => { + continue; + } + }; + + let Some(output_type) = hydrated.output_type() else { + continue; + }; + + if let Ok(()) = assert_trait_is_implemented(krate_collection, output_type, ©) { + should_not_be_never_clone(output_type, id, component_db, computation_db, diagnostics); + } + } +} + +fn should_not_be_never_clone( + type_: &ResolvedType, + id: ComponentId, + db: &ComponentDb, + computation_db: &ComputationDb, + diagnostics: &mut crate::diagnostic::DiagnosticSink, +) { + let id = db.derived_from(&id).unwrap_or(id); + let user_id = db.user_component_id(id).unwrap(); + let kind = db.user_db()[user_id].kind(); + let registration = db.registration(user_id); + let source = diagnostics.annotated( + db.registration_target(user_id), + format!("The {kind} was registered here"), + ); + + let (clone_if_necessary, never_clone) = if registration.kind.is_attribute() { + ("clone_if_necessary", "never_clone") + } else { + ("CloneIfNecessary", "NeverClone") + }; + + let output_type = type_.display_for_error(); + let warning_msg = match kind { + ComponentKind::Constructor => { + let callable_path = &computation_db[user_id].path; + format!( + "`{output_type}` implements `Copy`, but its constructor, `{callable_path}`, is marked as `{never_clone}`." + ) + } + ComponentKind::PrebuiltType => { + format!("`{output_type}` implements `Copy`, but it's marked as `{never_clone}`.") + } + _ => unreachable!(), + }; + + let help = format!( + "Either set the cloning strategy to `{clone_if_necessary}` or remove `Copy` for `{output_type}`", + ); + + let diagnostic = CompilerDiagnostic::builder(anyhow::anyhow!(warning_msg)) + .severity(miette::Severity::Warning) + .optional_source(source) + .help(help) + .build(); + + diagnostics.push(diagnostic); +} diff --git a/libs/pavexc/src/compiler/app.rs b/libs/pavexc/src/compiler/app.rs index 8b2d21382..12ccb8792 100644 --- a/libs/pavexc/src/compiler/app.rs +++ b/libs/pavexc/src/compiler/app.rs @@ -14,7 +14,9 @@ use crate::compiler::analyses::application_state::ApplicationState; use crate::compiler::analyses::call_graph::{ ApplicationStateCallGraph, application_state_call_graph, }; -use crate::compiler::analyses::cloning::clonables_can_be_cloned; +use crate::compiler::analyses::cloning::{ + clonables_can_be_cloned, never_clones_should_not_be_copyable, +}; use crate::compiler::analyses::components::{ComponentDb, ComponentId}; use crate::compiler::analyses::computations::ComputationDb; use crate::compiler::analyses::config_types::ConfigTypeDb; @@ -116,6 +118,12 @@ impl App { &krate_collection, &mut diagnostics, ); + never_clones_should_not_be_copyable( + &component_db, + &computation_db, + &krate_collection, + &mut diagnostics, + ); exit_on_errors!(diagnostics); let handler_id2pipeline = { let handler_ids: BTreeSet<_> = router.handler_ids(); diff --git a/libs/ui_tests/Cargo.toml b/libs/ui_tests/Cargo.toml index 56d270c69..9ed371910 100644 --- a/libs/ui_tests/Cargo.toml +++ b/libs/ui_tests/Cargo.toml @@ -11,6 +11,8 @@ members = [ "annotations/non_existing_dependency/generated_app", "app_builder", "app_builder/generated_app", + "blueprint/common/a_warning_is_emitted_for_copyable_never_clone", + "blueprint/common/a_warning_is_emitted_for_copyable_never_clone/generated_app", "blueprint/common/async_callable_are_supported", "blueprint/common/async_callable_are_supported/generated_app", "blueprint/common/cannot_return_the_unit_type", diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/Cargo.toml b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/Cargo.toml new file mode 100644 index 000000000..e2e62057f --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app_cd2e094a" +version = "0.1.0" +edition.workspace = true + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = ["cfg(pavex_ide_hint)"] + +[dependencies] +workspace_hack = { version = "0.1", path = "../../../workspace_hack" } + +[dependencies.pavex] +workspace = true + +[dependencies.pavex_cli_client] +workspace = true diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/diagnostics.dot b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/diagnostics.dot new file mode 100644 index 000000000..a9794a1c1 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/diagnostics.dot @@ -0,0 +1,75 @@ +digraph "GET / - 0" { + 0 [ label = "0| app_cd2e094a::B"] + 1 [ label = "1| crate::route_0::Next0(app_cd2e094a::B) -> crate::route_0::Next0"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_0::Next0) -> pavex::middleware::Next"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::response::Response"] + 4 [ label = "4| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 0 -> 1 [ ] + 3 -> 4 [ ] +} + +digraph "GET / - 1" { + 0 [ label = "0| app_cd2e094a::B"] + 1 [ label = "1| app_cd2e094a::a() -> app_cd2e094a::A"] + 2 [ label = "2| app_cd2e094a::handler_1(app_cd2e094a::A, app_cd2e094a::B) -> pavex::response::Response"] + 3 [ label = "3| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 0 -> 2 [ ] + 1 -> 2 [ ] + 2 -> 3 [ ] +} + +digraph "GET /2 - 0" { + 0 [ label = "0| app_cd2e094a::A1"] + 1 [ label = "1| app_cd2e094a::B1"] + 2 [ label = "2| crate::route_1::Next0(app_cd2e094a::B1, app_cd2e094a::A1) -> crate::route_1::Next0"] + 3 [ label = "3| pavex::middleware::Next::new(crate::route_1::Next0) -> pavex::middleware::Next"] + 4 [ label = "4| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::response::Response"] + 5 [ label = "5| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 3 -> 4 [ ] + 2 -> 3 [ ] + 0 -> 2 [ ] + 1 -> 2 [ ] + 4 -> 5 [ ] +} + +digraph "GET /2 - 1" { + 0 [ label = "0| app_cd2e094a::B1"] + 1 [ label = "1| app_cd2e094a::A1"] + 2 [ label = "2| app_cd2e094a::handler_2(app_cd2e094a::A1, app_cd2e094a::B1) -> pavex::response::Response"] + 3 [ label = "3| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 0 -> 2 [ ] + 1 -> 2 [ ] + 2 -> 3 [ ] +} + +digraph "* * - 0" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| crate::route_2::Next0(&'a pavex::router::AllowedMethods) -> crate::route_2::Next0<'a>"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_2::Next0<'a>) -> pavex::middleware::Next>"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] + 4 [ label = "4| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 3 -> 4 [ ] + 0 -> 1 [ ] +} + +digraph "* * - 1" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::response::Response"] + 2 [ label = "2| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] +} + +digraph app_state { + 0 [ label = "0| app_cd2e094a::b1() -> app_cd2e094a::B1"] + 1 [ label = "1| app_cd2e094a::b() -> app_cd2e094a::B"] + 2 [ label = "2| app_cd2e094a::a1() -> app_cd2e094a::A1"] + 3 [ label = "3| crate::ApplicationState(app_cd2e094a::A1, app_cd2e094a::B, app_cd2e094a::B1) -> crate::ApplicationState"] + 0 -> 3 [ ] + 1 -> 3 [ ] + 2 -> 3 [ ] +} diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/app.rs b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/app.rs new file mode 100644 index 000000000..26a216c26 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/app.rs @@ -0,0 +1,265 @@ +//! Do NOT edit this code. +//! It was automatically generated by Pavex. +//! All manual edits will be lost next time the code is generated. +extern crate alloc; +struct ServerState { + router: Router, + application_state: ApplicationState, +} +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ApplicationConfig {} +pub struct ApplicationState { + pub a_1: app::A1, + pub b: app::B, + pub b_1: app::B1, +} +impl ApplicationState { + pub async fn new( + _app_config: crate::ApplicationConfig, + ) -> Result { + Ok(Self::_new().await) + } + async fn _new() -> crate::ApplicationState { + let v0 = app::b1(); + let v1 = app::b(); + let v2 = app::a1(); + crate::ApplicationState { + a_1: v2, + b: v1, + b_1: v0, + } + } +} +#[deprecated(note = "Use `ApplicationState::new` instead.")] +pub async fn build_application_state( + _app_config: crate::ApplicationConfig, +) -> Result { + crate::ApplicationState::new(_app_config).await +} +#[derive(Debug, thiserror::Error)] +pub enum ApplicationStateError {} +pub fn run( + server_builder: pavex::server::Server, + application_state: ApplicationState, +) -> pavex::server::ServerHandle { + async fn handler( + request: http::Request, + connection_info: Option, + server_state: std::sync::Arc, + ) -> pavex::response::Response { + let (router, state) = (&server_state.router, &server_state.application_state); + router.route(request, connection_info, state).await + } + let router = Router::new(); + let server_state = std::sync::Arc::new(ServerState { + router, + application_state, + }); + server_builder.serve(handler, server_state) +} +struct Router { + router: matchit::Router, +} +impl Router { + /// Create a new router instance. + /// + /// This method is invoked once, when the server starts. + pub fn new() -> Self { + Self { router: Self::router() } + } + fn router() -> matchit::Router { + let mut router = matchit::Router::new(); + router.insert("/", 0u32).unwrap(); + router.insert("/2", 1u32).unwrap(); + router + } + pub async fn route( + &self, + request: http::Request, + _connection_info: Option, + #[allow(unused)] + state: &ApplicationState, + ) -> pavex::response::Response { + let (request_head, _) = request.into_parts(); + let request_head: pavex::request::RequestHead = request_head.into(); + let Ok(matched_route) = self.router.at(&request_head.target.path()) else { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter( + vec![], + ) + .into(); + return route_2::entrypoint(&allowed_methods).await; + }; + match matched_route.value { + 0u32 => { + match &request_head.method { + &pavex::http::Method::GET => { + route_0::entrypoint(state.b.clone()).await + } + _ => { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter([ + pavex::http::Method::GET, + ]) + .into(); + route_2::entrypoint(&allowed_methods).await + } + } + } + 1u32 => { + match &request_head.method { + &pavex::http::Method::GET => { + route_1::entrypoint(state.a_1.clone(), state.b_1.clone()).await + } + _ => { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter([ + pavex::http::Method::GET, + ]) + .into(); + route_2::entrypoint(&allowed_methods).await + } + } + } + i => unreachable!("Unknown route id: {}", i), + } + } +} +pub mod route_0 { + pub async fn entrypoint(s_0: app::B) -> pavex::response::Response { + let response = wrapping_0(s_0).await; + response + } + async fn stage_1(s_0: app::B) -> pavex::response::Response { + let response = handler(s_0).await; + response + } + async fn wrapping_0(v0: app::B) -> pavex::response::Response { + let v1 = crate::route_0::Next0 { + s_0: v0, + next: stage_1, + }; + let v2 = pavex::middleware::Next::new(v1); + let v3 = pavex::middleware::wrap_noop(v2).await; + ::into_response(v3) + } + async fn handler(v0: app::B) -> pavex::response::Response { + let v1 = app::a(); + let v2 = app::handler_1(v1, v0); + ::into_response(v2) + } + struct Next0 + where + T: std::future::Future, + { + s_0: app::B, + next: fn(app::B) -> T, + } + impl std::future::IntoFuture for Next0 + where + T: std::future::Future, + { + type Output = pavex::response::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)(self.s_0) + } + } +} +pub mod route_1 { + pub async fn entrypoint( + s_0: app::A1, + s_1: app::B1, + ) -> pavex::response::Response { + let response = wrapping_0(s_0, s_1).await; + response + } + async fn stage_1( + s_0: app::B1, + s_1: app::A1, + ) -> pavex::response::Response { + let response = handler(s_0, s_1).await; + response + } + async fn wrapping_0( + v0: app::A1, + v1: app::B1, + ) -> pavex::response::Response { + let v2 = crate::route_1::Next0 { + s_0: v1, + s_1: v0, + next: stage_1, + }; + let v3 = pavex::middleware::Next::new(v2); + let v4 = pavex::middleware::wrap_noop(v3).await; + ::into_response(v4) + } + async fn handler( + v0: app::B1, + v1: app::A1, + ) -> pavex::response::Response { + let v2 = app::handler_2(v1, v0); + ::into_response(v2) + } + struct Next0 + where + T: std::future::Future, + { + s_0: app::B1, + s_1: app::A1, + next: fn(app::B1, app::A1) -> T, + } + impl std::future::IntoFuture for Next0 + where + T: std::future::Future, + { + type Output = pavex::response::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)(self.s_0, self.s_1) + } + } +} +pub mod route_2 { + pub async fn entrypoint<'a>( + s_0: &'a pavex::router::AllowedMethods, + ) -> pavex::response::Response { + let response = wrapping_0(s_0).await; + response + } + async fn stage_1<'a>( + s_0: &'a pavex::router::AllowedMethods, + ) -> pavex::response::Response { + let response = handler(s_0).await; + response + } + async fn wrapping_0( + v0: &pavex::router::AllowedMethods, + ) -> pavex::response::Response { + let v1 = crate::route_2::Next0 { + s_0: v0, + next: stage_1, + }; + let v2 = pavex::middleware::Next::new(v1); + let v3 = pavex::middleware::wrap_noop(v2).await; + ::into_response(v3) + } + async fn handler(v0: &pavex::router::AllowedMethods) -> pavex::response::Response { + let v1 = pavex::router::default_fallback(v0).await; + ::into_response(v1) + } + struct Next0<'a, T> + where + T: std::future::Future, + { + s_0: &'a pavex::router::AllowedMethods, + next: fn(&'a pavex::router::AllowedMethods) -> T, + } + impl<'a, T> std::future::IntoFuture for Next0<'a, T> + where + T: std::future::Future, + { + type Output = pavex::response::Response; + type IntoFuture = T; + fn into_future(self) -> Self::IntoFuture { + (self.next)(self.s_0) + } + } +} \ No newline at end of file diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/diagnostics.dot b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/diagnostics.dot new file mode 100644 index 000000000..45c572ad1 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/diagnostics.dot @@ -0,0 +1,69 @@ +digraph "GET / - 0" { + 0 [ label = "0| app::B"] + 1 [ label = "1| crate::route_0::Next0(app::B) -> crate::route_0::Next0"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_0::Next0) -> pavex::middleware::Next"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::response::Response"] + 4 [ label = "4| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 0 -> 1 [ ] + 3 -> 4 [ ] +} +digraph "GET / - 1" { + 0 [ label = "0| app::B"] + 1 [ label = "1| app::a() -> app::A"] + 2 [ label = "2| app::handler_1(app::A, app::B) -> pavex::response::Response"] + 3 [ label = "3| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 0 -> 2 [ ] + 1 -> 2 [ ] + 2 -> 3 [ ] +} +digraph "GET /2 - 0" { + 0 [ label = "0| app::A1"] + 1 [ label = "1| app::B1"] + 2 [ label = "2| crate::route_1::Next0(app::B1, app::A1) -> crate::route_1::Next0"] + 3 [ label = "3| pavex::middleware::Next::new(crate::route_1::Next0) -> pavex::middleware::Next"] + 4 [ label = "4| pavex::middleware::wrap_noop(pavex::middleware::Next) -> pavex::response::Response"] + 5 [ label = "5| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 3 -> 4 [ ] + 2 -> 3 [ ] + 0 -> 2 [ ] + 1 -> 2 [ ] + 4 -> 5 [ ] +} +digraph "GET /2 - 1" { + 0 [ label = "0| app::B1"] + 1 [ label = "1| app::A1"] + 2 [ label = "2| app::handler_2(app::A1, app::B1) -> pavex::response::Response"] + 3 [ label = "3| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 0 -> 2 [ ] + 1 -> 2 [ ] + 2 -> 3 [ ] +} +digraph "* * - 0" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| crate::route_2::Next0(&'a pavex::router::AllowedMethods) -> crate::route_2::Next0<'a>"] + 2 [ label = "2| pavex::middleware::Next::new(crate::route_2::Next0<'a>) -> pavex::middleware::Next>"] + 3 [ label = "3| pavex::middleware::wrap_noop(pavex::middleware::Next>) -> pavex::response::Response"] + 4 [ label = "4| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 2 -> 3 [ ] + 1 -> 2 [ ] + 3 -> 4 [ ] + 0 -> 1 [ ] +} +digraph "* * - 1" { + 0 [ label = "0| &pavex::router::AllowedMethods"] + 1 [ label = "1| pavex::router::default_fallback(&pavex::router::AllowedMethods) -> pavex::response::Response"] + 2 [ label = "2| ::into_response(pavex::response::Response) -> pavex::response::Response"] + 1 -> 2 [ ] + 0 -> 1 [ ] +} +digraph app_state { + 0 [ label = "0| app::b1() -> app::B1"] + 1 [ label = "1| app::b() -> app::B"] + 2 [ label = "2| app::a1() -> app::A1"] + 3 [ label = "3| crate::ApplicationState(app::A1, app::B, app::B1) -> crate::ApplicationState"] + 0 -> 3 [ ] + 1 -> 3 [ ] + 2 -> 3 [ ] +} \ No newline at end of file diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/stderr.txt b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/stderr.txt new file mode 100644 index 000000000..ef4034661 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/expectations/stderr.txt @@ -0,0 +1,52 @@ +WARNING: + โš  `app::B` implements `Copy`, but its constructor, + โ”‚ `app::b`, is marked as `NeverClone`. + โ”‚ + โ”‚ โ•ญโ”€[blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs:52:1] + โ”‚ 52 โ”‚ bp.import(from![crate]); + โ”‚ 53 โ”‚ bp.singleton(f!(crate::b)).never_clone(); + โ”‚ ยท  โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€ + โ”‚ ยท โ•ฐโ”€โ”€ The constructor was registered here + โ”‚ 54 โ”‚ bp.request_scoped(f!(crate::a)).never_clone(); + โ”‚ โ•ฐโ”€โ”€โ”€โ”€ + โ”‚ help: Either set the cloning strategy to `CloneIfNecessary` or remove `Copy` + โ”‚ for `app::B` +WARNING: + โš  `app::A` implements `Copy`, but its constructor, + โ”‚ `app::a`, is marked as `NeverClone`. + โ”‚ + โ”‚ โ•ญโ”€[blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs:53:1] + โ”‚ 53 โ”‚ bp.singleton(f!(crate::b)).never_clone(); + โ”‚ 54 โ”‚ bp.request_scoped(f!(crate::a)).never_clone(); + โ”‚ ยท  โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€ + โ”‚ ยท โ•ฐโ”€โ”€ The constructor was registered here + โ”‚ 55 โ”‚ bp.route(GET, "/", f!(crate::handler_1)); + โ”‚ โ•ฐโ”€โ”€โ”€โ”€ + โ”‚ help: Either set the cloning strategy to `CloneIfNecessary` or remove `Copy` + โ”‚ for `app::A` +WARNING: + โš  `app::B1` implements `Copy`, but its constructor, + โ”‚ `app::b1`, is marked as `never_clone`. + โ”‚ + โ”‚ โ•ญโ”€[blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs:40:1] + โ”‚ 40 โ”‚ + โ”‚ 41 โ”‚ โ•ญโ”€โ–ถ #[pavex::singleton(never_clone)] + โ”‚ 42 โ”‚ โ”œโ”€โ–ถ pub fn b1() -> B1 { + โ”‚ ยท โ•ฐโ”€โ”€โ”€โ”€ The constructor was registered here + โ”‚ 43 โ”‚ todo!() + โ”‚ โ•ฐโ”€โ”€โ”€โ”€ + โ”‚ help: Either set the cloning strategy to `clone_if_necessary` or remove + โ”‚ `Copy` for `app::B1` +WARNING: + โš  `app::A1` implements `Copy`, but its constructor, + โ”‚ `app::a1`, is marked as `never_clone`. + โ”‚ + โ”‚ โ•ญโ”€[blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs:32:1] + โ”‚ 32 โ”‚ + โ”‚ 33 โ”‚ โ•ญโ”€โ–ถ #[pavex::singleton(never_clone)] + โ”‚ 34 โ”‚ โ”œโ”€โ–ถ pub fn a1() -> A1 { + โ”‚ ยท โ•ฐโ”€โ”€โ”€โ”€ The constructor was registered here + โ”‚ 35 โ”‚ todo!() + โ”‚ โ•ฐโ”€โ”€โ”€โ”€ + โ”‚ help: Either set the cloning strategy to `clone_if_necessary` or remove + โ”‚ `Copy` for `app::A1` \ No newline at end of file diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs new file mode 100644 index 000000000..de3e541d6 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/lib.rs @@ -0,0 +1,58 @@ +use pavex::blueprint::{from, router::GET, Blueprint}; +use pavex::response::Response; + +use pavex::f; + +#[derive(Copy, Clone)] +pub struct A; + +impl A { + pub fn new() -> Self { + Self + } +} + +pub fn a() -> A { + todo!() +} + +#[derive(Copy, Clone)] +pub struct B; + +pub fn b() -> B { + todo!() +} + +pub fn handler_1(_a: A, _b: B) -> Response { + todo!() +} + +#[derive(Copy, Clone)] +pub struct A1; + +#[pavex::singleton(never_clone)] +pub fn a1() -> A1 { + todo!() +} + +#[derive(Copy, Clone)] +pub struct B1; + +#[pavex::singleton(never_clone)] +pub fn b1() -> B1 { + todo!() +} + +pub fn handler_2(_a1: A1, _b1: B1) -> Response { + todo!() +} + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.import(from![crate]); + bp.singleton(f!(crate::b)).never_clone(); + bp.request_scoped(f!(crate::a)).never_clone(); + bp.route(GET, "/", f!(crate::handler_1)); + bp.route(GET, "/2", f!(crate::handler_2)); + bp +} diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/main.rs b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/main.rs new file mode 100644 index 000000000..4ad9cf809 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/src/main.rs @@ -0,0 +1,24 @@ +//! This code is generated by `pavex_test_runner`, +//! Do NOT modify it manually. +use app_cd2e094a::blueprint; +use pavex_cli_client::{Client, config::Color}; +use pavex_cli_client::commands::generate::GenerateError; + +fn main() -> Result<(), Box> { + let ui_test_dir: std::path::PathBuf = std::env::var("UI_TEST_DIR").unwrap().into(); + let outcome = Client::new() + .color(Color::Always) + .pavex_cli_path(std::env::var("PAVEX_TEST_CLI_PATH").unwrap().into()) + .generate(blueprint(), ui_test_dir.join("generated_app")) + .diagnostics_path("diagnostics.dot".into()) + .execute(); + match outcome { + Ok(_) => {}, + Err(GenerateError::NonZeroExitCode(_)) => { std::process::exit(1); } + Err(e) => { + eprintln!("Failed to invoke `pavex generate`.\n{:?}", e); + std::process::exit(1); + } + } + Ok(()) +} diff --git a/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/test_config.toml b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/test_config.toml new file mode 100644 index 000000000..52ccee9e9 --- /dev/null +++ b/libs/ui_tests/blueprint/common/a_warning_is_emitted_for_copyable_never_clone/test_config.toml @@ -0,0 +1,5 @@ +description = """When a type marked as NeverClone implements Copy, Pavex emits a warning.""" + +[expectations] +codegen = "pass" +lints = "fail"