-
Notifications
You must be signed in to change notification settings - Fork 541
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a chapter on editions. #1835
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,336 @@ | ||
# Editions | ||
|
||
<!-- toc --> | ||
|
||
This chapter gives an overview of how Edition support works in rustc. | ||
This assumes that you are familiar with what Editions are (see the [Edition Guide]). | ||
|
||
[Edition Guide]: https://doc.rust-lang.org/edition-guide/ | ||
|
||
## Edition definition | ||
|
||
The `--edition` CLI flag specifies the edition to use for a crate. | ||
This can be accessed from [`Session::edition`]. | ||
There are convenience functions like [`Session::at_least_rust_2021`] for checking the crate's | ||
edition, though you should be careful about whether you check the global session or the span, see | ||
[Edition hygiene] below. | ||
|
||
As an alternative to the `at_least_rust_20xx` convenience methods, the [`Edition`] type also | ||
supports comparisons for doing range checks, such as `span.edition() >= Edition::Edition2021`. | ||
|
||
[`Session::edition`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.edition | ||
[`Session::at_least_rust_2021`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.at_least_rust_2021 | ||
[`Edition`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/edition/enum.Edition.html | ||
|
||
### Adding a new edition | ||
|
||
Adding a new edition mainly involves adding a variant to the [`Edition`] enum and then fixing | ||
everything that is broken. See [#94461](https://github.com/rust-lang/rust/pull/94461) for an | ||
example. | ||
|
||
### Features and Edition stability | ||
|
||
The [`Edition`] enum defines whether or not an edition is stable. | ||
If it is not stable, then the `-Zunstable-options` CLI option must be passed to enable it. | ||
|
||
When adding a new feature, there are two options you can choose for how to handle stability with a | ||
future edition: | ||
|
||
- Just check the edition of the span like `span.at_least_rust_20xx()` (see [Edition hygiene]) or the | ||
[`Session::edition`]. This will implicitly depend on the stability of the edition itself to | ||
indicate that your feature is available. | ||
- Place your new behavior behind a [feature gate]. | ||
|
||
It may be sufficient to only check the current edition for relatively simple changes. | ||
However, for larger language changes, you should consider creating a feature gate. | ||
There are several benefits to using a feature gate: | ||
|
||
- A feature gate makes it easier to work on and experiment with a new feature. | ||
- It makes the intent clear when the `#![feature(…)]` attribute is used that your new feature is | ||
being enabled. | ||
- It makes testing of editions easier so that features that are not yet complete do not interfere | ||
with testing of edition-specific features that are complete and ready. | ||
- It decouples the feature from an edition, which makes it easier for the team to make a deliberate | ||
decision of whether or not a feature should be added to the next edition when the feature is | ||
ready. | ||
|
||
When a feature is complete and ready, the feature gate can be removed (and the code should just | ||
check the span or `Session` edition to determine if it is enabled). | ||
|
||
There are a few different options for doing feature checks: | ||
|
||
- For highly experimental features, that may or may not be involved in an edition, they can | ||
implement regular feature gates like `tcx.features().my_feature`, and ignore editions for the time | ||
being. | ||
|
||
- For experimental features that *might* be involved in an edition, they should implement gates with | ||
`tcx.features().my_feature && span.at_least_rust_20xx()`. | ||
This requires the user to still specify `#![feature(my_feature)]`, to avoid disrupting testing of | ||
other edition features which are ready and have been accepted within the edition. | ||
|
||
- For experimental features that have graduated to definitely be part of an edition, | ||
they should implement gates with `tcx.features().my_feature || span.at_least_rust_20xx()`, | ||
or just remove the feature check altogether and just check `span.at_least_rust_20xx()`. | ||
|
||
If you need to do the feature gating in multiple places, consider placing the check in a single | ||
function so that there will only be a single place to update. For example: | ||
|
||
```rust,ignore | ||
// An example from Edition 2021 disjoint closure captures. | ||
|
||
fn enable_precise_capture(tcx: TyCtxt<'_>, span: Span) -> bool { | ||
tcx.features().capture_disjoint_fields || span.rust_2021() | ||
} | ||
``` | ||
|
||
See [Lints and stability](#lints-and-stability) below for more information about how lints handle | ||
stability. | ||
|
||
[feature gate]: ../feature-gates.md | ||
|
||
## Edition parsing | ||
|
||
For the most part, the lexer is edition-agnostic. | ||
Within [`StringReader`], tokens can be modified based on edition-specific behavior. | ||
For example, C-String literals like `c"foo"` are split into multiple tokens in editions before 2021. | ||
This is also where things like reserved prefixes are handled for the 2021 edition. | ||
|
||
Edition-specific parsing is relatively rare. One example is `async fn` which checks the span of the | ||
token to determine if it is the 2015 edition, and emits an error in that case. | ||
This can only be done if the syntax was already invalid. | ||
|
||
If you need to do edition checking in the parser, you will normally want to look at the edition of | ||
the token, see [Edition hygiene]. | ||
In some rare cases you may instead need to check the global edition from [`ParseSess::edition`]. | ||
|
||
Most edition-specific parsing behavior is handled with [migration lints] instead of in the parser. | ||
This is appropriate when there is a *change* in syntax (as opposed to new syntax). | ||
This allows the old syntax to continue to work on previous editions. | ||
The lint then checks for the change in behavior. | ||
On older editions, the lint pass should emit the migration lint to help with migrating to new | ||
editions. | ||
On newer editions, your code should emit a hard error with `emit_err` instead. | ||
For example, the deprecated `start...end` pattern syntax emits the | ||
[`ellipsis_inclusive_range_patterns`] lint on editions before 2021, and in 2021 is an hard error via | ||
the `emit_err` method. | ||
|
||
[`StringReader`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse/lexer/struct.StringReader.html | ||
[`ParseSess::edition`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/parse/struct.ParseSess.html#structfield.edition | ||
[`ellipsis_inclusive_range_patterns`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#ellipsis-inclusive-range-patterns | ||
|
||
### Keywords | ||
|
||
New keywords can be introduced across an edition boundary. | ||
This is implemented by functions like [`Symbol::is_used_keyword_conditional`], which rely on the | ||
ordering of how the keywords are defined. | ||
|
||
When new keywords are introduced, the [`keyword_idents`] lint should be updated so that automatic | ||
migrations can transition code that might be using the keyword as an identifier (see | ||
[`KeywordIdents`]). | ||
An alternative to consider is to implement the keyword as a weak keyword if the position it is used | ||
is sufficient to distinguish it. | ||
|
||
An additional option to consider is the `k#` prefix which was introduced in [RFC 3101]. | ||
This allows the use of a keyword in editions *before* the edition where the keyword is introduced. | ||
This is currently not implemented. | ||
|
||
[`Symbol::is_used_keyword_conditional`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html#method.is_used_keyword_conditional | ||
[`keyword_idents`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/allowed-by-default.html#keyword-idents | ||
[`KeywordIdents`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/builtin/struct.KeywordIdents.html | ||
[RFC 3101]: https://rust-lang.github.io/rfcs/3101-reserved_prefixes.html | ||
|
||
### Edition hygiene | ||
[edition hygiene]: #edition-hygiene | ||
|
||
Spans are marked with the edition of the crate that the span came from. | ||
See [Macro hygiene] in the Edition Guide for a user-centric description of what this means. | ||
|
||
You should normally use the edition from the token span instead of looking at the global `Session` | ||
edition. | ||
For example, use `span.edition().at_least_rust_2021()` instead of `sess.at_least_rust_2021()`. | ||
This helps ensure that macros behave correctly when used across crates. | ||
|
||
[Macro hygiene]: https://doc.rust-lang.org/nightly/edition-guide/editions/advanced-migrations.html#macro-hygiene | ||
|
||
## Lints | ||
|
||
Lints support a few different options for interacting with editions. | ||
Lints can be *future incompatible edition migration lints*, which are used to support | ||
[migrations][migration lints] to newer editions. | ||
Alternatively, lints can be [edition-specific](#edition-specific-lints), where they change their | ||
default level starting in a specific edition. | ||
|
||
### Migration lints | ||
[migration lints]: #migration-lints | ||
[migration lint]: #migration-lints | ||
|
||
*Migration lints* are used to migrate projects from one edition to the next. | ||
They are implemented with a `MachineApplicable` [suggestion](../diagnostics.md#suggestions) which | ||
will rewrite code so that it will **successfully compile in both the previous and the next | ||
edition**. | ||
For example, the [`keyword_idents`] lint will take identifiers that conflict with a new keyword to | ||
use the raw identifier syntax to avoid the conflict (for example changing `async` to `r#async`). | ||
|
||
Migration lints must be declared with the [`FutureIncompatibilityReason::EditionError`] or | ||
[`FutureIncompatibilityReason::EditionSemanticsChange`] [future-incompatible | ||
option](../diagnostics.md#future-incompatible-lints) in the lint declaration: | ||
|
||
```rust,ignore | ||
declare_lint! { | ||
pub KEYWORD_IDENTS, | ||
Allow, | ||
"detects edition keywords being used as an identifier", | ||
@future_incompatible = FutureIncompatibleInfo { | ||
reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018), | ||
reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>", | ||
}; | ||
} | ||
``` | ||
|
||
When declared like this, the lint is automatically added to the appropriate | ||
`rust-20xx-compatibility` lint group. | ||
When a user runs `cargo fix --edition`, cargo will pass the `--force-warn rust-20xx-compatibility` | ||
flag to force all of these lints to appear during the edition migration. | ||
Cargo also passes `--cap-lints=allow` so that no other lints interfere with the edition migration. | ||
|
||
Migration lints can be either `Allow` or `Warn` by default. | ||
If it is `Allow`, users usually won't see this warning unless they are doing an edition migration | ||
manually or there is a problem during the migration. | ||
Most migration lints are `Allow`. | ||
|
||
If it is `Warn` by default, users on all editions will see this warning. | ||
Only use `Warn` if you think it is important for everyone to be aware of the change, and to | ||
encourage people to update their code on all editions. | ||
Beware that new warn-by-default lint that hit many projects can be very disruptive and frustrating | ||
for users. | ||
You may consider switching an `Allow` to `Warn` several years after the edition stabilizes. | ||
This will only show up for the relatively small number of stragglers who have not updated to the new | ||
edition. | ||
|
||
[`keyword_idents`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/allowed-by-default.html#keyword-idents | ||
[`FutureIncompatibilityReason::EditionError`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.FutureIncompatibilityReason.html#variant.EditionError | ||
[`FutureIncompatibilityReason::EditionSemanticsChange`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.FutureIncompatibilityReason.html#variant.EditionSemanticsChange | ||
|
||
### Edition-specific lints | ||
|
||
Lints can be marked so that they have a different level starting in a specific edition. | ||
In the lint declaration, use the `@edition` marker: | ||
|
||
```rust,ignore | ||
declare_lint! { | ||
pub SOME_LINT_NAME, | ||
Allow, | ||
"my lint description", | ||
@edition Edition2024 => Warn; | ||
} | ||
``` | ||
|
||
Here, `SOME_LINT_NAME` defaults to `Allow` on all editions before 2024, and then becomes `Warn` | ||
afterwards. | ||
|
||
This should generally be used sparingly, as there are other options: | ||
|
||
- Small impact stylistic changes unrelated to an edition can just make the lint `Warn` on all | ||
editions. If you want people to adopt a different way to write things, then go ahead and commit to | ||
having it show up for all projects. | ||
|
||
Beware that if a new warn-by-default lint hits many projects, it can be very disruptive and | ||
frustrating for users. | ||
|
||
- Change the new style to be a hard error in the new edition, and use a [migration lint] to | ||
automatically convert projects to the new style. For example, | ||
[`ellipsis_inclusive_range_patterns`] is a hard error in 2021, and warns in all previous editions. | ||
|
||
Beware that these cannot be added after the edition stabilizes. | ||
|
||
- Migration lints can also change over time. | ||
For example, the migration lint can start out as `Allow` by default. | ||
For people performing the migration, they will automatically get updated to the new code. | ||
Then, after some years, the lint can be made to `Warn` in previous editions. | ||
|
||
For example [`anonymous_parameters`] was a 2018 Edition migration lint (and a hard-error in 2018) | ||
that was `Allow` by default in previous editions. | ||
Then, three years later, it was changed to `Warn` for all previous editions, so that all users got | ||
a warning that the style was being phased out. | ||
If this was a warning from the start, it would have impacted many projects and be very disruptive. | ||
By making it part of the edition, most users eventually updated to the new edition and were | ||
handled by the migration. | ||
Switching to `Warn` only impacted a few stragglers who did not update. | ||
|
||
[`ellipsis_inclusive_range_patterns`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#ellipsis-inclusive-range-patterns | ||
[`anonymous_parameters`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#anonymous-parameters | ||
|
||
### Lints and stability | ||
|
||
Lints can be marked as being unstable, which can be helpful when developing a new edition feature, | ||
and you want to test out a migration lint. | ||
The feature gate can be specified in the lint's declaration like this: | ||
|
||
```rust,ignore | ||
declare_lint! { | ||
pub SOME_LINT_NAME, | ||
Allow, | ||
"my cool lint", | ||
@feature_gate = sym::my_feature_name; | ||
} | ||
``` | ||
|
||
Then, the lint will only fire if the user has the appropriate `#![feature(my_feature_name)]`. | ||
Just beware that when it comes time to do crater runs testing the migration that the feature gate | ||
will need to be removed. | ||
|
||
Alternatively, you can implement an allow-by-default [migration lint] for an upcoming unstable | ||
edition without a feature gate. | ||
Although users may technically be able to enable the lint before the edition is stabilized, most | ||
will not notice the new lint exists, and it should not disrupt anything or cause any breakage. | ||
|
||
### Idiom lints | ||
|
||
In the 2018 edition, there was a concept of "idiom lints" under the `rust-2018-idioms` lint group. | ||
The concept was to have new idiomatic styles under a different lint group separate from the forced | ||
migrations under the `rust-2018-compatibility` lint group, giving some flexibility as to how people | ||
opt-in to certain edition changes. | ||
|
||
Overall this approach did not seem to work very well, | ||
and it is unlikely that we will use the idiom groups in the future. | ||
|
||
## Standard library changes | ||
|
||
### Preludes | ||
|
||
Each edition comes with a specific prelude of the standard library. | ||
These are implemented as regular modules in [`core::prelude`] and [`std::prelude`]. | ||
New items can be added to the prelude, just beware that this can conflict with user's pre-existing | ||
code. | ||
Usually a [migration lint] should be used to migrate existing code to avoid the conflict. | ||
For example, [`rust_2021_prelude_collisions`] is used to handle the collisions with the new traits | ||
in 2021. | ||
|
||
[`core::prelude`]: https://doc.rust-lang.org/core/prelude/index.html | ||
[`std::prelude`]: https://doc.rust-lang.org/std/prelude/index.html | ||
[`rust_2021_prelude_collisions`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/allowed-by-default.html#rust-2021-prelude-collisions | ||
|
||
### Customized language behavior | ||
|
||
Usually it is not possible to make breaking changes to the standard library. | ||
In some rare cases, the teams may decide that the behavior change is important enough to break this | ||
rule. | ||
The downside is that this requires special handling in the compiler to be able to distinguish when | ||
the old and new signatures or behaviors should be used. | ||
|
||
One example is the change in method resolution for [`into_iter()` of arrays][into-iter]. | ||
This was implemented with the `#[rustc_skip_array_during_method_dispatch]` attribute on the | ||
`IntoIterator` trait which then tells the compiler to consider an alternate trait resolution choice | ||
based on the edition. | ||
|
||
Another example is the [`panic!` macro changes][panic-macro]. | ||
This required defining multiple panic macros, and having the built-in panic macro implementation | ||
determine the appropriate way to expand it. | ||
This also included the [`non_fmt_panics`] [migration lint] to adjust old code to the new form, which | ||
required the `rustc_diagnostic_item` attribute to detect the usage of the panic macro. | ||
|
||
In general it is recommended to avoid these special cases except for very high value situations. | ||
|
||
[into-iter]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html | ||
[panic-macro]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html | ||
[`non_fmt_panics`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#non-fmt-panics |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could place this under the "# Contributing to Rust" section like the stability attr guide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! Posted #1838.