Feature #22304
closedAdd rb_warn_to_remove_at() for deprecation warnings shown by default
Added by shugo (Shugo Maeda) 16 days ago. Updated 2 days ago.
Description
Deprecations such as #22205 and #22276 are scheduled in phases: a warning shown only when Warning[:deprecated] is enabled, then a warning shown by default, then the removal.
For the first phase, rb_warn_deprecated_to_remove_at(X.Y, fmt, suggest, ...) (#17432) prints "... is deprecated and will be removed in Ruby X.Y", and a RUBY_DEBUG build fails to compile when the version reaches X.Y.
For the second phase, there is no equivalent API. A warning with the :deprecated category is suppressed by default by definition, so the warning must be emitted with rb_warn without a category, and the message and the version check have to be written by hand.
I propose to add rb_warn_to_remove_at(X.Y, fmt, suggest, ...) to internal/error.h: the same message and compile-time check, emitted with rb_warn. The name does not contain "deprecated" because rb_warn_deprecated* means a warning gated by Warning[:deprecated].
Updated by shugo (Shugo Maeda) 16 days ago
Actions
#1
- Related to Feature #22205: Deprecate ruby2_keywords added
- Related to Bug #22276: alias in a module falls back to Object even in classes not inheriting from Object added
Updated by shugo (Shugo Maeda) 9 days ago
1Actions
#2
[ruby-core:126767]
In addition, how about a higher-level macro that takes both versions of the schedule?
Before 4.2 it warns only when Warning[:deprecated] is enabled, from 4.2 it warns by default via rb_warn_to_remove, and at 4.3 a RUBY_DEBUG build fails to compile. The version check is done at compile time, so the phase switches automatically on a version bump and cannot be forgotten.
#22276 and #22273 can use it in 4.1 already, with no behavior change until 4.2.
Updated by shugo (Shugo Maeda) 9 days ago
Actions
#3
[ruby-core:126771]
I've created a pull request: https://github.com/ruby/ruby/pull/18904
Updated by Dan0042 (Daniel DeLorme) 8 days ago
ยท Edited
Actions
#4
[ruby-core:126777]
I very much like the idea, but the issue with version numbers is that major version jumps break linear assumptions.
For example, in 2023 (Ruby 3.3), if we want to deprecate something in 3 years, we might target 3.6. But as Ruby jumped from 3.4 to 4.0, then 3.6 never exists; 4.1 is the actual 3-year mark.
Because versions don't scale linearly with time, specifying a target year (e.g., 2026) or a relative year offset (e.g., 3) in rb_warn_scheduled_deprecation is much safer.
This also enables a useful ENV flag to filter warnings by horizon:
- RUBY_DEPRECATION_HORIZON=5: Show all deprecations coming up in the next 5 years.
- RUBY_DEPRECATION_HORIZON=1: Only show warnings due immediately next version. (default?)
- RUBY_DEPRECATION_HORIZON=0: No warnings.
Updated by shugo (Shugo Maeda) 8 days ago
Actions
#5
[ruby-core:126778]
Thank you for the idea. I think the API should keep the version numbers, because the warning message has to tell users the version ("will be removed in Ruby 4.3"), and a year cannot be converted to a version at compile time.
A major version jump does not cause a silent problem. For example, when the version jumps from 3.4 to 4.0, a schedule such as (3.5, 3.6) is reached at once, and a RUBY_DEBUG build fails to compile at the removal check, exactly as rb_warn_deprecated_to_remove_at does today. So each schedule has to be reviewed and rewritten by hand at a major bump, which we should do anyway.
A horizon filter like RUBY_DEPRECATION_HORIZON sounds useful, but it is a separate run-time mechanism (an environment variable and a version comparison), so how about proposing it in a separate ticket?
Updated by Dan0042 (Daniel DeLorme) 8 days ago
Actions
#6
[ruby-core:126783]
shugo (Shugo Maeda) wrote in #note-5:
a year cannot be converted to a version at compile time.
I don't understand why converting a year to a version at compile time would be needed. We would need to compare year >= RUBY_RELEASE_YEAR at compile time for the hard removal check. But for runtime deprecation warnings, displaying the target version is just a simple year-to-version mapping in a central place. When a major version bump happens, we update that single mapping, instead of editing N places across the codebase where someone hardcoded the wrong version. That seems much less error-prone.
(Also, as a C habit, I have a bias toward passing integers rather than floats or strings.)
each schedule has to be reviewed and rewritten by hand at a major bump, which we should do anyway.
Sorry for the trouble, but could you explain why "we should do anyway"? I might be missing something here.
A horizon filter like
RUBY_DEPRECATION_HORIZON... how about proposing it in a separate ticket?
Fair enough! I can open a separate ticket for that. Though it only works if we switch to year-based targets, as version numbers make dynamic horizons tricky to calculate.
Updated by shugo (Shugo Maeda) 8 days ago
Actions
#7
[ruby-core:126786]
Dan0042 (Daniel DeLorme) wrote in #note-6:
Sorry for the trouble, but could you explain why "we should do anyway"? I might be missing something here.
A major version bump is a chance to introduce incompatibilities, so I think we should decide for each deprecation whether to keep the schedule or to remove it at the major version.
My intention was not to make the schedule fully automatic, but to make it fail-safe.
I also see two problems with years:
RUBY_RELEASE_YEARis the date of the commit or the release, so it advances with every patch release, and a compile-time check on it would fail on stable branches.- The yearly release is a convention, not a guarantee. If a release is delayed or skipped, a year-based schedule silently changes its meaning.
If we introduce a new macro for the compile-time check instead of RUBY_RELEASE_YEAR, a serial number like RUBY_MINOR_CODE, which is bumped for each minor version, may be better than a year.
Updated by Dan0042 (Daniel DeLorme) 6 days ago
Actions
#8
[ruby-core:126808]
A major version bump is a chance to introduce incompatibilities, so I think we should decide for each deprecation whether to keep the schedule or to remove it at the major version.
Ok I see, that makes sense. It might also be useful to say "deprecate this at v5.0" no matter what year it turns out to be.
So what do you think should happen to
2023-12-25 Ruby 3.3.0 new message: feature X will be deprecated in Ruby 3.6
2025-12-25 Ruby 4.0.0 changed to: feature X will be deprecated in Ruby 4.1
2026-03-26 Ruby 3.3.11 keep incorrect "3.6" message or update to "4.1" ?
Does the updated target version need to be backported to every branch ?
Updated by shugo (Shugo Maeda) 6 days ago
Actions
#9
[ruby-core:126810]
Dan0042 (Daniel DeLorme) wrote in #note-8:
Does the updated target version need to be backported to every branch ?
There is a similar case. Ruby 3.3 still says "Process::Status#& is deprecated and will be removed in Ruby 3.4", although the removal was postponed to 3.5 on master and 3.4 was released without removing it. The message was not backported. But I don't know whether that was a deliberate decision, so I would like to hear the opinion of the stable branch maintainers.
@hsbt (Hiroshi SHIBATA) @nagachika (Tomoyuki Chikanaga) @k0kubun (Takashi Kokubun) What do you think?
Updated by shugo (Shugo Maeda) 2 days ago
Actions
#10
- Status changed from Open to Closed
Applied in changeset git|611bb146b08978aacb26bf4a4e8390e699628350.
[Feature #22304] Add rb_warn_to_remove_at and rb_warn_scheduled_deprecation
rb_warn_deprecated_to_remove_at warns only when Warning[:deprecated]
is enabled, so a deprecation scheduled to warn by default in a later
version needed a hand-written rb_warn and a separate commit to switch.
rb_warn_to_remove_at emits the same message with rb_warn and no
category. rb_warn_scheduled_deprecation(X.Y, X.Z, ...) selects
between the two at compile time by RUBY_API_VERSION_CODE: gated by
Warning[:deprecated] before X.Y, shown by default from X.Y, and a
compile error in RUBY_DEBUG builds at X.Z, like the existing removal
check. ruby/version.h is now included unconditionally and RUBY_VERSION_SINCE
and the version-string macros are always defined for that, while the
compile-time checks and RBIMPL_TODO stay RUBY_DEBUG only. Both
functions are exported so that the new -test-/warning extension can
exercise both phases.
Use it for the two alias deprecations in rb_alias, which are scheduled
to warn by default in 4.2 and to be removed in 4.3.
Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com