Skip to content

Private deprecated items should not be expected to have since #17893

Open

Description

@kpreid

Summary

deprecated_attributes_without_since (just added in #17609) currently applies to deprecated items that are not visible outside the crate. It should not, since such items are not part of the versioned API, and work addressing the deprecation would not have any necessary relationship to the version of the crate.

Lint Name

deprecated_attributes_without_since

Reproducer

I tried this code:

#![warn(clippy::deprecated_attributes_without_since)]
mod foo {
    #[deprecated]
    pub(crate) fn bar() {}
}

I saw this happen:

warning: `deprecated` attribute without specifying what version of the crate deprecated the item
 --> src/lib.rs:3:5
  |
3 |     #[deprecated]
  |     ^^^^^^^^^^^^^ help: add a version: `#[deprecated(since = /* version */)]`

I expected to see this happen: No warning

Version

1.101.0-nightly (2026-10-17 8006091eb71ae7d9e9bc)

Additional Labels

No response

Activity

  1. added
    C-bugCategory: Clippy is not doing the correct thing
    I-false-positiveIssue: The lint was triggered on code it shouldn't have
    on Oct 18, 2026
  2. osguzik commented on Oct 18, 2026

    @osguzik

    I'd like to work on this.
    @rustbot claim

  3. osguzik commented on Oct 19, 2026

    @osguzik

    Hey, so I traced the issue to deprecated_attributes_without_fields::check_fields. Would a simple visibility check be enough here, or would it be better to move the since check to a late pass?
    My understanding is that the first approach would still lint pub items inside private modules.

  4. kpreid commented on Oct 25, 2026

    @kpreid
    ContributorAuthor

    I don’t have enough experience with Clippy to say what the correct implementation is, but I think that it should respect effective visibility. For example, even without private modules involved:

    struct Foo;
    impl Foo {
        #[deprecated]
        pub fn bar(&self) {}
    }

    bar's written visibility is public but certainly should not trigger the lint, since Foo is not public.

  5. hashcatHitman commented on Oct 25, 2026

    @hashcatHitman
    Member

    I'm a little confused as to why this would be an issue, and kind of disagree that it's a false positive at all. The point of #[deprecated] is to communicate to people writing code. I see two ways to look at this:

    • You only care about communicating this information to end-users. In which case, why are you deprecating things that aren't part of your external API at all? Just remove them.
    • You want to communicate these things internally too. In which case, you might as well document what version the deprecation happened in. You're effectively treating yourself as an end-user. It's pedantic for a reason, yeah? You can always allow/expect cases you disagree with, or even just satisfy it with #[deprecated(since = "TBD")].

    In any case, if this is going to be treated as something to be "fixed", I'd ask that it be a configuration option rather than a change in behavior. I want the current behavior. I consider it the correct behavior, and I'd consider the proposed behavior a false-negative.

  6. Jarcho commented on Oct 25, 2026

    @Jarcho
    Contributor

    What version would an internal item even be deprecated in? Unlike public API items which have a first version containing the deprecation, private items are deprecated immediately in whatever commit added the attribute.

    The practical benefit is also lost for private items. As documentation it tells you which release notes to check for more info. For tooling it helps guide when it should complain about the deprecation. Neither of these apply to internal items. For everything else you have the reason field or just comments on the item. Both work for internal development notes.

  7. osguzik commented on Oct 25, 2026

    @osguzik

    @rustbot unclaim

  8. hashcatHitman commented on Oct 26, 2026

    @hashcatHitman
    Member

    What version would an internal item even be deprecated in? Unlike public API items which have a first version containing the deprecation, private items are deprecated immediately in whatever commit added the attribute.

    I'd say the same version that they would have been if they weren't private. It isn't exactly correct, as you said, but it's still giving you a rough idea of when the deprecation happened at a glance.

    The practical benefit is also lost for private items. As documentation it tells you which release notes to check for more info. For tooling it helps guide when it should complain about the deprecation. Neither of these apply to internal items. For everything else you have the reason field or just comments on the item. Both work for internal development notes.

    The practical benefit is that I don't have to do the whole song and dance with git blame and git tag --contains every time I'd like to figure out roughly when something was deprecated. I also wouldn't want that information to end up lost or confused through some series of history rewrites.

  9. kpreid commented on Oct 26, 2026

    @kpreid
    ContributorAuthor

    In which case, why are you deprecating things that aren't part of your external API at all? Just remove them.

    This supposes that there is a straightforward replacement for the deprecated item, rather than each call site requiring further engineering to stop needing the deprecated item. As I see it, the point of bothering to use #[deprecated] at all for private items is to keep track of the nontrivial cases.

    You want to communicate these things internally too. In which case, you might as well document what version the deprecation happened in.

    If the goal is to finish removing the item before the next published release, then this means one has to write “this has been deprecated since [next version, that hasn’t been published yet]”, which seems to me more like busywork than useful record-keeping.

    That said, I can see that other users might want since everywhere, and a configuration option seems like a reasonable solution.

  10. Jarcho commented on Oct 26, 2026

    @Jarcho
    Contributor

    The practical benefit is that I don't have to do the whole song and dance with git blame and git tag --contains every time I'd like to figure out roughly when something was deprecated. I also wouldn't want that information to end up lost or confused through some series of history rewrites.

    If all you want is to have a date then putting it in there and calling it a day works. If you're actually tracking the deprecation there are many options that do that better. That said, the bar for putting this behind a config option isn't really that high. It isn't hard to implement and it's straightforward to describe what the option does.

  11. kpreid commented on Oct 31, 2026

    @kpreid
    ContributorAuthor

    If all you want is to have a date then putting it in there and calling it a day works.

    Clippy denies that by default:

    #![allow(unused)]
    
    #[deprecated(since = "2026-10-31")]
    fn foo() {}
    error: the since field must contain a semver-compliant version
     --> src/lib.rs:3:14
      |
    3 | #[deprecated(since = "2026-10-31")]
      |              ^^^^^^^^^^^^^^^^^^^^
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/main/index.html#deprecated_semver
      = note: `#[deny(clippy::deprecated_semver)]` on by default
    

    RFC 1790 specifically mentions that

    since is defined to contain the version of the crate at the time of deprecating the item, following the semver scheme. Rustc does not know about versions, thus the content of this field is not checked (but will be by external lints, e.g. rust-clippy.

    So, deprecated_semver is working as intended — since is not intended be a date rather than a version. (The RFC does not mention anything about applying #[deprecated] to non-public items.) That leaves the following possibilities:

    • #[deprecated] simply should not be put on non-public items at all. (This seems worse than all the alternatives.)
    • since should always be filled with a version, even for private items. (@hashcatHitman’s position.)
    • since should not be required for private items. (My original position.)
    • deprecated_semver should allow since to be anything for private items, so that deprecated_attributes_without_since can be satisfied by a date or other non-semver string. (Making your suggestion work.)
    • Even more relaxed but optionally: a configuration option which disables both deprecated_semver and deprecated_attributes_without_since for private items. This is overkill for my original request, but consistently fits a frame of “do what you want if it’s private”.
  12. hashcatHitman commented on Oct 31, 2026

    @hashcatHitman
    Member

    You forgot the secret 6th option: write the date in calVer notation. I'm pretty sure it'd be accepted? But that could lead to problems later if we make lints actually use that information, as was the intention.

    So like this:

    #![allow(unused)]
    
    #[deprecated(since = "2026.10.31")]
    fn foo() {}

    (unless it actually checks that you have a tag matching... which would surprise me, but isn't impossible)

  13. added a commit that references this issue on Nov 1, 2026
  14. added
    I-nominatedIssue: Nominated to be discussed at the next Clippy meeting
    on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: Clippy is not doing the correct thingI-false-positiveIssue: The lint was triggered on code it shouldn't haveI-nominatedIssue: Nominated to be discussed at the next Clippy meetinggood first issueThese issues are a good way to get started with Clippy

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions