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
7 changes: 4 additions & 3 deletions compiler/rustc_middle/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,16 +925,17 @@ impl DynCompatibilityViolationSolution {
err.span_suggestion(
add_self_sugg.1,
format!(
"consider turning `{name}` into a method by giving it a `&self` argument"
"consider turning `{name}` into a method by giving it a `&self` \
argument, so that it is accessible through the trait object's vtable"
),
add_self_sugg.0,
Applicability::MaybeIncorrect,
);
err.span_suggestion(
make_sized_sugg.1,
format!(
"alternatively, consider constraining `{name}` so it does not apply to \
trait objects"
"alternatively, consider constraining `{name}` so it is explicitly \
marked as not applying to trait objects"
),
make_sized_sugg.0,
Applicability::MaybeIncorrect,
Expand Down
189 changes: 171 additions & 18 deletions compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2536,34 +2536,187 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
span: Span,
trait_pred: ty::PolyTraitClause<'tcx>,
) -> bool {
if !trait_pred.self_ty().skip_binder().is_unit() {
return false;
}
let node = self.tcx.hir_node_by_def_id(obligation.cause.body_def_id);
if let hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn {sig, body: body_id, .. }, .. }) = node
if let hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn { sig, body: body_id, .. }, ..
}) = node
&& let hir::ExprKind::Block(blk, _) = &self.tcx.hir_body(*body_id).value.kind
&& sig.decl.output.span().overlaps(span)
&& blk.expr.is_none()
&& trait_pred.self_ty().skip_binder().is_unit()
&& let Some(stmt) = blk.stmts.last()
&& let Some(candidate) = self.removable_trailing_semicolon(blk, obligation, trait_pred)
{
// A function body has a single return type, so keeping the value can't break any
// other use of it.
self.emit_semicolon_removal_suggestion(
err,
trait_pred,
candidate,
Applicability::MachineApplicable,
);
return true;
}
self.suggest_semicolon_removal_in_closure_arg(obligation, err, trait_pred)
}

