Skip to content
Draft
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
62 changes: 46 additions & 16 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
AttributeKind::Naked(..) => self.check_naked(hir_id, target),
AttributeKind::NonExhaustive(attr_span) => {
self.check_non_exhaustive(*attr_span, span, target, item)
self.check_non_exhaustive(hir_id, *attr_span, span, target, item)
}
AttributeKind::MayDangle(attr_span) => self.check_may_dangle(hir_id, *attr_span),
AttributeKind::Link(_, attr_span) => self.check_link(hir_id, *attr_span, target),
Expand Down Expand Up @@ -802,30 +802,60 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

/// Checks if the `#[non_exhaustive]` attribute on an `item` is valid.
/// Checks if the `#[non_exhaustive]` attribute on an `item` is valid and effective.
fn check_non_exhaustive(
&self,
hir_id: HirId,
attr_span: Span,
span: Span,
target: Target,
item: Option<&'tcx Item<'tcx>>,
) {
match target {
Target::Struct => {
if let hir::Item {
kind: hir::ItemKind::Struct(_, _, hir::VariantData::Struct { fields, .. }),
..
} = item.unwrap()
&& !fields.is_empty()
&& fields.iter().any(|f| f.default.is_some())
{
self.dcx().emit_err(diagnostics::NonExhaustiveWithDefaultFieldValues {
attr_span,
defn_span: span,
});
if matches!(target, Target::Enum | Target::Variant)
&& !self.tcx.effective_visibilities(()).is_reachable(hir_id.owner.def_id)
{
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
attr_span,
diagnostics::UnusedNonExhaustive::Unreachable,
);
} else if target == Target::Struct {
let (_, _, data) = item.unwrap().expect_struct();
let fields = data.fields();

if !fields.is_empty() && fields.iter().any(|f| f.default.is_some()) {
self.dcx().emit_err(diagnostics::NonExhaustiveWithDefaultFieldValues {
attr_span,
defn_span: span,
});
}

if !self.tcx.effective_visibilities(()).is_reachable(hir_id.owner.def_id) {
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
attr_span,
diagnostics::UnusedNonExhaustive::Unreachable,
);
return;
}

let mut spans = MultiSpan::from_span(attr_span);
for field in fields.iter() {
if !self.tcx.visibility(field.def_id).is_public() {
spans.push_primary_span(field.span);
}
}
_ => {}

if spans.primary_spans().len() > 1 {
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
spans,
diagnostics::UnusedNonExhaustive::StructWithNonPublicField,
);
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_passes/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ pub(crate) struct NonExhaustiveWithDefaultFieldValues {
pub defn_span: Span,
}

#[derive(Diagnostic)]
pub(crate) enum UnusedNonExhaustive {
#[diag("`#[non_exhaustive]` has no effect on an unreachable item")]
Unreachable,
#[diag("`#[non_exhaustive]` has no effect on a struct with non-public fields")]
#[note(
"non-public fields already prevent the struct from being constructed or exhaustively matched by downstream crates"
)]
StructWithNonPublicField,
}

#[derive(Diagnostic)]
#[diag("`#[doc(alias = \"...\")]` isn't allowed on {$location}")]
pub(crate) struct DocAliasBadLocation<'a> {
Expand Down
2 changes: 0 additions & 2 deletions library/core/src/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,11 @@ union MaybeEscapedCharacter<const N: usize> {
/// Marker type to indicate that the character is always escaped,
/// used to optimize the iterator implementation.
#[derive(Clone, Copy)]
#[non_exhaustive]
pub(crate) struct AlwaysEscaped;

/// Marker type to indicate that the character may be escaped,
/// used to optimize the iterator implementation.
#[derive(Clone, Copy)]
#[non_exhaustive]
pub(crate) struct MaybeEscaped;

/// An iterator over a possibly escaped character.
Expand Down
1 change: 0 additions & 1 deletion library/core/src/mem/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pub struct Type {
/// Info of a trait implementation, you can retrieve the vtable with [Self::get_vtable]
#[derive(Debug, PartialEq, Eq)]
#[unstable(feature = "type_info", issue = "146922")]
#[non_exhaustive]
pub struct TraitImpl<T: PointeeSized> {
pub(crate) vtable: DynMetadata<T>,
}
Expand Down
2 changes: 2 additions & 0 deletions library/coretests/tests/mem/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ fn test_structs() {
}

const {
#[expect(unused_attributes)]
#[non_exhaustive]
struct NonExhaustive {
a: u8,
Expand Down Expand Up @@ -224,6 +225,7 @@ fn test_enums() {
enum E {
Some(u32),
None,
#[expect(unused_attributes)]
#[non_exhaustive]
Foomp {
a: (),
Expand Down
1 change: 0 additions & 1 deletion library/proc_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ impl !Sync for TokenStream {}
/// The contained error message is explicitly not guaranteed to be stable in any way,
/// and may change between Rust versions or across compilations.
#[stable(feature = "proc_macro_lib", since = "1.15.0")]
#[non_exhaustive]
#[derive(Debug)]
pub struct LexError(String);

Expand Down
1 change: 0 additions & 1 deletion library/std/src/sys/process/unsupported.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ impl fmt::Debug for Command {
}

#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct ExitStatus();

impl ExitStatus {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/tests/fail/match/single_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// at least the semantics don't depend on the crate you're in.
//
// See: rust-lang/rust#147722
#![allow(dead_code)]
#![allow(dead_code, unused_attributes)]

#[repr(u8)]
enum Exhaustive {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/tests/fail/match/single_variant_uninit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// at least the semantics don't depend on the crate you're in.
//
// See: rust-lang/rust#147722
#![allow(dead_code)]
#![allow(dead_code, unused_attributes)]
#![allow(unreachable_patterns)]

#[repr(u8)]
Expand Down
1 change: 1 addition & 0 deletions tests/ui/attributes/malformed-attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ mod yooo {

#[non_exhaustive = 1]
//~^ ERROR malformed
//~^^ WARN `#[non_exhaustive]` has no effect on an unreachable item
enum Slenum {

}
Expand Down
29 changes: 18 additions & 11 deletions tests/ui/attributes/malformed-attrs.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ LL | #[cfg_attr(predicate, attr1, attr2, ...)]
| ++++++++++++++++++++++++++++++

error[E0463]: can't find crate for `wloop`
--> $DIR/malformed-attrs.rs:213:1
--> $DIR/malformed-attrs.rs:214:1
|
LL | extern crate wloop;
| ^^^^^^^^^^^^^^^^^^^ can't find crate
Expand Down Expand Up @@ -755,7 +755,7 @@ LL + #[non_exhaustive]
|

error[E0565]: malformed `thread_local` attribute input
--> $DIR/malformed-attrs.rs:205:3
--> $DIR/malformed-attrs.rs:206:3
|
LL | #[thread_local()]
| ^^^^^^^^^^^^--
Expand All @@ -769,7 +769,7 @@ LL + #[thread_local]
|

error[E0565]: malformed `no_link` attribute input
--> $DIR/malformed-attrs.rs:209:3
--> $DIR/malformed-attrs.rs:210:3
|
LL | #[no_link()]
| ^^^^^^^--
Expand All @@ -783,7 +783,7 @@ LL + #[no_link]
|

error[E0539]: malformed `macro_use` attribute input
--> $DIR/malformed-attrs.rs:211:3
--> $DIR/malformed-attrs.rs:212:3
|
LL | #[macro_use = 1]
| ^^^^^^^^^^---
Expand All @@ -801,7 +801,7 @@ LL + #[macro_use(name1, name2, ...)]
|

error[E0539]: malformed `macro_export` attribute input
--> $DIR/malformed-attrs.rs:216:3
--> $DIR/malformed-attrs.rs:217:3
|
LL | #[macro_export = 18]
| ^^^^^^^^^^^^^----
Expand All @@ -818,7 +818,7 @@ LL + #[macro_export(local_inner_macros)]
|

error[E0658]: the `allow_internal_unsafe` attribute side-steps the `unsafe_code` lint
--> $DIR/malformed-attrs.rs:218:3
--> $DIR/malformed-attrs.rs:219:3
|
LL | #[allow_internal_unsafe = 1]
| ^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -827,7 +827,7 @@ LL | #[allow_internal_unsafe = 1]
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error[E0565]: malformed `allow_internal_unsafe` attribute input
--> $DIR/malformed-attrs.rs:218:3
--> $DIR/malformed-attrs.rs:219:3
|
LL | #[allow_internal_unsafe = 1]
| ^^^^^^^^^^^^^^^^^^^^^^---
Expand All @@ -852,6 +852,14 @@ LL | | #[coroutine = 63] || {}
LL | | }
| |_- not a `const fn`

warning: `#[non_exhaustive]` has no effect on an unreachable item
--> $DIR/malformed-attrs.rs:199:1
|
LL | #[non_exhaustive = 1]
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: requested on the command line with `-W unused-attributes`

error: valid forms for the attribute are `doc = "string"`, `doc(alias)`, `doc(attribute)`, `doc(auto_cfg)`, `doc(cfg)`, `doc(fake_variadic)`, `doc(hidden)`, `doc(html_favicon_url)`, `doc(html_logo_url)`, `doc(html_no_source)`, `doc(html_playground_url)`, `doc(html_root_url)`, `doc(include)`, `doc(inline)`, `doc(issue_tracker_base_url)`, `doc(keyword)`, `doc(masked)`, `doc(no_default_passes)`, `doc(no_inline)`, `doc(notable_trait)`, `doc(passes)`, `doc(plugins)`, `doc(rust_logo)`, `doc(search_unbox)`, `doc(spotlight)`, and `doc(test)`
--> $DIR/malformed-attrs.rs:41:3
|
Expand Down Expand Up @@ -888,7 +896,6 @@ LL | | #[coroutine = 63] || {}
... |
LL | | }
| |_^
= note: requested on the command line with `-W unused-attributes`

error: valid forms for the attribute are `doc = "string"`, `doc(alias)`, `doc(attribute)`, `doc(auto_cfg)`, `doc(cfg)`, `doc(fake_variadic)`, `doc(hidden)`, `doc(html_favicon_url)`, `doc(html_logo_url)`, `doc(html_no_source)`, `doc(html_playground_url)`, `doc(html_root_url)`, `doc(include)`, `doc(inline)`, `doc(issue_tracker_base_url)`, `doc(keyword)`, `doc(masked)`, `doc(no_default_passes)`, `doc(no_inline)`, `doc(notable_trait)`, `doc(passes)`, `doc(plugins)`, `doc(rust_logo)`, `doc(search_unbox)`, `doc(spotlight)`, and `doc(test)`
--> $DIR/malformed-attrs.rs:79:3
Expand Down Expand Up @@ -965,7 +972,7 @@ LL | #[automatically_derived = 18]
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!

error: valid forms for the attribute are `ignore` and `ignore = "reason"`
--> $DIR/malformed-attrs.rs:225:3
--> $DIR/malformed-attrs.rs:226:3
|
LL | #[ignore = 1]
| ^^^^^^^^^^
Expand All @@ -984,7 +991,7 @@ LL | #[coroutine = 63] || {}
= note: expected unit type `()`
found coroutine `{coroutine@$DIR/malformed-attrs.rs:118:23: 118:25}`

error: aborting due to 74 previous errors; 8 warnings emitted
error: aborting due to 74 previous errors; 9 warnings emitted

Some errors have detailed explanations: E0308, E0463, E0539, E0565, E0658, E0805.
For more information about an error, try `rustc --explain E0308`.
Expand Down Expand Up @@ -1012,7 +1019,7 @@ LL | #[ignore()]

Future breakage diagnostic:
error: valid forms for the attribute are `ignore` and `ignore = "reason"`
--> $DIR/malformed-attrs.rs:225:3
--> $DIR/malformed-attrs.rs:226:3
|
LL | #[ignore = 1]
| ^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
*/

// Ignore non_exhaustive in the same crate
#[expect(unused_attributes)]
#[non_exhaustive]
enum L1 { A, B }

#[expect(unused_attributes)]
#[non_exhaustive]
enum L2 { C }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
error[E0004]: non-exhaustive patterns: `L1::B` not covered
--> $DIR/non-exhaustive-match.rs:28:25
--> $DIR/non-exhaustive-match.rs:30:25
|
LL | let _b = || { match l1 { L1::A => () } };
| ^^ pattern `L1::B` not covered
|
note: `L1` defined here
--> $DIR/non-exhaustive-match.rs:12:6
--> $DIR/non-exhaustive-match.rs:13:6
|
LL | enum L1 { A, B }
| ^^ - not covered
Expand All @@ -16,7 +16,7 @@ LL | let _b = || { match l1 { L1::A => (), L1::B => todo!() } };
| ++++++++++++++++++

error[E0004]: non-exhaustive patterns: type `E1` is non-empty
--> $DIR/non-exhaustive-match.rs:33:25
--> $DIR/non-exhaustive-match.rs:35:25
|
LL | let _d = || { match e1 {} };
| ^^
Expand All @@ -35,7 +35,7 @@ LL ~ } };
|

error[E0004]: non-exhaustive patterns: `_` not covered
--> $DIR/non-exhaustive-match.rs:35:25
--> $DIR/non-exhaustive-match.rs:37:25
|
LL | let _e = || { match e2 { E2::A => (), E2::B => () } };
| ^^ pattern `_` not covered
Expand All @@ -53,7 +53,7 @@ LL | let _e = || { match e2 { E2::A => (), E2::B => (), _ => todo!() } };
| ++++++++++++++

error[E0505]: cannot move out of `l2` because it is borrowed
--> $DIR/non-exhaustive-match.rs:42:22
--> $DIR/non-exhaustive-match.rs:44:22
|
LL | let _c = || { match l2 { L2::C => (), _ => () } };
| -- -- borrow occurs due to use in closure
Expand All @@ -66,7 +66,7 @@ LL | _c();
| -- borrow later used here

error[E0505]: cannot move out of `e3` because it is borrowed
--> $DIR/non-exhaustive-match.rs:48:22
--> $DIR/non-exhaustive-match.rs:50:22
|
LL | let _g = || { match e3 { E3::C => (), _ => () } };
| -- -- borrow occurs due to use in closure
Expand Down
1 change: 1 addition & 0 deletions tests/ui/feature-gates/feature-gate-cfg-target-compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
struct Foo(u64, u64);

#[cfg_attr(target(os = "linux"), non_exhaustive)] //~ ERROR compact `cfg(target(..))` is experimental
//~^ WARN `#[non_exhaustive]` has no effect on an unreachable item
struct Bar(u64, u64);

#[cfg(not(any(all(target(os = "linux")))))] //~ ERROR compact `cfg(target(..))` is experimental
Expand Down
14 changes: 11 additions & 3 deletions tests/ui/feature-gates/feature-gate-cfg-target-compact.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ LL | #[cfg_attr(target(os = "linux"), non_exhaustive)]
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error[E0658]: compact `cfg(target(..))` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-compact.rs:7:19
--> $DIR/feature-gate-cfg-target-compact.rs:8:19
|
LL | #[cfg(not(any(all(target(os = "linux")))))]
| ^^^^^^^^^^^^^^^^^^^^
Expand All @@ -29,7 +29,7 @@ LL | #[cfg(not(any(all(target(os = "linux")))))]
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error[E0658]: compact `cfg(target(..))` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-compact.rs:11:10
--> $DIR/feature-gate-cfg-target-compact.rs:12:10
|
LL | cfg!(target(os = "linux"));
| ^^^^^^^^^^^^^^^^^^^^
Expand All @@ -38,6 +38,14 @@ LL | cfg!(target(os = "linux"));
= help: add `#![feature(cfg_target_compact)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error: aborting due to 4 previous errors
warning: `#[non_exhaustive]` has no effect on an unreachable item
--> $DIR/feature-gate-cfg-target-compact.rs:4:34
|
LL | #[cfg_attr(target(os = "linux"), non_exhaustive)]
| ^^^^^^^^^^^^^^
|
= note: requested on the command line with `-W unused-attributes`

error: aborting due to 4 previous errors; 1 warning emitted

For more information about this error, try `rustc --explain E0658`.
Loading