From 306b4a202b24a60103423afaa61e5e5c3a25cc22 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 5 Feb 2025 16:53:34 +0900 Subject: [PATCH] tests: Test size of public types --- .github/workflows/ci.yml | 3 +- Cargo.toml | 2 + src/gen/tests/assert_impl.rs | 4 + src/gen/tests/track_size.rs | 28 +++ src/gen/tests/track_size.txt | 1 + src/lib.rs | 7 + tools/codegen/Cargo.toml | 5 +- tools/codegen/src/main.rs | 338 ++--------------------------------- 8 files changed, 64 insertions(+), 324 deletions(-) create mode 100644 src/gen/tests/track_size.rs create mode 100644 src/gen/tests/track_size.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f3f29a..b048c74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,8 +70,7 @@ jobs: - uses: taiki-e/install-action@cargo-minimal-versions - uses: taiki-e/install-action@cargo-careful if: matrix.rust == 'nightly' - - run: cargo test --workspace --all-features ${EXCLUDE} --tests --no-run - if: matrix.rust != 'nightly' + - run: cargo test --workspace --all-features ${EXCLUDE} --no-run - run: cargo test --workspace --all-features ${EXCLUDE} if: matrix.rust == 'nightly' - run: cargo careful test --workspace --all-features ${EXCLUDE} diff --git a/Cargo.toml b/Cargo.toml index 6494654..14b480f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ pin-project = "1.0.11" futures = { package = "futures-util", version = "0.3", default-features = false } rustversion = "1" static_assertions = "1" +test-helper = { features = ["git"], git = "https://github.com/taiki-e/test-helper.git", rev = "e8333e1" } trybuild = { git = "https://github.com/taiki-e/trybuild.git", branch = "dev" } # adjust overwrite behavior [lints] @@ -52,6 +53,7 @@ non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(careful)', ] } unnameable_types = "warn" unreachable_pub = "warn" diff --git a/src/gen/tests/assert_impl.rs b/src/gen/tests/assert_impl.rs index 19e8a02..a103811 100644 --- a/src/gen/tests/assert_impl.rs +++ b/src/gen/tests/assert_impl.rs @@ -13,8 +13,12 @@ fn assert_send() {} fn assert_sync() {} fn assert_unpin() {} +fn assert_unwind_safe() {} +fn assert_ref_unwind_safe() {} const _: fn() = || { assert_send::(); assert_sync::(); assert_unpin::(); + assert_unwind_safe::(); + assert_ref_unwind_safe::(); }; diff --git a/src/gen/tests/track_size.rs b/src/gen/tests/track_size.rs new file mode 100644 index 0000000..55b58d7 --- /dev/null +++ b/src/gen/tests/track_size.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// This file is @generated by futures-async-stream-internal-codegen +// (gen_track_size function at tools/codegen/src/main.rs). +// It is not intended for manual editing. + +#![cfg_attr(rustfmt, rustfmt::skip)] +#![allow(dead_code, clippy::std_instead_of_alloc, clippy::std_instead_of_core)] +use std::{fmt::Write as _, path::Path, string::String}; +fn write_size(out: &mut String) { + let _ = writeln!( + out, "{}: {}", std::any::type_name:: (), std::mem::size_of:: () + ); +} +/// Test the size of public types. This is not intended to keep a specific size and +/// is intended to be used only as a help in optimization. +/// +/// Ignore non-64-bit targets due to usize/ptr size, and ignore Miri/cargo-careful +/// as we set -Z randomize-layout for them. +#[test] +#[cfg_attr(any(not(target_pointer_width = "64"), miri, careful), ignore)] +fn track_size() { + let mut out = String::new(); + write_size::(&mut out); + test_helper::git::assert_diff( + Path::new(env!("CARGO_MANIFEST_DIR")).join("src/gen/tests/track_size.txt"), + out, + ); +} diff --git a/src/gen/tests/track_size.txt b/src/gen/tests/track_size.txt new file mode 100644 index 0000000..28b9031 --- /dev/null +++ b/src/gen/tests/track_size.txt @@ -0,0 +1 @@ +futures_async_stream::future::ResumeTy: 8 diff --git a/src/lib.rs b/src/lib.rs index fdc7e2c..d608feb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,6 +294,9 @@ mod tests; #[cfg(test)] #[path = "gen/tests/assert_impl.rs"] mod assert_impl; +#[cfg(test)] +#[path = "gen/tests/track_size.rs"] +mod track_size; #[doc(inline)] pub use futures_async_stream_macro::for_await; @@ -340,6 +343,10 @@ mod future { // SAFETY: see `Send` impl unsafe impl Sync for ResumeTy {} + // Needed to work around old nightly bug (fixed in nightly-2024-05-24 by https://github.com/rust-lang/rust/pull/125392) + impl core::panic::UnwindSafe for ResumeTy {} + impl core::panic::RefUnwindSafe for ResumeTy {} + /// Wrap a coroutine in a future. /// /// This function returns a `GenFuture` underneath, but hides it in `impl Trait` to give diff --git a/tools/codegen/Cargo.toml b/tools/codegen/Cargo.toml index 600513b..7089297 100644 --- a/tools/codegen/Cargo.toml +++ b/tools/codegen/Cargo.toml @@ -7,9 +7,8 @@ fs-err = "3" globset = { version = "0.4", default-features = false } prettyplease = "0.2" proc-macro2 = { version = "1", default-features = false } -quote = { version = "1", default-features = false } -syn = { version = "2", default-features = false, features = ["parsing", "printing", "full", "visit-mut"] } -test-helper = { features = ["git"], git = "https://github.com/taiki-e/test-helper.git", rev = "295ad29" } +syn = { version = "2", default-features = false, features = ["parsing", "printing", "full"] } +test-helper = { features = ["codegen"], git = "https://github.com/taiki-e/test-helper.git", rev = "e8333e1" } [lints] workspace = true diff --git a/tools/codegen/src/main.rs b/tools/codegen/src/main.rs index 3cd01d2..b685379 100644 --- a/tools/codegen/src/main.rs +++ b/tools/codegen/src/main.rs @@ -5,332 +5,32 @@ #[macro_use] mod file; -use std::{ - collections::{BTreeSet, HashSet}, - path::Path, -}; - -use fs_err as fs; -use quote::{format_ident, quote, ToTokens as _}; -use syn::visit_mut::{self, VisitMut}; - use crate::file::*; fn main() { gen_assert_impl(); + gen_track_size(); } fn gen_assert_impl() { - const NOT_SEND: &[&str] = &[]; - const NOT_SYNC: &[&str] = &[]; - const NOT_UNPIN: &[&str] = &[]; - // const NOT_UNWIND_SAFE: &[&str] = &["future::ResumeTy"]; - // const NOT_REF_UNWIND_SAFE: &[&str] = &["future::ResumeTy"]; - - let workspace_root = &workspace_root(); - let out_dir = &workspace_root.join("src/gen/tests"); - fs::create_dir_all(out_dir).unwrap(); - - let files: BTreeSet = test_helper::git::ls_files(workspace_root.join("src"), &["*.rs"]) - .into_iter() - .filter_map(|(file_name, path)| { - // Assertions are only needed for the library's public APIs. - if file_name == "main.rs" || file_name.starts_with("bin/") { - return None; - } - Some(path.to_string_lossy().into_owned()) - }) - .collect(); - - let mut tokens = quote! {}; - let mut visited_types = HashSet::new(); - let mut use_generics_helpers = false; - for f in &files { - let s = fs::read_to_string(f).unwrap(); - let mut ast = syn::parse_file(&s).unwrap(); - - let module = if f.ends_with("lib.rs") { - vec![] - } else { - let name = format_ident!("{}", Path::new(f).file_stem().unwrap().to_string_lossy()); - vec![name.into()] - }; - - // TODO: assert impl trait returned from public functions - ItemVisitor::new(module, |item, module| match item { - syn::Item::Struct(syn::ItemStruct { vis, ident, generics, .. }) - | syn::Item::Enum(syn::ItemEnum { vis, ident, generics, .. }) - | syn::Item::Union(syn::ItemUnion { vis, ident, generics, .. }) - | syn::Item::Type(syn::ItemType { vis, ident, generics, .. }) - if matches!(vis, syn::Visibility::Public(..)) => - { - let path_string = quote! { #(#module::)* #ident }.to_string().replace(' ', ""); - visited_types.insert(path_string.clone()); - - let has_generics = generics.type_params().count() != 0; - let has_lifetimes = generics.lifetimes().count() != 0; - assert_eq!( - generics.const_params().count(), - 0, - "gen_assert_impl doesn't support const generics yet; skipped `{path_string}`" - ); - - let lt = generics.lifetimes().map(|_| quote! { '_ }); - if has_generics { - let lt = quote! { #(#lt,)* }; - use_generics_helpers = true; - // Send & Sync & Unpin & UnwindSafe & RefUnwindSafe - let unit = generics.type_params().map(|_| quote! { () }); - let unit_generics = quote! { <#lt #(#unit),*> }; - // !Send & Sync - let not_send = generics.type_params().map(|_| quote! { NotSend }); - let not_send_generics = quote! { <#lt #(#not_send),*> }; - // Send & !Sync - let not_sync = generics.type_params().map(|_| quote! { NotSync }); - let not_sync_generics = quote! { <#lt #(#not_sync),*> }; - // !Unpin - let not_unpin = generics.type_params().map(|_| quote! { NotUnpin }); - let not_unpin_generics = quote! { <#lt #(#not_unpin),*> }; - // // !UnwindSafe - // let not_unwind_safe = generics.type_params().map(|_| quote! { NotUnwindSafe }); - // let not_unwind_safe_generics = quote! { <#lt #(#not_unwind_safe),*> }; - // // !RefUnwindSafe - // let not_ref_unwind_safe = - // generics.type_params().map(|_| quote! { NotRefUnwindSafe }); - // let not_ref_unwind_safe_generics = quote! { <#lt #(#not_ref_unwind_safe),*> }; - if NOT_SEND.contains(&path_string.as_str()) { - tokens.extend(quote! { - assert_not_send!(crate:: #(#module::)* #ident #unit_generics); - }); - } else { - tokens.extend(quote! { - assert_send::(); - assert_send::(); - assert_not_send!(crate:: #(#module::)* #ident #not_send_generics); - }); - } - if NOT_SYNC.contains(&path_string.as_str()) { - tokens.extend(quote! { - assert_not_sync!(crate:: #(#module::)* #ident #unit_generics); - }); - } else { - tokens.extend(quote! { - assert_sync::(); - assert_sync::(); - assert_not_sync!(crate:: #(#module::)* #ident #not_sync_generics); - }); - } - if NOT_UNPIN.contains(&path_string.as_str()) { - tokens.extend(quote! { - assert_not_unpin!(crate:: #(#module::)* #ident #unit_generics); - }); - } else { - tokens.extend(quote! { - assert_unpin::(); - assert_not_unpin!(crate:: #(#module::)* #ident #not_unpin_generics); - }); - } - // if NOT_UNWIND_SAFE.contains(&path_string.as_str()) { - // tokens.extend(quote! { - // assert_not_unwind_safe!(crate:: #(#module::)* #ident #unit_generics); - // }); - // } else { - // tokens.extend(quote! { - // assert_unwind_safe::(); - // assert_not_unwind_safe!( - // crate:: #(#module::)* #ident #not_unwind_safe_generics - // ); - // }); - // } - // if NOT_REF_UNWIND_SAFE.contains(&path_string.as_str()) { - // tokens.extend(quote! { - // assert_not_ref_unwind_safe!( - // crate:: #(#module::)* #ident #unit_generics - // ); - // }); - // } else { - // tokens.extend(quote! { - // assert_ref_unwind_safe::(); - // assert_not_ref_unwind_safe!( - // crate:: #(#module::)* #ident #not_ref_unwind_safe_generics - // ); - // }); - // } - } else { - let lt = if has_lifetimes { - quote! { <#(#lt),*> } - } else { - quote! {} - }; - if NOT_SEND.contains(&path_string.as_str()) { - tokens.extend(quote! { - assert_not_send!(crate:: #(#module::)* #ident #lt); - }); - } else { - tokens.extend(quote! { - assert_send::(); - }); - } - if NOT_SYNC.contains(&path_string.as_str()) { - tokens.extend(quote! { - assert_not_sync!(crate:: #(#module::)* #ident #lt); - }); - } else { - tokens.extend(quote! { - assert_sync::(); - }); - } - if NOT_UNPIN.contains(&path_string.as_str()) { - tokens.extend(quote! { - assert_not_unpin!(crate:: #(#module::)* #ident #lt); - }); - } else { - tokens.extend(quote! { - assert_unpin::(); - }); - } - // if NOT_UNWIND_SAFE.contains(&path_string.as_str()) { - // tokens.extend(quote! { - // assert_not_unwind_safe!(crate:: #(#module::)* #ident #lt); - // }); - // } else { - // tokens.extend(quote! { - // assert_unwind_safe::(); - // }); - // } - // if NOT_REF_UNWIND_SAFE.contains(&path_string.as_str()) { - // tokens.extend(quote! { - // assert_not_ref_unwind_safe!(crate:: #(#module::)* #ident #lt); - // }); - // } else { - // tokens.extend(quote! { - // assert_ref_unwind_safe::(); - // }); - // } - } - } - _ => {} - }) - .visit_file_mut(&mut ast); - } - - let mut use_macros = use_generics_helpers; - for (list, name) in &[ - (NOT_SEND, "NOT_SEND"), - (NOT_SYNC, "NOT_SYNC"), - (NOT_UNPIN, "NOT_UNPIN"), - // (NOT_UNWIND_SAFE, "NOT_UNWIND_SAFE"), - // (NOT_REF_UNWIND_SAFE, "NOT_REF_UNWIND_SAFE"), - ] { - use_macros |= !list.is_empty(); - for &ty in *list { - assert!(visited_types.contains(ty), "unknown type `{ty}` specified in {name} constant"); - } - } - - let mut out = quote! { - #![allow( - dead_code, - unused_macros, - clippy::std_instead_of_alloc, - clippy::std_instead_of_core, - )] - fn assert_send() {} - fn assert_sync() {} - fn assert_unpin() {} - // fn assert_unwind_safe() {} - // fn assert_ref_unwind_safe() {} - }; - if use_generics_helpers { - out.extend(quote! { - /// `Send` & `!Sync` - struct NotSync(core::cell::UnsafeCell<()>); - /// `!Send` & `Sync` - struct NotSend(std::sync::MutexGuard<'static, ()>); - /// `!Send` & `!Sync` - struct NotSendSync(*const ()); - /// `!Unpin` - struct NotUnpin(core::marker::PhantomPinned); - // /// `!UnwindSafe` - // struct NotUnwindSafe(&'static mut ()); - // /// `!RefUnwindSafe` - // struct NotRefUnwindSafe(core::cell::UnsafeCell<()>); - }); - } - if use_macros { - out.extend(quote! { - macro_rules! assert_not_send { - ($ty:ty) => { - static_assertions::assert_not_impl_all!($ty: Send); - }; - } - macro_rules! assert_not_sync { - ($ty:ty) => { - static_assertions::assert_not_impl_all!($ty: Sync); - }; - } - macro_rules! assert_not_unpin { - ($ty:ty) => { - static_assertions::assert_not_impl_all!($ty: Unpin); - }; - } - // macro_rules! assert_not_unwind_safe { - // ($ty:ty) => { - // static_assertions::assert_not_impl_all!($ty: std::panic::UnwindSafe); - // }; - // } - // macro_rules! assert_not_ref_unwind_safe { - // ($ty:ty) => { - // static_assertions::assert_not_impl_all!($ty: std::panic::RefUnwindSafe); - // }; - // } - }); - } - out.extend(quote! { - const _: fn() = || { - #tokens - }; - }); - write(function_name!(), out_dir.join("assert_impl.rs"), out).unwrap(); -} - -#[must_use] -struct ItemVisitor { - module: Vec, - f: F, -} - -impl ItemVisitor -where - F: FnMut(&mut syn::Item, &[syn::PathSegment]), -{ - fn new(module: Vec, f: F) -> Self { - Self { module, f } - } + let (path, out) = test_helper::codegen::gen_assert_impl( + &workspace_root(), + test_helper::codegen::AssertImplConfig { + exclude: &[], + not_send: &[], + not_sync: &[], + not_unpin: &[], + not_unwind_safe: &[], + not_ref_unwind_safe: &[], + }, + ); + write(function_name!(), path, out).unwrap(); } -impl VisitMut for ItemVisitor -where - F: FnMut(&mut syn::Item, &[syn::PathSegment]), -{ - fn visit_item_mut(&mut self, item: &mut syn::Item) { - match item { - syn::Item::Mod(item) => { - self.module.push(item.ident.clone().into()); - visit_mut::visit_item_mod_mut(self, item); - self.module.pop(); - } - syn::Item::Macro(item) => { - if let Ok(mut file) = syn::parse2::(item.mac.tokens.clone()) { - visit_mut::visit_file_mut(self, &mut file); - item.mac.tokens = file.into_token_stream(); - } - visit_mut::visit_item_macro_mut(self, item); - } - _ => { - (self.f)(item, &self.module); - visit_mut::visit_item_mut(self, item); - } - } - } +fn gen_track_size() { + let (path, out) = test_helper::codegen::gen_track_size( + &workspace_root(), + test_helper::codegen::TrackSizeConfig { exclude: &[] }, + ); + write(function_name!(), path, out).unwrap(); }