/// If the value of `block` is discarded by a trailing semicolon and keeping it would satisfy
/// `trait_pred`, return that statement, its expression and the type of that expression.
fn removable_trailing_semicolon(
&self,
block: &hir::Block<'tcx>,
obligation: &PredicateObligation<'tcx>,
trait_pred: ty::PolyTraitClause<'tcx>,
) -> Option<(&'tcx hir::Stmt<'tcx>, &'tcx hir::Expr<'tcx>, Ty<'tcx>)> {
if block.expr.is_none()
&& let Some(stmt) = block.stmts.last()
&& let hir::StmtKind::Semi(expr) = stmt.kind
&& !stmt.span.from_expansion()
&& !matches!(expr.kind, hir::ExprKind::Err(_))
// Only suggest this if the expression behind the semicolon implements the predicate
&& let Some(typeck_results) = &self.typeck_results
&& let Some(ty) = typeck_results.expr_ty_opt(expr)
&& let Some(ty) =
typeck_results.expr_ty_opt(expr).map(|ty| self.resolve_vars_if_possible(ty))
&& self.predicate_may_hold(&self.mk_trait_obligation_with_new_self_ty(
obligation.param_env, trait_pred.map_bound(|trait_pred| (trait_pred, ty))
))
{
err.span_label(
expr.span,
format!(
"this expression has type `{}`, which implements `{}`",
ty,
trait_pred.print_modifiers_and_trait_path()
),
);
err.span_suggestion(
self.tcx.sess.source_map().end_point(stmt.span),
"remove this semicolon",
"",
Applicability::MachineApplicable,
Some((stmt, expr, ty))
} else {
None
}
}

fn emit_semicolon_removal_suggestion(
&self,
err: &mut Diag<'_>,
trait_pred: ty::PolyTraitClause<'tcx>,
(stmt, expr, ty): (&hir::Stmt<'_>, &hir::Expr<'_>, Ty<'tcx>),
applicability: Applicability,
) {
err.span_label(
expr.span,
format!(
"this expression has type `{}`, which implements `{}`",
ty,
trait_pred.print_modifiers_and_trait_path()
),
);
err.span_suggestion(
self.tcx.sess.source_map().end_point(stmt.span),
"remove this semicolon",
"",
applicability,
);
}

/// Detect when a closure argument returns `()` because of a trailing semicolon and that makes
/// a trait bound on the function's generic param fail, and suggest removing the semicolon:
///
/// ```text
/// fn bar<R: Bar>(_: impl Fn() -> R) {}
/// bar(|| { 5u8; })
/// // - help: remove this semicolon
/// ```
fn suggest_semicolon_removal_in_closure_arg(
&self,
obligation: &PredicateObligation<'tcx>,
err: &mut Diag<'_>,
trait_pred: ty::PolyTraitClause<'tcx>,
) -> bool {
let &ObligationCauseCode::WhereClauseInExpr(callee_def_id, _, hir_id, idx) =
obligation.cause.code().peel_derives()
else {
return false;
};
// This cause code is used for the clauses of any item named in an expression, like an
// associated const or a type alias, and `fn_sig` is only defined for functions.
if !matches!(self.tcx.def_kind(callee_def_id), DefKind::Fn | DefKind::AssocFn) {
return false;
}
let hir::Node::Expr(expr) = self.tcx.hir_node(hir_id) else {
return false;
};
let Some(typeck_results) = &self.typeck_results else {
return false;
};
let args: Vec<&hir::Expr<'_>> =
match expr.kind {
hir::ExprKind::MethodCall(_, receiver, args, _) => {
iter::once(receiver).chain(args).collect()
}
// For a call like `bar(..)` the obligation is attached to the callee path expression,
// so the arguments live in its parent.
_ => match self.tcx.parent_hir_node(expr.hir_id) {
hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Call(callee, args), ..
}) if callee.hir_id == expr.hir_id => args.iter().collect(),
_ => return false,
},
};
// Work with the callee's own clauses and signature, where the failing bound is still
// written in terms of the generic param it was declared on.
let clauses = self.tcx.clauses_of(callee_def_id).instantiate_identity(self.tcx);
let Some(ty::ClauseKind::Trait(failed)) = clauses
.clauses
.get(idx)
.map(|clause| clause.as_ref().skip_norm_wip().kind().skip_binder())
else {
return false;
};
let sig =
self.tcx.fn_sig(callee_def_id).instantiate_identity().skip_norm_wip().skip_binder();
let mut candidates = args.into_iter().enumerate().filter_map(|(i, arg)| {
// The bound has to be on what the closure returns. A bound on an unrelated param that
// also happened to be inferred as `()` would not be satisfied by removing a semicolon.
let declared = sig.inputs().get(i)?.peel_refs();
if !clauses.clauses.iter().any(|clause| {
matches!(
clause.as_ref().skip_norm_wip().kind().skip_binder(),
ty::ClauseKind::Projection(proj)
if self.tcx.is_lang_item(proj.def_id(), LangItem::FnOnceOutput)
&& proj.projection_term.self_ty() == declared
&& proj.term.as_type() == Some(failed.self_ty())
)
}) {
return None;
}
// The error can be reported while the closure argument is still being checked, before
// its own type is recorded, so identify closures syntactically and only fall back to
// the argument's type (e.g. for a closure bound to a variable and passed by path).
let closure_def_id = match arg.kind {
hir::ExprKind::Closure(closure) => closure.def_id,
_ => match typeck_results
.expr_ty_adjusted_opt(arg)
.map(|ty| *self.resolve_vars_if_possible(ty).peel_refs().kind())
{
Some(ty::Closure(def_id, _)) => def_id.as_local()?,
_ => return None,
},
};
let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(closure), .. }) =
self.tcx.hir_node_by_def_id(closure_def_id)
else {
return None;
};
let hir::ExprKind::Block(block, None) = self.tcx.hir_body(closure.body).value.kind
else {
return None;
};
self.removable_trailing_semicolon(block, obligation, trait_pred)
});
// Only emit the suggestion when a single closure argument matches, to avoid pointing at
// an unrelated closure.
if let Some(candidate) = candidates.next()
&& candidates.next().is_none()
{
// The same closure can be passed to somewhere else that expects it to return `()`,
// where keeping the value would introduce a new error.
self.emit_semicolon_removal_suggestion(
err,
trait_pred,
candidate,
Applicability::MaybeIncorrect,
);
return true;
}
Expand Down
28 changes: 21 additions & 7 deletions rust-bors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,34 @@ label_prefix = "ec2"
region = "us-east-2"
images = {
"x86_64ami" = "latest-gha-runner-ami"
"arm64ami" = "latest-gha-runner-ami-arm64"
}
jit_runner = "organization"
# Prices per hour of on-demand compute in us-east-2 (as of Aug 2026)
# See rough assessment of build speed for dist-x86_64-quick in https://github.com/rust-lang/simpleinfra/issues/1132
# m8a.2x 8 vCPU, 32 GB $0.48688/hr
# c8a.4x 16 vCPU, 32 GB $0.86216/hr
# c8a.8x 32 vCPU, 64 GB $1.72432/hr
# c8a.12x 48 vCPU, 96 GB $2.58648/hr
# CodeBuild 36 vCPU $4.78799/hr
allowed_instances = [
# AMD Zen 5 (x86_64) instances, a subset of these is used in production.
# Prices per hour of on-demand compute in us-east-2 (as of Aug 2026)
# See rough assessment of build speed for dist-x86_64-quick in https://github.com/rust-lang/simpleinfra/issues/1132
# m8a.2x 8 vCPU, 32 GB $0.48688/hr
# c8a.4x 16 vCPU, 32 GB $0.86216/hr
# c8a.8x 32 vCPU, 64 GB $1.72432/hr
# c8a.12x 48 vCPU, 96 GB $2.58648/hr
# CodeBuild 36 vCPU $4.78799/hr
"m8a.2xlarge",
"c8a.4xlarge",
"c8a.8xlarge",
"c8a.12xlarge",

# Graviton 4 (aarch64) instances, currently just for experimentation
"m8g.2xlarge",
"c8g.4xlarge",
"c8g.8xlarge",
"c8g.12xlarge",

# Graviton 5 (aarch64) instances, currently just for experimentation
"m9g.2xlarge",
"c9g.4xlarge",
"c9g.8xlarge",
"c9g.12xlarge",
]

