Skip to content

Commit 6e611ee

Browse files
committed
Add more rules about when expressions diverge
#2067 added a chapter on divergence along with rules for some of the more subtle expressions like `if` or `match`. This adds rules for almost all the rest of the expressions. Most of these are pretty straightforward propagation rules which could be left implicit. It may seem a little excessive to have a separate rule for each one, but I think the consistency is worth it because there are various subtleties. This also may help us be clear about these things when adding new expressions to the language to ensure we think about the divergence rules. This does not cover const expressions (const blocks, static, const, array repeat, etc.) because I still do not yet fully know how those should be documented (see #2153). I'm not entirely excited by having the long list of rules in the divergence chapter, mostly because of the length. However, I think it is helpful to cross-index these kinds of things. There are some interesting subtleties that I noticed: - `while` loops cannot diverge. It's a little surprising to me (I would expect the condition to allow it to diverge). I suspect this is due to the desugaring to a loop expression using `break`, which does not diverge. - Updated details for let-chains. - I couldn't think of a way for a `path` expression to refer to a never type on stable, so I left a comment in there to update it when never type is stabilized. I am writing this as-if the uninhabited-static lint is an error and not supported. References for the implementation: - Place expression with never must be read: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L318-L326 - `break` is never: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L819-L820 - `continue` is never: https://github.com/rust-lang/rust/blob/0376d43d443cba463a0b6a6ec9140ea17d7b7130/compiler/rustc_hir_typeck/src/expr.rs#L861 - `return` is never: https://github.com/rust-lang/rust/blob/0376d43d443cba463a0b6a6ec9140ea17d7b7130/compiler/rustc_hir_typeck/src/expr.rs#L921 - `if` divergence: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L1236-L1242 - `loop`: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L1450-L1456 - `asm!`: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/expr.rs#L3684 - `match`: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/_match.rs#L19-L178 - block break: https://github.com/rust-lang/rust/blob/59fd4ef94daa991e6797b5aa6127e824f3067def/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs#L1167-L1171 - lazy bool: https://github.com/rust-lang/rust/blob/0376d43d443cba463a0b6a6ec9140ea17d7b7130/compiler/rustc_hir_typeck/src/op.rs#L111 Closes #2152
1 parent 300e99f commit 6e611ee

21 files changed

Lines changed: 251 additions & 2 deletions

dev-guide/src/rules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ When assigning rules to new paragraphs or modifying rule names, use the followin
3333
- `intro`: The beginning paragraph of each section. It should explain the construct being defined overall.
3434
- `syntax`: Syntax definitions or explanations when BNF syntax definitions are not used.
3535
- `namespace`: For items only, specifies the namespace(s) the item introduces a name in. It may also be used elsewhere when defining a namespace (e.g., `r[attribute.diagnostic.namespace]`).
36+
- `diverging`: The divergence behavior of an expression. Be sure to update the Divergence chapter, too.
3637
6. When a rule doesn't fall under the above keywords, or for section rule IDs, name the subrule as follows:
3738
- If the rule names a specific Rust language construct (e.g., an attribute, standard library type/function, or keyword-introduced concept), use the construct as named in the language, appropriately case-adjusted (but do not replace `_`s with `-`s).
3839
- Other than Rust language concepts with `_`s in the name, use `-` characters to separate words within a "subrule".

