You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/normalization.md
+72-14Lines changed: 72 additions & 14 deletions
Original file line number
Diff line number
Diff line change
@@ -4,9 +4,9 @@
4
4
5
5
## What is normalization
6
6
7
-
In Rust there are a number of types that are considered equal to some "underlying" type, for example inherent associated types, trait associated types, free type aliases (`type Foo = u32`), and opaque types (`-> impl RPIT`). Alias types are represented by the [`TyKind::Alias`][tykind_alias] variant, with the kind of aliases tracked by the [`AliasTyKind`][aliaskind] enum.
7
+
In Rust there are a number of types that are considered equal to some "underlying" type, for example inherent associated types, trait associated types, free type aliases (`type Foo = u32`), and opaque types (`-> impl RPIT`). We consider such types to be "aliases", alias types are represented by the [`TyKind::Alias`][tykind_alias] variant, with the kind of alias tracked by the [`AliasTyKind`][aliaskind] enum.
8
8
9
-
Normalization is the process of taking these alias types and determining the underlying type that they are equal to. For example given some type alias `type Foo = u32`, normalizing `Foo` would give `u32`.
9
+
Normalization is the process of taking these alias types and replacing them with the underlying type that they are equal to. For example given some type alias `type Foo = u32`, normalizing `Foo` would give `u32`.
@@ -17,19 +17,19 @@ When interfacing with the type system it will often be the case that it's necess
17
17
18
18
An additional complication is that the compiler is currently undergoing a transition from the old trait solver to the new trait solver. As part of this transition our approach to normalization in the compiler has changed somewhat significantly, resulting in some normalization entry points being "old solver only" slated for removal in the long-term once the new solver has stabilized.
19
19
20
-
Here is a rough overview of the different entry points to normalization in the compiler.
20
+
Here is a rough overview of the different entry points to normalization in the compiler:
[`normalize`][normalize]/[`deeply_normalize`][deeply_normalize]/[`structurally_normalize`][structurally_normalize] are the main normalization entry points for normalizing during various analysis' such as type checking, impl wellformedness checking, collecting the types of RPITITs, etc. It's able to handle inference variables during normalization and will return any nested goals required for the normalization to hold.
28
+
[`normalize`][normalize]/[`deeply_normalize`][deeply_normalize]/[`structurally_normalize`][structurally_normalize] are the main normalization entry points for normalizing during various analysis' such as type checking, impl wellformedness checking, collecting the types of RPITITs, etc. They are able to handle inference variables during normalization and will return any nested goals required for the normalization to hold.
29
29
30
30
These normalization functions are often mirrored on other contexts that wrap an [`InferCtxt`][infcx], such as [`FnCtxt`][fcx] or [`ObligationCtxt`][ocx]. They behave largely the same except that these wrappers can either handle providing some of the arguments to the normalize functions or handle the returned goals itself.
31
31
32
-
Due to the new normalization approach of the new solver the `normalize`method is a no-op under the new solver and is slated for removal once the new solver is stabilized. Under the new solver the intention is to delay normalization up until matching on the type is actually required, at which point `structurally_normalize` should be called. In some rare cases it is still desirable to eagerly normalize a whole value ahead of time and so `deeply_normalize` exists.
32
+
The `normalize`function is a no-op under the new solver, and will be removed once the new solver is stabilized. It is intended to be used in cases where normalization is not required under the new solver, in cases where normalization is required with bother solvers the `structurally_normalize` function can be used.
33
33
34
34
When matching on types during HIR typeck we would like to emit an error if the type is an inference variable as we do not know what type it will wind up being inferred to. The `FnCtxt` type (used during HIR typeck) has a method for this, [`fcx.structurally_resolve`][structurally_resolve], when the new solver is enabled it will *also* attempt to normalize the type via `structurally_normalize`.
35
35
@@ -59,27 +59,85 @@ In practice `query_normalize` is used for normalization in the borrow checker, a
59
59
60
60
### `traits::normalize_with_depth(_to)`
61
61
62
-
[`traits::normalize_with_depth(_to)`][norm_with_depth] is only used by the internals of the old trait solver. It is effectively calling into the internals of how normalization is implemented by the old solver. Other normalization entry points cannot be used from within the internals of the old trait solver as it would result in handling goal cycles and recursion depth incorrectly.
62
+
[`traits::normalize_with_depth(_to)`][norm_with_depth] is only used by the internals of the old trait solver. It is effectively a raw entry point to the internals of how normalization is implemented by the old solver. Other normalization entry points cannot be used from within the internals of the old trait solver as it would result in incorrectly handling goal cycles and recursion depth.
63
63
64
-
When the new solver is stabilized, the old solver and its implementation of normalization will be removed (of which this function is part of).
64
+
This should not be used outside of the implementation of the old solver. When the new solver is stabilized, the old solver and its implementation of normalization (of which this function is part of) will be removed .
> FIXME: This section is somewhat incomplete and could do with expansion
70
+
> NOTE: This section is somewhat incomplete and could do with expansion
71
71
72
-
## Ambiguous vs Rigid Aliases
72
+
## Rigid, Ambiguous and Unnormalized Aliases
73
73
74
-
Aliases can either be "ambiguous" or "rigid".
74
+
Aliases can either be "rigid", "ambiguous", or simply unnormalized.
75
75
76
-
When an alias cannot yet be normalized due to containing inference variables, such as `<_ as Iterator>::Item`, we consider it to be an "ambiguous" alias where "ambiguous" refers to the fact that it is not clear what type implements `Iterator` yet.
76
+
We generally consider types to be rigid if their "shape" isn't going to change, for example `Box` is rigid as no amount of normalization can turn a `Box` into a `u32`, whereas `<vec::IntoIter<u32> as Iterator>::Item` is not rigid as it can be normalized to `u32`.
77
77
78
-
On the other hand if we can determine the source for how the self type (`_` in the previous example) implements the trait (e.g. `Iterator`) but the source does not determine the underlying type of the associated type (e.g. `Item`) then it is considered a "rigid" alias.
78
+
Aliases are rigid when we know that we will never be able to normalize them furthur. A concrete example of a *rigid* alias would be `<T as Iterator>::Item` in an environemnt where there is no `T: Iterator<Item = ...>` bound, only a `T: Iterator` bound:
79
+
```rust
80
+
fnfoo<T:Iterator>() {
81
+
// This alias is *rigid*
82
+
let_: <TasIterator>::Item;
83
+
}
79
84
80
-
We generally consider types to be rigid if their "shape" isn't going to change, for example `Box` is rigid as no amount of normalization can turn a `Box` into a `u32`, whereas `<vec::IntoIter<u32> as Iterator>::Item` is not rigid as it can be normalized to `u32`.
85
+
fnbar<T:Iterator<Item=u32>>() {
86
+
// This alias is *not* rigid as it can be normalized to `u32`
87
+
let_: <TasIterator>::Item;
88
+
}
89
+
```
90
+
91
+
This definition also imples that illformed aliases can be considered rigid. For example the type `<u32 as Iterator>::Item` is a rigid alias as it can never be normalized as there is no `u32: Iterator` impl (ignoring `feature(trivial_bounds)`).
92
+
93
+
When an alias can't yet be normalized but may eventually wind up normalizeable we consider it to be an "ambiguous" alias. This can occur when an alias contains inference variables which prevent being able to determine how the trait is implemented:
94
+
```rust
95
+
fnfoo<T:Iterator, U:Iterator>() {
96
+
// This alias is considered to be "ambiguous"
97
+
let_: <_asIterator>::Item;
98
+
}
99
+
```
100
+
101
+
The reason we call them "ambiguous" aliases is because its *ambiguous* whether this is a rigid alias or not.
102
+
103
+
The source of the `_: Iterator` trait impl is *ambiguous* (i.e. unknown), it could be some `impl Iterator for u32` or it could be some `T: Iterator` trait bound, we don't know yet. Depending on how `_: Iterator` holds the alias could be an unnormalized alias or it could be a rigid alias; it's *ambiguous* what kind of alias this is.
104
+
105
+
Finally, an alias can just be unnormalized, `<Vec<u32> as IntoIterator>::Iter` is an unnormalized alias as it can already be normalized to `std::vec::IntoIter<u32>`, it just hasn't been done yet.
106
+
107
+
---
81
108
82
-
If an alias is not ambiguous and also is not rigid then it is either not well formed (the self type does not implement the trait), or it can simply be normalized to its underlying type.
109
+
It is worth noting that Free and Inherent aliases cannot be rigid or ambiguous as naming them also implies having resolved the definition of the alias, which specifies the underlying type of the alias:
110
+
```rust
111
+
#![feature(inherent_associated_types)]
112
+
113
+
traitTrait {}
114
+
115
+
implTraitforu32 {}
116
+
implTraitforu64 {}
117
+
118
+
structFoo<T>(T);
119
+
120
+
impl<T:Trait> Foo<T> {
121
+
typeInherent=u32;
122
+
}
123
+
124
+
fnret_alias<T:Trait>(_:T) ->Foo<T>::Inherent {
125
+
10_u32
126
+
}
127
+
128
+
fnmain() {
129
+
// `t` has type `?x`
130
+
letmutt=Default::default();
131
+
// `ret_alias` returns a value of type `Foo<?x>::Inherent`
132
+
t=ret_alias(t);
133
+
134
+
// we have equated `?x` with `Foo<?x>::Inherent` this can
135
+
// only succeed if we're able to normalize inherent aliases
136
+
// with ambiguous where clauses on the impl.
137
+
//
138
+
// Or, put another way, it is *not* an ambiguous alias.
0 commit comments