# Enable unrolling of rollup member PRs after rollup merge
Expand Down
30 changes: 8 additions & 22 deletions src/ci/github-actions/jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@ runners:
free_disk: true
<<: *base-job

- &job-linux-4c-largedisk
os: ubuntu-24.04-4core-16gb
<<: *base-job

- &job-linux-8c
os: ubuntu-24.04-8core-32gb
<<: *base-job

- &job-linux-16c
os: ubuntu-24.04-16core-64gb
<<: *base-job

- &job-macos-15
os: macos-15 # macOS 15 Arm64
<<: *base-job
Expand Down Expand Up @@ -65,14 +53,12 @@ runners:
os: codebuild-ubuntu-22-36c-$github.run_id-$github.run_attempt
<<: *base-job

- &job-linux-8c-codebuild
free_disk: true
codebuild: true
os: codebuild-ubuntu-22-8c-$github.run_id-$github.run_attempt
- &job-linux-x86-32c-ec2
os: ec2-x86_64ami-c8a.8xlarge-x64-linux-$github.run_id-$github.run_attempt
<<: *base-job

- &job-linux-32c-ec2
os: ec2-x86_64ami-c8a.8xlarge-x64-linux-$github.run_id-$github.run_attempt
- &job-linux-x86-8c-ec2
os: ec2-x86_64ami-m8a.2xlarge-x64-linux-$github.run_id-$github.run_attempt
<<: *base-job

envs:
Expand Down Expand Up @@ -179,7 +165,7 @@ pr:
# These jobs automatically inherit envs.try, to avoid repeating
# it in each job definition.
try:
- <<: [*job-dist-x86_64-linux, *job-linux-32c-ec2]
- <<: [*job-dist-x86_64-linux, *job-linux-x86-32c-ec2]
name: dist-x86_64-linux-quick

# Jobs that only run when explicitly invoked in one of the following ways:
Expand All @@ -203,7 +189,7 @@ optional:
DIST_TRY_BUILD: 1
# We repeat the try job here so that it can be explicitly executed using `@bors try jobs`, to test
# full x64 Linux dist try builds on EC2.
- <<: [*job-dist-x86_64-linux, *job-linux-32c-ec2]
- <<: [*job-dist-x86_64-linux, *job-linux-x86-32c-ec2]
name: dist-x86_64-linux-quick

# Main CI jobs that have to be green to merge a commit into the default branch.
Expand Down Expand Up @@ -312,14 +298,14 @@ auto:
- name: dist-x86_64-illumos
<<: *job-linux-4c

- <<: [*job-dist-x86_64-linux, *job-linux-32c-ec2]
- <<: [*job-dist-x86_64-linux, *job-linux-x86-32c-ec2]

- name: dist-x86_64-linux-alt
env:
IMAGE: dist-x86_64-linux
CODEGEN_BACKENDS: llvm,cranelift
DOCKER_SCRIPT: dist-alt.sh
<<: *job-linux-8c
<<: *job-linux-x86-8c-ec2

- name: dist-x86_64-musl
env:
Expand Down
15 changes: 15 additions & 0 deletions tests/crashes/153005.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//@ known-bug: #153005
#![feature(non_lifetime_binders)]
#![feature(derive_coerce_pointee)]

#[derive(core::marker::CoercePointee)]
#[repr(transparent)]
struct _Ptr5<'a, #[pointee] T: ?Sized, X>
where
for<V> V: Sized,
{
data: &'a T,
x: core::marker::PhantomData<X>,
}

fn main() {}
6 changes: 6 additions & 0 deletions tests/crashes/153362.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//@ known-bug: #153362
struct ThinDst {
b: unsafe<> (),
}

const C1: &ThinDst = unsafe { std::mem::transmute(b"d".as_ptr()) };
15 changes: 15 additions & 0 deletions tests/crashes/153375.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//@ known-bug: #153375
//@ aux-build: aux153375.rs
extern crate aux153375;
use aux153375::Request;

struct Bar<'ws>(&'ws ());

impl<'ws> Request for Bar<'ws> {
type A<'a>
= u8
where
Self: 'a;

fn f(_: Self::A<'_>) -> impl Sized {}
}
10 changes: 10 additions & 0 deletions tests/crashes/153947.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//@ known-bug: #153947
#![expect(drop_bounds)]
pub struct Thing<T>(T) where [T]: Sized, Self: Drop;
impl<T> Drop for Thing<T> where [T]: Sized, Self: Drop {
fn drop(&mut self) {}
}
impl<T> Drop for Thing<T> where [T]: Sized, Self: Drop {
fn drop(&mut self) {}
}
fn main() {}
Loading
Loading