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
2 changes: 1 addition & 1 deletion .github/workflows/post-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Post merge analysis
on:
push:
branches:
- master
- main

jobs:
analysis:
Expand Down
12 changes: 6 additions & 6 deletions compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3465,14 +3465,9 @@ impl Drop for Buffy {

pub fn stderr_destination(color: ColorConfig) -> Destination {
let buffer_writer = std::io::stderr();
let choice = color.to_color_choice();
// We need to resolve `ColorChoice::Auto` before `Box`ing since
// `ColorChoice::Auto` on `dyn Write` will always resolve to `Never`
let choice = if matches!(choice, ColorChoice::Auto) {
AutoStream::choice(&buffer_writer)
} else {
choice
};
let choice = get_stderr_color_choice(color, &buffer_writer);
// On Windows we'll be performing global synchronization on the entire
// system for emitting rustc errors, so there's no need to buffer
// anything.
Expand All @@ -3487,6 +3482,11 @@ pub fn stderr_destination(color: ColorConfig) -> Destination {
}
}

pub fn get_stderr_color_choice(color: ColorConfig, stderr: &std::io::Stderr) -> ColorChoice {
let choice = color.to_color_choice();
if matches!(choice, ColorChoice::Auto) { AutoStream::choice(stderr) } else { choice }
}

/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
///
/// See #36178.
Expand Down
62 changes: 51 additions & 11 deletions compiler/rustc_mir_transform/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ struct Access {
/// When we encounter multiple statements at the same location, we only increase the liveness,
/// in order to avoid false positives.
live: bool,
/// Is this a direct access to the place itself, no projections, or to a field?
/// This helps distinguish `x = ...` from `x.field = ...`
is_direct: bool,
}

#[tracing::instrument(level = "debug", skip(tcx), ret)]
Expand Down Expand Up @@ -689,15 +692,17 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
|place: Place<'tcx>, kind, source_info: SourceInfo, live: &DenseBitSet<PlaceIndex>| {
if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) {
if !is_indirect(extra_projections) {
let is_direct = extra_projections.is_empty();
match assignments[index].entry(source_info) {
IndexEntry::Vacant(v) => {
let access = Access { kind, live: live.contains(index) };
let access = Access { kind, live: live.contains(index), is_direct };
v.insert(access);
}
IndexEntry::Occupied(mut o) => {
// There were already a sighting. Mark this statement as live if it
// was, to avoid false positives.
o.get_mut().live |= live.contains(index);
o.get_mut().is_direct &= is_direct;
}
}
}
Expand Down Expand Up @@ -781,7 +786,7 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
continue;
};
let source_info = body.local_decls[place.local].source_info;
let access = Access { kind, live: live.contains(index) };
let access = Access { kind, live: live.contains(index), is_direct: true };
assignments[index].insert(source_info, access);
}
}
Expand Down Expand Up @@ -875,6 +880,33 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
dead_captures
}

/// Check if a local is referenced in any reachable basic block.
/// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.
fn is_local_in_reachable_code(&self, local: Local) -> bool {
struct LocalVisitor {
target_local: Local,
found: bool,
}

impl<'tcx> Visitor<'tcx> for LocalVisitor {
fn visit_local(&mut self, local: Local, _context: PlaceContext, _location: Location) {
if local == self.target_local {
self.found = true;
}
}
}

let mut visitor = LocalVisitor { target_local: local, found: false };
for (bb, bb_data) in traversal::postorder(self.body) {
visitor.visit_basic_block_data(bb, bb_data);
if visitor.found {
return true;
}
}

false
}

