Skip to content
Merged
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
45 changes: 45 additions & 0 deletions src/stored_projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ rustc_session::declare_lint! {
/// biject whenever a small table's rows differ, which is a property of
/// the table and not of the type; see `Val::decides`.
///
/// A field that is also assigned somewhere (`x.f = …`) is skipped: the
/// constructors are then not the only thing deciding it, and a value the
/// literals always pair one way may be re-paired later.
///
/// Silent on any type with an explicit `repr`, on foreign types, and
/// below `stored-projection-min-sites` construction sites.
pub STORED_PROJECTION,
Expand All @@ -31,6 +35,8 @@ rustc_session::declare_lint! {
pub struct StoredProjection {
min_sites: usize,
seen: HashMap<DefId, Vec<Site>>,
/// (variant, field) pairs written by an assignment rather than a literal.
assigned: HashMap<DefId, BTreeSet<Symbol>>,
}

rustc_session::impl_lint_pass!(StoredProjection => [STORED_PROJECTION]);
Expand All @@ -43,6 +49,7 @@ impl StoredProjection {
Self {
min_sites: config.stored_projection_min_sites.max(FLOOR),
seen: HashMap::new(),
assigned: HashMap::new(),
}
}
}
Expand Down Expand Up @@ -152,8 +159,43 @@ fn classify<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<Val> {
}
}

impl StoredProjection {
/// `s.f = …`, through any number of derefs and autoderefs: `f` of `s`'s
/// struct is decided by more than its literals.
fn note_assignment<'tcx>(&mut self, cx: &LateContext<'tcx>, place: &'tcx Expr<'tcx>) {
let mut place = place;
while let ExprKind::Unary(rustc_hir::UnOp::Deref, inner) | ExprKind::DropTemps(inner) =
place.kind
{
place = inner;
}
let ExprKind::Field(base, field) = place.kind else {
return;
};
let Some(adt) = cx
.typeck_results()
.expr_ty_adjusted(base)
.peel_refs()
.ty_adt_def()
else {
return;
};
if !adt.is_struct() || !adt.did().is_local() {
return;
}
self.assigned
.entry(adt.non_enum_variant().def_id)
.or_default()
.insert(field.name);
}
}

impl<'tcx> LateLintPass<'tcx> for StoredProjection {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Assign(place, ..) | ExprKind::AssignOp(_, place, _) = expr.kind {
self.note_assignment(cx, place);
return;
}
let ExprKind::Struct(qpath, fields, _) = expr.kind else {
return;
};
Expand Down Expand Up @@ -218,6 +260,9 @@ impl<'tcx> LateLintPass<'tcx> for StoredProjection {
for s in &sites[1..] {
common.retain(|k| s.fields.contains_key(k));
}
if let Some(assigned) = self.assigned.get(&did) {
common.retain(|f| !assigned.contains(f));
}
let common: Vec<Symbol> = common.into_iter().collect();
for i in 0..common.len() {
for j in (i + 1)..common.len() {
Expand Down
32 changes: 32 additions & 0 deletions ui/stored_projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,36 @@ pub fn records() -> [Record; 2] {
]
}

/// `owner` is filled in after construction, so the literals are not the only
/// thing deciding it: every `Detached` site starts it at `None`, and `attach`
/// pairs it with `Detached` anyway.
#[derive(Clone, Copy, PartialEq)]
pub enum Source {
Attached,
Detached,
}

pub struct Job {
source: Source,
owner: Option<u32>,
}

pub fn attached(owner: u32) -> Job {
Job {
source: Source::Attached,
owner: Some(owner),
}
}

pub fn detached() -> Job {
Job {
source: Source::Detached,
owner: None,
}
}

pub fn attach(job: &mut Job, owner: u32) {
job.owner = Some(owner);
}

fn main() {}
Loading