src/divergence.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,47 @@ fn example() {
1717

1818
See the following rules for specific expression divergence behavior:
1919

20+
- [asm.diverging.naked_asm] --- `naked_asm!`.
21+
- [asm.diverging.noreturn] --- `noreturn` in `asm!`.
22+
- [expr.arith-logic.diverging] --- Arithmetic and logic expressions.
23+
- [expr.array.diverging] --- Array expressions.
24+
- [expr.array.index.diverging] --- Index expressions.
25+
- [expr.as.diverging] --- `as` expressions.
26+
- [expr.assign.diverging] --- Assignment expressions.
27+
- [expr.await.diverging] --- `.await` expressions.
28+
- [expr.block.async.diverging] --- Async block expressions.
2029
- [expr.block.diverging] --- Block expressions.
30+
- [expr.bool-logic.diverging] --- Lazy boolean expressions.
31+
- [expr.borrow.diverging] --- Borrow expressions.
32+
- [expr.call.diverging] --- Call expressions.
33+
- [expr.closure.diverging] --- Closure expressions.
34+
- [expr.cmp.diverging] --- Comparison expressions.
35+
- [expr.compound-assign.diverging] --- Compound assignment expressions.
36+
- [expr.deref.diverging] --- Dereference expressions.
37+
- [expr.field.diverging] --- Field expressions.
2138
- [expr.if.diverging] --- `if` expressions.
39+
- [expr.literal.diverging] --- Literal expressions.
2240
- [expr.loop.block-labels.type] --- Labeled block expressions with `break`.
2341
- [expr.loop.break-value.diverging] --- `loop` expressions with `break`.
2442
- [expr.loop.break.diverging] --- `break` expressions.
2543
- [expr.loop.continue.diverging] --- `continue` expressions.
44+
- [expr.loop.for.diverging] --- `for` expressions.
2645
- [expr.loop.infinite.diverging] --- Infinite `loop` expressions.
46+
- [expr.loop.while.diverging] --- `while` expressions.
2747
- [expr.match.diverging] --- `match` expressions.
2848
- [expr.match.empty] --- Empty `match` expressions.
49+
- [expr.method.diverging] --- Method call expressions.
50+
- [expr.negate.diverging] --- Negation expressions.
51+
- [expr.paren.diverging] --- Parenthesized expressions.
52+
- [expr.path.diverging] --- Path expressions.
53+
- [expr.placeholder.diverging] --- Underscore expressions.
54+
- [expr.range.diverging] --- Range expressions.
2955
- [expr.return.diverging] --- `return` expressions.
30-
- [type.never.constraint] --- Function calls returning `!`.
56+
- [expr.struct.diverging] --- Struct expressions.
57+
- [expr.try.diverging] --- Try propagation expressions.
58+
- [expr.tuple-index.diverging] --- Tuple indexing expressions.
59+
- [expr.tuple.diverging] --- Tuple expressions.
60+
- [statement.let.diverging] --- `let` statements.
3161

3262
> [!NOTE]
3363
> The [`panic!`] macro and related panic-generating macros like [`unreachable!`] also have the type [`!`] and are diverging.

src/expressions/array-expr.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ const EMPTY: Vec<i32> = Vec::new();
7272
[EMPTY; 2];
7373
```
7474

75+
r[expr.array.diverging]
76+
An array expression [diverges] if any of its operands diverges.
77+
7578
r[expr.array.index]
7679
## Array and slice indexing expressions
7780

@@ -113,6 +116,35 @@ arr[10]; // warning: index out of bounds
113116
r[expr.array.index.trait-impl]
114117
The array index expression can be implemented for types other than arrays and slices by implementing the [Index] and [IndexMut] traits.
115118

119+
r[expr.array.index.diverging]
120+
An index expression [diverges] if either of its operands diverges, or if the type of the indexed element is the [never type] and the value is guaranteed to be read.
121+
122+
> [!EXAMPLE]
123+
> ```rust
124+
> fn phantom_place<F: FnOnce() -> T, T>() -> [T; 1] { loop {} }
125+
>
126+
> fn diverging_place_read() -> ! {
127+
> // Create an array with the never type.
128+
> let x /* : [!; 1] */ = phantom_place::<fn() -> !, _>();
129+
> // A read of a place expression produces a diverging block.
130+
> x[0];
131+
> }
132+
> ```
133+
>
134+
> The following example contrasts with the above because it does not perform a read. This means it does not diverge, causing a compile error.
135+
>
136+
> ```rust,compile_fail
137+
> # fn phantom_place<F: FnOnce() -> T, T>() -> [T; 1] { loop {} }
138+
> #
139+
> fn diverging_place_no_read() -> ! {
140+
> // Create an array with the never type.
141+
> let x /* : [!; 1] */ = phantom_place::<fn() -> !, _>();
142+
> // This does not constitute a read.
143+
> let _ = x[0];
144+
> // ERROR: Expected type !, found ()
145+
> }
146+
> ```
147+
116148
[`Copy`]: ../special-types-and-traits.md#copy
117149
[IndexMut]: std::ops::IndexMut
118150
[Index]: std::ops::Index
@@ -121,9 +153,11 @@ The array index expression can be implemented for types other than arrays and sl
121153
[const block expression]: expr.block.const
122154
[constant expression]: ../const_eval.md#constant-expressions
123155
[constant item]: ../items/constant-items.md
156+
[diverges]: divergence
124157
[inferred const]: items.generics.const.inferred
125158
[literal]: ../tokens.md#literals
126159
[memory location]: ../expressions.md#place-expressions-and-value-expressions
160+
[never type]: type.never
127161
[panic]: ../panic.md
128162
[path]: path-expr.md
129163
[slice]: ../types/slice.md

src/expressions/await-expr.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ r[expr.await.edition2018]
2929
> [!EDITION-2018]
3030
> Await expressions are only available beginning with Rust 2018.
3131
32+
r[expr.await.diverging]
33+
An await expression [diverges] if the future's output type is the [never type].
34+
3235
r[expr.await.task]
3336
## Task context
3437

@@ -63,6 +66,8 @@ where the `yield` pseudo-code returns `Poll::Pending` and, when re-invoked, resu
6366
[`poll::Pending`]: std::task::Poll::Pending
6467
[`poll::Ready`]: std::task::Poll::Ready
6568
[async context]: ../expressions/block-expr.md#async-context
69+
[diverges]: divergence
6670
[future]: std::future::Future
71+
[never type]: type.never
6772
[`IntoFuture`]: std::future::IntoFuture
6873
[`IntoFuture::into_future`]: std::future::IntoFuture::into_future

src/expressions/block-expr.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ The actual data format for this type is unspecified.
191191
> [!NOTE]
192192
> The future type that rustc generates is roughly equivalent to an enum with one variant per `await` point, where each variant stores the data needed to resume from its corresponding point.
193193
194+
r[expr.block.async.diverging]
195+
An async block expression does not itself [diverge], but evaluating the future (such as through `await`) diverges if the output type is the [never type].
196+
194197
r[expr.block.async.edition2018]
195198
> [!EDITION-2018]
196199
> Async blocks are only available beginning with Rust 2018.
@@ -352,6 +355,7 @@ fn is_unix_platform() -> bool {
352355
[call expressions]: call-expr.md
353356
[capture modes]: ../types/closure.md#capture-modes
354357
[constant items]: ../items/constant-items.md
358+
[diverge]: divergence
355359
[diverges]: expr.block.diverging
356360
[final operand]: expr.block.inner-attributes
357361
[free item]: ../glossary.md#free-item

src/expressions/call-expr.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ let three: i32 = add(1i32, 2i32);
3232
let name: &'static str = (|| "Rust")();
3333
```
3434

35+
r[expr.call.diverging]
36+
A call expression [diverges] if any of its operands diverges, or if the return type of the function is the [never type].
37+
3538
r[expr.call.desugar]
3639
## Disambiguating function calls
3740

@@ -103,5 +106,7 @@ Refer to [RFC 132] for further details and motivations.
103106
[`default()`]: std::default::Default::default
104107
[`size_of()`]: std::mem::size_of
105108
[automatically dereferenced]: field-expr.md#automatic-dereferencing
109+
[diverges]: divergence
106110
[fully-qualified syntax]: ../paths.md#qualified-paths
111+
[never type]: type.never
107112
[non-function types]: ../types/function-item.md

src/expressions/closure-expr.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ A closure expression denotes a function that maps a list of parameters onto the
3131
r[expr.closure.unique-type]
3232
Each closure expression has a unique, anonymous type.
3333

34+
r[expr.closure.diverging]
35+
A closure expression itself does not [diverge]. However, calling the closure diverges if the return type is the [never type].
36+
3437
r[expr.closure.captures]
3538
Significantly, closure expressions _capture their environment_, which regular [function definitions] do not.
3639

@@ -105,6 +108,8 @@ Attributes on closure parameters follow the same rules and restrictions as [regu
105108
[block]: block-expr.md
106109
[call traits and coercions]: ../types/closure.md#call-traits-and-coercions
107110
[closure type]: ../types/closure.md
111+
[diverge]: divergence
108112
[function definitions]: ../items/functions.md
113+
[never type]: type.never
109114
[patterns]: ../patterns.md
110115
[regular function parameters]: ../items/functions.md#attributes-on-function-parameters

src/expressions/field-expr.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,43 @@ foo().x;
4242
(mystruct.function_field)() // Call expression containing a field expression
4343
```
4444
45+
r[expr.field.diverging]
46+
A field expression [diverges] if its expression operand diverges or if the type of the field is the [never type] and the value is guaranteed to be read.
47+
48+
> [!EXAMPLE]
49+
> ```rust
50+
> struct S<T> {
51+
> f: T
52+
> }
53+
>
54+
> fn phantom_place<F: FnOnce() -> T, T>() -> S<T> { loop {} }
55+
>
56+
> fn diverging_place_read() -> ! {
57+
> // Create a struct with a field with the never type.
58+
> let x /* : S<!> */ = phantom_place::<fn() -> !, _>();
59+
> // A read of a place expression produces a diverging block.
60+
> x.f;
61+
> }
62+
> ```
63+
>
64+
> The following example contrasts with the above because it does not perform a read. This means it does not diverge, causing a compile error.
65+
>
66+
> ```rust,compile_fail
67+
> # struct S<T> {
68+
> # f: T
69+
> # }
70+
> #
71+
> # fn phantom_place<F: FnOnce() -> T, T>() -> S<T> { loop {} }
72+
> #
73+
> fn diverging_place_no_read() -> ! {
74+
> // Create a struct with a field with the never type.
75+
> let x /* : S<!> */ = phantom_place::<fn() -> !, _>();
76+
> // This does not constitute a read.
77+
> let _ = x.f;
78+
> // ERROR: Expected type !, found ()
79+
> }
80+
> ```
81+
4582
r[expr.field.autoref-deref]
4683
## Automatic dereferencing
4784
@@ -71,8 +108,10 @@ let d: String = x.f3; // Move out of x.f3
71108
[`drop`]: ../special-types-and-traits.md#drop
72109
[identifier]: ../identifiers.md
73110
[call expression]: call-expr.md
111+
[diverges]: divergence
74112
[method call expression]: method-call-expr.md
75113
[mutable]: ../expressions.md#mutability
114+
[never type]: type.never
76115
[parenthesized expression]: grouped-expr.md
77116
[place expression]: ../expressions.md#place-expressions-and-value-expressions
78117
[struct]: ../items/structs.md

src/expressions/grouped-expr.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,8 @@ assert_eq!( a.f (), "The method f");
4444
assert_eq!((a.f)(), "The field f");
4545
```
4646

47+
r[expr.paren.diverging]
48+
A parenthesized expression [diverges] if its operand diverges.
49+
50+
[diverges]: divergence
4751
[place]: ../expressions.md#place-expressions-and-value-expressions

src/expressions/if-expr.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ assert_eq!(y, "Bigger");
7373
r[expr.if.diverging]
7474
An `if` expression [diverges] if either the condition expression diverges or if all arms diverge.
7575

76+
The condition expression diverges if the leftmost condition in the `&&` chain diverges, where `let` patterns are considered to diverge if the scrutinee diverges.
77+
7678
```rust,no_run
7779
fn diverging_condition() -> ! {
7880
// Diverges because the condition expression diverges

0 commit comments

Comments
 (0)