Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Automatically scan the initializer body in `[pin_]init!` to conditionally generate field
accessors; this reduces stack bloat and prevents alignment errors on packed structs by
only creating references when a field is actually used.
- `#[pin_data]` now generates a `*Projection` struct similar to the `pin-project` crate.
- Add initializer code blocks to `[try_][pin_]init!` macros: make initializer
macros accept any number of `_: {/* arbitrary code */},` & make them run the
code at that point.
- Make the `[try_][pin_]init!` macros expose initialized fields via a `let`
binding as `&mut T` or `Pin<&mut T>` for later fields.
- Rewrote all proc-macros (`[pin_]init!`, `#[pin_data]`, `#[pinned_drop]`,
`derive([Maybe]Zeroable)`), using `syn` with better diagnostics.
`derive([Maybe]Zeroable)`), using `syn` with better diagnostics.
- `derive([Maybe]Zeroable)` now support tuple structs.
- `[pin_]init!` now supports attributes on fields (such as `#[cfg(...)]`).
- Add a `#[default_error(<type>)]` attribute to `[pin_]init!` to override the
Expand Down
2 changes: 1 addition & 1 deletion internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ proc-macro = true
[dependencies]
quote = "1.0"
proc-macro2 = "1.0"
syn = { version = "2.0.86", features = ["full", "parsing", "visit-mut"] }
syn = { version = "2.0.86", features = ["full", "parsing", "visit", "visit-mut"] }

[build-dependencies]
rustc_version = "0.4"
Expand Down
72 changes: 57 additions & 15 deletions internal/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@

use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use std::collections::HashSet;
use syn::{
braced,
parse::{End, Parse},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type,
token,
visit::{self, Visit},
Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type,
};

use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};

struct AccessorScanner<'a> {
field_names: HashSet<&'a Ident>,
used_fields: HashSet<Ident>,
}

impl<'a> Visit<'_> for AccessorScanner<'a> {
fn visit_ident(&mut self, i: &Ident) {
if self.field_names.contains(i) {
self.used_fields.insert(i.clone());
}
visit::visit_ident(self, i);
}
}

pub(crate) struct Initializer {
attrs: Vec<InitializerAttribute>,
this: Option<This>,
Expand Down Expand Up @@ -145,6 +162,27 @@ pub(crate) fn expand(
};
// `mixed_site` ensures that the data is not accessible to the user-controlled code.
let data = Ident::new("__data", Span::mixed_site());
let mut field_names = HashSet::new();
for field in &fields {
if let Some(ident) = field.kind.ident() {
field_names.insert(ident);
}
}
let mut scanner = AccessorScanner {
field_names,
used_fields: HashSet::new(),
};
for field in &fields {
match &field.kind {
InitializerKind::Value {
value: Some((_, expr)),
..
} => scanner.visit_expr(expr),
InitializerKind::Init { value, .. } => scanner.visit_expr(value),
InitializerKind::Code { block, .. } => scanner.visit_block(block),
_ => {}
}
}
let init_fields = init_fields(
&fields,
pinned,
Expand All @@ -153,6 +191,7 @@ pub(crate) fn expand(
.any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)),
&data,
&slot,
&scanner.used_fields,
);
let field_check = make_field_check(&fields, init_kind, &path);
Ok(quote! {{
Expand Down Expand Up @@ -239,6 +278,7 @@ fn init_fields(
generate_initialized_accessors: bool,
data: &Ident,
slot: &Ident,
used_fields: &HashSet<Ident>,
) -> TokenStream {
let mut guards = vec![];
let mut guard_attrs = vec![];
Expand Down Expand Up @@ -272,13 +312,14 @@ fn init_fields(
unsafe { &mut (*#slot).#ident }
}
};
let accessor = generate_initialized_accessors.then(|| {
quote! {
#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
}
});
let accessor = (generate_initialized_accessors && used_fields.contains(ident))
.then(|| {
quote! {
#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
}
});
quote! {
#(#attrs)*
{
Expand Down Expand Up @@ -326,13 +367,14 @@ fn init_fields(
},
)
};
let accessor = generate_initialized_accessors.then(|| {
quote! {
#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
}
});
let accessor = (generate_initialized_accessors && used_fields.contains(ident))
.then(|| {
quote! {
#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
}
});
quote! {
#(#attrs)*
{
Expand Down
30 changes: 30 additions & 0 deletions tests/packed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![allow(unknown_lints)]
#![allow(unexpected_cfgs)]

use core::convert::Infallible;
use pin_init::{pin_data, pin_init};

#[pin_data]
struct MagicStruct {
a: u32,
b: u32,
}

#[test]
fn test_scanner_skips_unused() {
let _ = pin_init!(MagicStruct {
a: 1,
b: 2,
} ? Infallible);
}

#[test]
fn test_scanner_finds_used() {
let _ = pin_init!(MagicStruct {
a: 1,
b: 2,
_: {
let _ = b;
}
} ? Infallible);
}
11 changes: 0 additions & 11 deletions tests/ui/compile-fail/init/packed_struct.rs

This file was deleted.

10 changes: 0 additions & 10 deletions tests/ui/compile-fail/init/packed_struct.stderr

This file was deleted.

Loading