/// Report fully unused locals, and forget the corresponding assignments.
fn report_fully_unused(&mut self) {
let tcx = self.tcx;
Expand Down Expand Up @@ -928,6 +960,10 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {

let statements = &mut self.assignments[index];
if statements.is_empty() {
if !self.is_local_in_reachable_code(local) {
continue;
}

let sugg = if from_macro {
errors::UnusedVariableSugg::NoSugg { span: def_span, name }
} else {
Expand Down Expand Up @@ -977,8 +1013,10 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
self.checked_places,
self.body,
) {
statements.clear();
continue;
statements.retain(|_, access| access.is_direct);
if statements.is_empty() {
continue;
}
}

let typo = maybe_suggest_typo();
Expand Down Expand Up @@ -1049,26 +1087,28 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {

let Some((name, decl_span)) = self.checked_places.names[index] else { continue };

// We have outstanding assignments and with non-trivial drop.
// This is probably a drop-guard, so we do not issue a warning there.
if maybe_drop_guard(
let is_maybe_drop_guard = maybe_drop_guard(
tcx,
self.typing_env,
index,
&self.ever_dropped,
self.checked_places,
self.body,
) {
continue;
}
);

// We probed MIR in reverse order for dataflow.
// We revert the vector to give a consistent order to the user.
for (source_info, Access { live, kind }) in statements.into_iter().rev() {
for (source_info, Access { live, kind, is_direct }) in statements.into_iter().rev() {
if live {
continue;
}

// If this place was dropped and has non-trivial drop,
// skip reporting field assignments.
if !is_direct && is_maybe_drop_guard {
continue;
}

// Report the dead assignment.
let Some(hir_id) = source_info.scope.lint_root(&self.body.source_scopes) else {
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use core::ops::ControlFlow;
use std::borrow::Cow;
use std::collections::hash_set;
use std::path::PathBuf;

use rustc_abi::ExternAbi;
use rustc_ast::ast::LitKind;
use rustc_ast::{LitIntType, TraitObjectSyntax};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::unord::UnordSet;
use rustc_errors::codes::*;
use rustc_errors::{
Expand Down Expand Up @@ -1927,11 +1928,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
if self.tcx.visibility(did).is_accessible_from(body_def_id, self.tcx) {
// don't suggest foreign `#[doc(hidden)]` types
if !did.is_local() {
while let Some(parent) = parent_map.get(&did) {
let mut previously_seen_dids: FxHashSet<DefId> = Default::default();
previously_seen_dids.insert(did);
while let Some(&parent) = parent_map.get(&did)
&& let hash_set::Entry::Vacant(v) =
previously_seen_dids.entry(parent)
{
if self.tcx.is_doc_hidden(did) {
return false;
}
did = *parent;
v.insert();
did = parent;
}
}
true
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_trait_selection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#![feature(associated_type_defaults)]
#![feature(box_patterns)]
#![feature(default_field_values)]
#![feature(hash_set_entry)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(iterator_try_reduce)]
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/collections/btree/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2414,7 +2414,7 @@ impl<K, V> Default for BTreeMap<K, V> {
#[stable(feature = "rust1", since = "1.0.0")]
impl<K: PartialEq, V: PartialEq, A: Allocator + Clone> PartialEq for BTreeMap<K, V, A> {
fn eq(&self, other: &BTreeMap<K, V, A>) -> bool {
self.iter().eq(other)
self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a == b)
}
}

Expand Down
96 changes: 96 additions & 0 deletions library/alloctests/tests/collections/eq_diff_len.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! Regression tests which fail if some collections' `PartialEq::eq` impls compare
//! elements when the collections have different sizes.
//! This behavior is not guaranteed either way, so regressing these tests is fine
//! if it is done on purpose.
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList};

/// This intentionally has a panicking `PartialEq` impl, to test that various
/// collections' `PartialEq` impls don't actually compare elements if their sizes
/// are unequal.
///
/// This is not advisable in normal code.
#[derive(Debug, Clone, Copy, Hash)]
struct Evil;

impl PartialEq for Evil {
fn eq(&self, _: &Self) -> bool {
panic!("Evil::eq is evil");
}
}
impl Eq for Evil {}

impl PartialOrd for Evil {
fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
Some(Ordering::Equal)
}
}

impl Ord for Evil {
fn cmp(&self, _: &Self) -> Ordering {
// Constructing a `BTreeSet`/`BTreeMap` uses `cmp` on the elements,
// but comparing it with with `==` uses `eq` on the elements,
// so Evil::cmp doesn't need to be evil.
Ordering::Equal
}
}

// check Evil works
#[test]
#[should_panic = "Evil::eq is evil"]
fn evil_eq_works() {
let v1 = vec![Evil];
let v2 = vec![Evil];

_ = v1 == v2;
}

// check various containers don't compare if their sizes are different

#[test]
fn vec_evil_eq() {
let v1 = vec![Evil];
let v2 = vec![Evil; 2];

assert_eq!(false, v1 == v2);
}

#[test]
fn hashset_evil_eq() {
let s1 = HashSet::from([(0, Evil)]);
let s2 = HashSet::from([(0, Evil), (1, Evil)]);

assert_eq!(false, s1 == s2);
}

#[test]
fn hashmap_evil_eq() {
let m1 = HashMap::from([(0, Evil)]);
let m2 = HashMap::from([(0, Evil), (1, Evil)]);

assert_eq!(false, m1 == m2);
}

#[test]
fn btreeset_evil_eq() {
let s1 = BTreeSet::from([(0, Evil)]);
let s2 = BTreeSet::from([(0, Evil), (1, Evil)]);

assert_eq!(false, s1 == s2);
}

#[test]
fn btreemap_evil_eq() {
let m1 = BTreeMap::from([(0, Evil)]);
let m2 = BTreeMap::from([(0, Evil), (1, Evil)]);

assert_eq!(false, m1 == m2);
}

#[test]
fn linkedlist_evil_eq() {
let m1 = LinkedList::from([Evil]);
let m2 = LinkedList::from([Evil; 2]);

assert_eq!(false, m1 == m2);
}
1 change: 1 addition & 0 deletions library/alloctests/tests/collections/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod binary_heap;
mod eq_diff_len;
10 changes: 5 additions & 5 deletions src/ci/citool/src/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ pub fn load_job_db(db: &str) -> anyhow::Result<JobDatabase> {
/// modulo certain carve-outs" in [`validate_job_database`].
///
/// This invariant is important to make sure that it's not easily possible (without modifying
/// `citool`) to have PRs with red PR-only CI jobs merged into `master`, causing all subsequent PR
/// `citool`) to have PRs with red PR-only CI jobs merged into `main`, causing all subsequent PR
/// CI runs to be red until the cause is fixed.
fn register_pr_jobs_as_auto_jobs(db: &mut JobDatabase) -> anyhow::Result<()> {
for pr_job in &db.pr_jobs {
Expand Down Expand Up @@ -273,7 +273,7 @@ pub enum RunType {
/// Merge attempt workflow
AutoJob,
/// Fake job only used for sharing Github Actions cache.
MasterJob,
MainJob,
}

/// Maximum number of custom try jobs that can be requested in a single
Expand Down Expand Up @@ -323,7 +323,7 @@ fn calculate_jobs(
(jobs, "try", &db.envs.try_env)
}
RunType::AutoJob => (db.auto_jobs.clone(), "auto", &db.envs.auto_env),
RunType::MasterJob => return Ok(vec![]),
RunType::MainJob => return Ok(vec![]),
};
let jobs = substitute_github_vars(jobs.clone())
.context("Failed to substitute GitHub context variables in jobs")?;
Expand Down Expand Up @@ -376,15 +376,15 @@ pub fn calculate_job_matrix(
eprintln!("Run type: {run_type:?}");

let jobs = calculate_jobs(&run_type, &db, channel)?;
if jobs.is_empty() && !matches!(run_type, RunType::MasterJob) {
if jobs.is_empty() && !matches!(run_type, RunType::MainJob) {
return Err(anyhow::anyhow!("Computed job list is empty"));
}

let run_type = match run_type {
RunType::PullRequest => "pr",
RunType::TryJob { .. } => "try",
RunType::AutoJob => "auto",
RunType::MasterJob => "master",
RunType::MainJob => "main",
};

eprintln!("Output");
Expand Down
2 changes: 1 addition & 1 deletion src/ci/citool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl GitHubContext {
Some(RunType::TryJob { job_patterns: patterns })
}
("push", "refs/heads/auto") => Some(RunType::AutoJob),
("push", "refs/heads/master") => Some(RunType::MasterJob),
("push", "refs/heads/main") => Some(RunType::MainJob),
_ => None,
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/ci/citool/tests/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ fn pr_jobs() {
}

#[test]
fn master_jobs() {
let stdout = get_matrix("push", "commit", "refs/heads/master");
fn main_jobs() {
let stdout = get_matrix("push", "commit", "refs/heads/main");
insta::assert_snapshot!(stdout, @r#"
jobs=[]
run_type=master
run_type=main
"#);
}

Expand Down
2 changes: 1 addition & 1 deletion src/ci/scripts/checkout-submodules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ci_dir=$(cd $(dirname $0) && pwd)/..
# On the beta channel we'll be automatically calculating the prerelease version
# via the git history, so unshallow our shallow clone from CI.
if [ "$(releaseChannel)" = "beta" ]; then
git fetch origin --unshallow beta master
git fetch origin --unshallow beta main
fi

function fetch_github_commit_archive {
Expand Down
Loading
Loading