Skip to content

Rollup of 6 pull requests #101452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
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
24 changes: 24 additions & 0 deletions compiler/rustc_ast/src/token.rs
Original file line number Diff line number Diff line change
@@ -398,6 +398,30 @@ impl Token {
}
}

/// Returns `true` if the token can appear at the start of an pattern.
///
/// Shamelessly borrowed from `can_begin_expr`, only used for diagnostics right now.
pub fn can_begin_pattern(&self) -> bool {
match self.uninterpolate().kind {
Ident(name, is_raw) =>
ident_can_begin_expr(name, self.span, is_raw), // value name or keyword
| OpenDelim(Delimiter::Bracket | Delimiter::Parenthesis) // tuple or array
| Literal(..) // literal
| BinOp(Minus) // unary minus
| BinOp(And) // reference
| AndAnd // double reference
// DotDotDot is no longer supported
| DotDot | DotDotDot | DotDotEq // ranges
| Lt | BinOp(Shl) // associated path
| ModSep => true, // global path
Interpolated(ref nt) => matches!(**nt, NtLiteral(..) |
NtPat(..) |
NtBlock(..) |
NtPath(..)),
_ => false,
}
}

/// Returns `true` if the token can appear at the start of a type.
pub fn can_begin_type(&self) -> bool {
match self.uninterpolate().kind {
3 changes: 2 additions & 1 deletion compiler/rustc_codegen_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
@@ -386,7 +386,8 @@ pub fn from_fn_attrs<'ll, 'tcx>(
) {
let span = cx
.tcx
.get_attr(instance.def_id(), sym::target_feature)
.get_attrs(instance.def_id(), sym::target_feature)
.next()
.map_or_else(|| cx.tcx.def_span(instance.def_id()), |a| a.span);
let msg = format!(
"the target features {} must all be either enabled or disabled together",
3 changes: 3 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/parser.ftl
Original file line number Diff line number Diff line change
@@ -150,3 +150,6 @@ parser_dotdotdot = unexpected token: `...`
parser_left_arrow_operator = unexpected token: `<-`
.suggestion = if you meant to write a comparison against a negative value, add a space in between `<` and `-`
parser_remove_let = expected pattern, found `let`
.suggestion = remove the unnecessary `let` keyword
8 changes: 8 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
@@ -823,6 +823,14 @@ pub fn is_builtin_only_local(name: Symbol) -> bool {
BUILTIN_ATTRIBUTE_MAP.get(&name).map_or(false, |attr| attr.only_local)
}

pub fn is_valid_for_get_attr(name: Symbol) -> bool {
BUILTIN_ATTRIBUTE_MAP.get(&name).map_or(false, |attr| match attr.duplicates {
WarnFollowing | ErrorFollowing | ErrorPreceding | FutureWarnFollowing
| FutureWarnPreceding => true,
DuplicatesOk | WarnFollowingWordOnly => false,
})
}

pub static BUILTIN_ATTRIBUTE_MAP: LazyLock<FxHashMap<Symbol, &BuiltinAttribute>> =
LazyLock::new(|| {
let mut map = FxHashMap::default();
4 changes: 2 additions & 2 deletions compiler/rustc_feature/src/lib.rs
Original file line number Diff line number Diff line change
@@ -151,7 +151,7 @@ pub use active::{Features, ACTIVE_FEATURES, INCOMPATIBLE_FEATURES};
pub use builtin_attrs::AttributeDuplicates;
pub use builtin_attrs::{
deprecated_attributes, find_gated_cfg, is_builtin_attr_name, is_builtin_only_local,
AttributeGate, AttributeTemplate, AttributeType, BuiltinAttribute, GatedCfg,
BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
is_valid_for_get_attr, AttributeGate, AttributeTemplate, AttributeType, BuiltinAttribute,
GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
};
pub use removed::{REMOVED_FEATURES, STABLE_REMOVED_FEATURES};
6 changes: 5 additions & 1 deletion compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
@@ -2274,7 +2274,11 @@ impl<'tcx> TyCtxt<'tcx> {
}

pub fn get_attr(self, did: DefId, attr: Symbol) -> Option<&'tcx ast::Attribute> {
self.get_attrs(did, attr).next()
if cfg!(debug_assertions) && !rustc_feature::is_valid_for_get_attr(attr) {
bug!("get_attr: unexpected called with DefId `{:?}`, attr `{:?}`", did, attr);
} else {
self.get_attrs(did, attr).next()
}
}

/// Determines whether an item is annotated with an attribute.
8 changes: 8 additions & 0 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -705,6 +705,14 @@ pub(crate) struct LeftArrowOperator {
pub span: Span,
}

#[derive(SessionDiagnostic)]
#[diag(parser::remove_let)]
pub(crate) struct RemoveLet {
#[primary_span]
#[suggestion(applicability = "machine-applicable", code = "")]
pub span: Span,
}

// SnapshotParser is used to create a snapshot of the parser
// without causing duplicate errors being emitted when the `Parser`
// is dropped.
9 changes: 8 additions & 1 deletion compiler/rustc_parse/src/parser/pat.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{ForceCollect, Parser, PathStyle, TrailingToken};
use crate::parser::diagnostics::RemoveLet;
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
use rustc_ast::ptr::P;
@@ -320,7 +321,13 @@ impl<'a> Parser<'a> {
maybe_recover_from_interpolated_ty_qpath!(self, true);
maybe_whole!(self, NtPat, |x| x);

let lo = self.token.span;
let mut lo = self.token.span;

if self.token.is_keyword(kw::Let) && self.look_ahead(1, |tok| tok.can_begin_pattern()) {
self.bump();
self.sess.emit_err(RemoveLet { span: lo });
lo = self.token.span;
}

let pat = if self.check(&token::BinOp(token::And)) || self.token.kind == token::AndAnd {
self.parse_pat_deref(expected)?
2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/check/check.rs
Original file line number Diff line number Diff line change
@@ -1464,7 +1464,7 @@ fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, vs: &'tcx [hir::Variant<'tcx>], def_id: L
def.destructor(tcx); // force the destructor to be evaluated

if vs.is_empty() {
if let Some(attr) = tcx.get_attr(def_id.to_def_id(), sym::repr) {
if let Some(attr) = tcx.get_attrs(def_id.to_def_id(), sym::repr).next() {
struct_span_err!(
tcx.sess,
attr.span,
20 changes: 10 additions & 10 deletions compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
@@ -1811,16 +1811,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return true;
}
}
// Notably, we only point to params that are local to the
// item we're checking, since those are the ones we are able
// to look in the final `hir::PathSegment` for. Everything else
// would require a deeper search into the `qpath` than I think
// is worthwhile.
if let Some(param_to_point_at) = param_to_point_at
&& self.point_at_path_if_possible(error, def_id, param_to_point_at, qpath)
{
return true;
}
}
// Notably, we only point to params that are local to the
// item we're checking, since those are the ones we are able
// to look in the final `hir::PathSegment` for. Everything else
// would require a deeper search into the `qpath` than I think
// is worthwhile.
if let Some(param_to_point_at) = param_to_point_at
&& self.point_at_path_if_possible(error, def_id, param_to_point_at, qpath)
{
return true;
}
}
hir::ExprKind::MethodCall(segment, args, ..) => {
25 changes: 17 additions & 8 deletions library/std/src/io/stdio.rs
Original file line number Diff line number Diff line change
@@ -607,15 +607,24 @@ pub fn stdout() -> Stdout {
}
}

// Flush the data and disable buffering during shutdown
// by replacing the line writer by one with zero
// buffering capacity.
pub fn cleanup() {
// Flush the data and disable buffering during shutdown
// by replacing the line writer by one with zero
// buffering capacity.
// We use try_lock() instead of lock(), because someone
// might have leaked a StdoutLock, which would
// otherwise cause a deadlock here.
if let Some(lock) = STDOUT.get().and_then(ReentrantMutex::try_lock) {
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
let mut initialized = false;
let stdout = STDOUT.get_or_init(|| {
initialized = true;
ReentrantMutex::new(RefCell::new(LineWriter::with_capacity(0, stdout_raw())))
});

if !initialized {
// The buffer was previously initialized, overwrite it here.
// We use try_lock() instead of lock(), because someone
// might have leaked a StdoutLock, which would
// otherwise cause a deadlock here.
if let Some(lock) = stdout.try_lock() {
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
}
}
}

8 changes: 8 additions & 0 deletions src/test/ui/issues/issue-100631.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// issue #100631, make sure `TyCtxt::get_attr` only called by case that compiler
// can reasonably deal with multiple attributes.
// `repr` will use `TyCtxt::get_attrs` since it's `DuplicatesOk`.
#[repr(C)] //~ ERROR: unsupported representation for zero-variant enum [E0084]
#[repr(C)]
enum Foo {}

fn main() {}
12 changes: 12 additions & 0 deletions src/test/ui/issues/issue-100631.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0084]: unsupported representation for zero-variant enum
--> $DIR/issue-100631.rs:4:1
|
LL | #[repr(C)]
| ^^^^^^^^^^
LL | #[repr(C)]
LL | enum Foo {}
| -------- zero-variant enum

error: aborting due to previous error

For more information about this error, try `rustc --explain E0084`.
270 changes: 270 additions & 0 deletions src/test/ui/let-else/let-else-drop-order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// run-pass
// edition:2021
// check-run-results
//
// Drop order tests for let else
//
// Mostly this ensures two things:
// 1. That let and let else temporary drop order is the same.
// This is a specific design request: https://github.com/rust-lang/rust/pull/93628#issuecomment-1047140316
// 2. That the else block truly only runs after the
// temporaries have dropped.
//
// We also print some nice tables for an overview by humans.
// Changes in those tables are considered breakages, but the
// important properties 1 and 2 are also enforced by the code.
// This is important as it's easy to update the stdout file
// with a --bless and miss the impact of that change.

#![feature(let_else)]
#![allow(irrefutable_let_patterns)]

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone)]
struct DropAccountant(Rc<RefCell<Vec<Vec<String>>>>);

impl DropAccountant {
fn new() -> Self {
Self(Default::default())
}
fn build_droppy(&self, v: u32) -> Droppy<u32> {
Droppy(self.clone(), v)
}
fn build_droppy_enum_none(&self, _v: u32) -> ((), DroppyEnum<u32>) {
((), DroppyEnum::None(self.clone()))
}
fn new_list(&self, s: impl ToString) {
self.0.borrow_mut().push(vec![s.to_string()]);
}
fn push(&self, s: impl ToString) {
let s = s.to_string();
let mut accounts = self.0.borrow_mut();
accounts.last_mut().unwrap().push(s);
}
fn print_table(&self) {
println!();

let accounts = self.0.borrow();
let before_last = &accounts[accounts.len() - 2];
let last = &accounts[accounts.len() - 1];
let before_last = get_comma_list(before_last);
let last = get_comma_list(last);
const LINES: &[&str] = &[
"vanilla",
"&",
"&mut",
"move",
"fn(this)",
"tuple",
"array",
"ref &",
"ref mut &mut",
];
let max_len = LINES.iter().map(|v| v.len()).max().unwrap();
let max_len_before = before_last.iter().map(|v| v.len()).max().unwrap();
let max_len_last = last.iter().map(|v| v.len()).max().unwrap();

println!(
"| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
"construct", before_last[0], last[0]
);
println!("| {:-<max_len$} | {:-<max_len_before$} | {:-<max_len_last$} |", "", "", "");

for ((l, l_before), l_last) in
LINES.iter().zip(before_last[1..].iter()).zip(last[1..].iter())
{
println!(
"| {: <max_len$} | {: <max_len_before$} | {: <max_len_last$} |",
l, l_before, l_last,
);
}
}
#[track_caller]
fn assert_all_equal_to(&self, st: &str) {
let accounts = self.0.borrow();
let last = &accounts[accounts.len() - 1];
let last = get_comma_list(last);
for line in last[1..].iter() {
assert_eq!(line.trim(), st.trim());
}
}
#[track_caller]
fn assert_equality_last_two_lists(&self) {
let accounts = self.0.borrow();
let last = &accounts[accounts.len() - 1];
let before_last = &accounts[accounts.len() - 2];
for (l, b) in last[1..].iter().zip(before_last[1..].iter()) {
if !(l == b || l == "n/a" || b == "n/a") {
panic!("not equal: '{last:?}' != '{before_last:?}'");
}
}
}
}

fn get_comma_list(sl: &[String]) -> Vec<String> {
std::iter::once(sl[0].clone())
.chain(sl[1..].chunks(2).map(|c| c.join(",")))
.collect::<Vec<String>>()
}

struct Droppy<T>(DropAccountant, T);

impl<T> Drop for Droppy<T> {
fn drop(&mut self) {
self.0.push("drop");
}
}

#[allow(dead_code)]
enum DroppyEnum<T> {
Some(DropAccountant, T),
None(DropAccountant),
}

impl<T> Drop for DroppyEnum<T> {
fn drop(&mut self) {
match self {
DroppyEnum::Some(acc, _inner) => acc,
DroppyEnum::None(acc) => acc,
}
.push("drop");
}
}

macro_rules! nestings_with {
($construct:ident, $binding:pat, $exp:expr) => {
// vanilla:
$construct!($binding, $exp.1);

// &:
$construct!(&$binding, &$exp.1);

// &mut:
$construct!(&mut $binding, &mut ($exp.1));

{
// move:
let w = $exp;
$construct!(
$binding,
{
let w = w;
w
}
.1
);
}

// fn(this):
$construct!($binding, std::convert::identity($exp).1);
};
}

macro_rules! nestings {
($construct:ident, $binding:pat, $exp:expr) => {
nestings_with!($construct, $binding, $exp);

// tuple:
$construct!(($binding, 77), ($exp.1, 77));

// array:
$construct!([$binding], [$exp.1]);
};
}

macro_rules! let_else {
($acc:expr, $v:expr, $binding:pat, $build:ident) => {
let acc = $acc;
let v = $v;

macro_rules! let_else_construct {
($arg:pat, $exp:expr) => {
loop {
let $arg = $exp else {
acc.push("else");
break;
};
acc.push("body");
break;
}
};
}
nestings!(let_else_construct, $binding, acc.$build(v));
// ref &:
let_else_construct!($binding, &acc.$build(v).1);

// ref mut &mut:
let_else_construct!($binding, &mut acc.$build(v).1);
};
}

macro_rules! let_ {
($acc:expr, $binding:tt) => {
let acc = $acc;

macro_rules! let_construct {
($arg:pat, $exp:expr) => {{
let $arg = $exp;
acc.push("body");
}};
}
let v = 0;
{
nestings_with!(let_construct, $binding, acc.build_droppy(v));
}
acc.push("n/a");
acc.push("n/a");
acc.push("n/a");
acc.push("n/a");

// ref &:
let_construct!($binding, &acc.build_droppy(v).1);

// ref mut &mut:
let_construct!($binding, &mut acc.build_droppy(v).1);
};
}

fn main() {
let acc = DropAccountant::new();

println!(" --- matching cases ---");

// Ensure that let and let else have the same behaviour
acc.new_list("let _");
let_!(&acc, _);
acc.new_list("let else _");
let_else!(&acc, 0, _, build_droppy);
acc.assert_equality_last_two_lists();
acc.print_table();

// Ensure that let and let else have the same behaviour
acc.new_list("let _v");
let_!(&acc, _v);
acc.new_list("let else _v");
let_else!(&acc, 0, _v, build_droppy);
acc.assert_equality_last_two_lists();
acc.print_table();

println!();

println!(" --- mismatching cases ---");

acc.new_list("let else _ mismatch");
let_else!(&acc, 1, DroppyEnum::Some(_, _), build_droppy_enum_none);
acc.new_list("let else _v mismatch");
let_else!(&acc, 1, DroppyEnum::Some(_, _v), build_droppy_enum_none);
acc.print_table();
// This ensures that we always drop before visiting the else case
acc.assert_all_equal_to("drop,else");

acc.new_list("let else 0 mismatch");
let_else!(&acc, 1, 0, build_droppy);
acc.new_list("let else 0 mismatch");
let_else!(&acc, 1, 0, build_droppy);
acc.print_table();
// This ensures that we always drop before visiting the else case
acc.assert_all_equal_to("drop,else");
}
51 changes: 51 additions & 0 deletions src/test/ui/let-else/let-else-drop-order.run.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--- matching cases ---

| construct | let _ | let else _ |
| ------------ | --------- | ---------- |
| vanilla | drop,body | drop,body |
| & | body,drop | body,drop |
| &mut | body,drop | body,drop |
| move | drop,body | drop,body |
| fn(this) | drop,body | drop,body |
| tuple | n/a,n/a | drop,body |
| array | n/a,n/a | drop,body |
| ref & | body,drop | body,drop |
| ref mut &mut | body,drop | body,drop |

| construct | let _v | let else _v |
| ------------ | --------- | ----------- |
| vanilla | drop,body | drop,body |
| & | body,drop | body,drop |
| &mut | body,drop | body,drop |
| move | drop,body | drop,body |
| fn(this) | drop,body | drop,body |
| tuple | n/a,n/a | drop,body |
| array | n/a,n/a | drop,body |
| ref & | body,drop | body,drop |
| ref mut &mut | body,drop | body,drop |

--- mismatching cases ---

| construct | let else _ mismatch | let else _v mismatch |
| ------------ | ------------------- | -------------------- |
| vanilla | drop,else | drop,else |
| & | drop,else | drop,else |
| &mut | drop,else | drop,else |
| move | drop,else | drop,else |
| fn(this) | drop,else | drop,else |
| tuple | drop,else | drop,else |
| array | drop,else | drop,else |
| ref & | drop,else | drop,else |
| ref mut &mut | drop,else | drop,else |

| construct | let else 0 mismatch | let else 0 mismatch |
| ------------ | ------------------- | ------------------- |
| vanilla | drop,else | drop,else |
| & | drop,else | drop,else |
| &mut | drop,else | drop,else |
| move | drop,else | drop,else |
| fn(this) | drop,else | drop,else |
| tuple | drop,else | drop,else |
| array | drop,else | drop,else |
| ref & | drop,else | drop,else |
| ref mut &mut | drop,else | drop,else |
11 changes: 11 additions & 0 deletions src/test/ui/parser/unnecessary-let.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fn main() {
for let x of [1, 2, 3] {}
//~^ ERROR expected pattern, found `let`
//~| ERROR missing `in` in `for` loop

match 1 {
let 1 => {}
//~^ ERROR expected pattern, found `let`
_ => {}
}
}
20 changes: 20 additions & 0 deletions src/test/ui/parser/unnecessary-let.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: expected pattern, found `let`
--> $DIR/unnecessary-let.rs:2:9
|
LL | for let x of [1, 2, 3] {}
| ^^^ help: remove the unnecessary `let` keyword

error: missing `in` in `for` loop
--> $DIR/unnecessary-let.rs:2:15
|
LL | for let x of [1, 2, 3] {}
| ^^ help: try using `in` here instead

error: expected pattern, found `let`
--> $DIR/unnecessary-let.rs:7:9
|
LL | let 1 => {}
| ^^^ help: remove the unnecessary `let` keyword

error: aborting due to 3 previous errors

6 changes: 6 additions & 0 deletions src/test/ui/typeck/point-at-type-param-in-path-expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn foo<T: std::fmt::Display>() {}

fn main() {
let x = foo::<()>;
//~^ ERROR `()` doesn't implement `std::fmt::Display`
}
17 changes: 17 additions & 0 deletions src/test/ui/typeck/point-at-type-param-in-path-expr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/point-at-type-param-in-path-expr.rs:4:19
|
LL | let x = foo::<()>;
| ^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
note: required by a bound in `foo`
--> $DIR/point-at-type-param-in-path-expr.rs:1:11
|
LL | fn foo<T: std::fmt::Display>() {}
| ^^^^^^^^^^^^^^^^^ required by this bound in `foo`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
3 changes: 0 additions & 3 deletions triagebot.toml
Original file line number Diff line number Diff line change
@@ -316,9 +316,6 @@ Examples of `T-libs-api` changes:
* Changing observable runtime behavior of library APIs
"""

[mentions."library/proc_macro/src/bridge"]
cc = ["@rust-lang/wg-rls-2"]

[mentions."src/librustdoc/clean/types.rs"]
cc = ["@camelid"]