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
42 changes: 34 additions & 8 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_hir_analysis::autoderef::{self, Autoderef};
use rustc_infer::infer::canonical::{Canonical, OriginalQueryValues, QueryResponse};
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferOk, TyCtxtInferExt};
use rustc_infer::traits::{ObligationCauseCode, PredicateObligation, query};
use rustc_lint::builtin::TRAIT_METHOD_ON_COERCED_NEVER_TYPE;
use rustc_macros::Diagnostic;
use rustc_middle::middle::stability;
use rustc_middle::ty::elaborate::supertrait_def_ids;
Expand Down Expand Up @@ -394,6 +395,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
#[diag("type annotations needed")]
struct MissingTypeAnnot;

#[derive(Diagnostic)]
#[diag("trait method call on a coerced never type")]
#[help("consider providing a type annotation")]
struct TraitMethodOnCoercedNeverType;

let mut orig_values = OriginalQueryValues::default();
let predefined_opaques_in_body = if self.next_trait_solver() {
self.tcx.mk_predefined_opaques_in_body_from_iter(
Expand Down Expand Up @@ -459,6 +465,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// If we encountered an `_` type or an error type during autoderef, this is
// ambiguous.
if let Some(bad_ty) = &steps.opt_bad_ty {
// Ended up encountering a type variable when doing autoderef,
// but it may not be a type variable after processing obligations
// in our local `FnCtxt`, so don't call `structurally_resolve_type`.
let ty = &bad_ty.ty;
let ty = self
.probe_instantiate_query_response(span, &orig_values, ty)
.unwrap_or_else(|_| span_bug!(span, "instantiating {:?} failed?", ty));
let ty = self.resolve_vars_if_possible(ty.value);

if is_suggestion.0 {
// Ambiguity was encountered during a suggestion. There's really
// not much use in suggesting methods in this case.
Expand All @@ -482,15 +497,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
span,
MissingTypeAnnot,
);
// If `ty` is an inference variable that was created by being adjusted from the never type,
// We demand the type to be equal to the never type, so we can probe the never type for methods
// (see https://github.com/rust-lang/rust/issues/143349)
} else if let ty::Infer(ty::TyVar(ty_id)) = *ty.kind()
&& let ty_id = self.root_var(ty_id)
&& let root_ty = Ty::new_var(self.tcx, ty_id)
&& self
.diverging_type_vars
.borrow()
.iter()
.any(|&candidate_id| self.root_var(candidate_id) == ty_id)
{
self.tcx.emit_node_span_lint(
TRAIT_METHOD_ON_COERCED_NEVER_TYPE,
scope_expr_id,
span,
TraitMethodOnCoercedNeverType,
);
self.demand_eqtype(span, root_ty, self.tcx.types.never);
} else {
// Ended up encountering a type variable when doing autoderef,
// but it may not be a type variable after processing obligations
// in our local `FnCtxt`, so don't call `structurally_resolve_type`.
let ty = &bad_ty.ty;
let ty = self
.probe_instantiate_query_response(span, &orig_values, ty)
.unwrap_or_else(|_| span_bug!(span, "instantiating {:?} failed?", ty));
let ty = self.resolve_vars_if_possible(ty.value);
let guar = match *ty.kind() {
_ if let Some(guar) = self.tainted_by_errors() => guar,
ty::Infer(ty::TyVar(_)) => {
Expand Down
33 changes: 33 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ declare_lint_pass! {
TEST_UNSTABLE_LINT,
TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
TRAIT_METHOD_ON_COERCED_NEVER_TYPE,
TRIVIAL_CASTS,
TRIVIAL_NUMERIC_CASTS,
TYVAR_BEHIND_RAW_POINTER,
Expand Down Expand Up @@ -5709,3 +5710,35 @@ declare_lint! {
report_in_deps: false,
};
}

declare_lint! {
/// The `trait_method_on_coerced_never_type` lint detects situations in which a never type, which
Copy link
Copy Markdown
Contributor Author

@JonathanBrouwer JonathanBrouwer May 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really unsure of what the correct terminology is, both for the lint definition and the diagnostic, so please give feedback on this

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the proper way to say it would be "method_call_on_diverging_infer_variable" and "detects situations in which a method is called on a value resulting from a never-to-any coercion, without necessary information to infer a type for it", but I'm not sure how much we want to surface this internal language...

/// was coerced to any, has a trait method on it.
///
/// ### Example
///
/// ```rust,no_run
/// fn main() {
/// let x = panic!();
/// x.clone();
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Calling trait methods on a coerced `!` was previously disallowed for the never type,
/// but it did work for empty enums such as `Infallible` since these don't coerce.
/// This means that changing the definition of `Infallible` to become a type alias to `!` (a long-term goal),
/// would break code that called a trait method on `Infallible`, in such a way that the `!` would coerce.
///
/// Therefore, to aid in the transition of changing `Infallible` to a type alias, this is temporarily allowed with a FCW.
Comment on lines +5731 to +5736
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a note that calling a method on ! is never useful (it's unreachable code) and propose removing the method call / using the never to any coercion.

Also mention that if people want to support MSRVs before Infallible = !, the proper fix is to match e {} as DesiredType.

pub TRAIT_METHOD_ON_COERCED_NEVER_TYPE,
Warn,
"detects trait method calls on an coerced never type",
@future_incompatible = FutureIncompatibleInfo {
reason: fcw!(FutureReleaseError #156047),
report_in_deps: true,
Copy link
Copy Markdown
Contributor Author

@JonathanBrouwer JonathanBrouwer May 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided on a lint with level Warn with report_in_deps: true. If something else is desired, let me know

View changes since the review

};
}
7 changes: 5 additions & 2 deletions tests/ui/never_type/basic/clone-never.rs
Copy link
Copy Markdown
Contributor Author

@JonathanBrouwer JonathanBrouwer May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this PR allows more code to compile on stable, for example see this test.

View changes since the review

Copy link
Copy Markdown
Contributor Author

@JonathanBrouwer JonathanBrouwer May 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a problem we could delay the problem by putting this change behind the never_type feature gate

Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! regression test for issue #2151
//@ check-pass
// Regression test for https://github.com/rust-lang/rust/issues/143349

fn main() {
let x = panic!(); //~ ERROR type annotations needed
let x = panic!();
x.clone();
//~^ WARN [trait_method_on_coerced_never_type]
//~| WARN previously accepted
}
31 changes: 20 additions & 11 deletions tests/ui/never_type/basic/clone-never.stderr
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
error[E0282]: type annotations needed
--> $DIR/clone-never.rs:4:9
warning: trait method call on a coerced never type
--> $DIR/clone-never.rs:6:7
|
LL | let x = panic!();
| ^
LL | x.clone();
| - type must be known at this point
| ^^^^^
|
help: consider giving `x` an explicit type
|
LL | let x: /* Type */ = panic!();
| ++++++++++++
= help: consider providing a type annotation
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #156047 <https://github.com/rust-lang/rust/issues/156047>
= note: `#[warn(trait_method_on_coerced_never_type)]` (part of `#[warn(future_incompatible)]`) on by default

warning: 1 warning emitted

error: aborting due to 1 previous error
Future incompatibility report: Future breakage diagnostic:
warning: trait method call on a coerced never type
--> $DIR/clone-never.rs:6:7
|
LL | x.clone();
| ^^^^^
|
= help: consider providing a type annotation
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #156047 <https://github.com/rust-lang/rust/issues/156047>
= note: `#[warn(trait_method_on_coerced_never_type)]` (part of `#[warn(future_incompatible)]`) on by default

For more information about this error, try `rustc --explain E0282`.
24 changes: 24 additions & 0 deletions tests/ui/never_type/basic/method-on-never.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ check-pass
// Regression test for https://github.com/rust-lang/rust/issues/143349

#![feature(never_type)]

trait Trait {
fn method(&self);
}
impl Trait for ! {
fn method(&self) {
todo!()
}
}

fn main() {
let x = loop {};
x.method();
//~^ WARN [trait_method_on_coerced_never_type]
//~| WARN previously accepted

{ loop {} }.method();
//~^ WARN [trait_method_on_coerced_never_type]
//~| WARN previously accepted
}
47 changes: 47 additions & 0 deletions tests/ui/never_type/basic/method-on-never.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
warning: trait method call on a coerced never type
--> $DIR/method-on-never.rs:17:7
|
LL | x.method();
| ^^^^^^
|
= help: consider providing a type annotation
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #156047 <https://github.com/rust-lang/rust/issues/156047>
= note: `#[warn(trait_method_on_coerced_never_type)]` (part of `#[warn(future_incompatible)]`) on by default

warning: trait method call on a coerced never type
--> $DIR/method-on-never.rs:21:17
|
LL | { loop {} }.method();
| ^^^^^^
|
= help: consider providing a type annotation
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #156047 <https://github.com/rust-lang/rust/issues/156047>

warning: 2 warnings emitted

Future incompatibility report: Future breakage diagnostic:
warning: trait method call on a coerced never type
--> $DIR/method-on-never.rs:17:7
|
LL | x.method();
| ^^^^^^
|
= help: consider providing a type annotation
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #156047 <https://github.com/rust-lang/rust/issues/156047>
= note: `#[warn(trait_method_on_coerced_never_type)]` (part of `#[warn(future_incompatible)]`) on by default

Future breakage diagnostic:
warning: trait method call on a coerced never type
--> $DIR/method-on-never.rs:21:17
|
LL | { loop {} }.method();
| ^^^^^^
|
= help: consider providing a type annotation
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #156047 <https://github.com/rust-lang/rust/issues/156047>
= note: `#[warn(trait_method_on_coerced_never_type)]` (part of `#[warn(future_incompatible)]`) on by default

Loading