From 671900eb5f0368a5f7ff5f90493699abb779843a Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 28 Jan 2023 17:26:28 +0100 Subject: [PATCH 1/3] Add section on comparing types --- src/ty.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/ty.md b/src/ty.md index 90e17e85f..5599bf098 100644 --- a/src/ty.md +++ b/src/ty.md @@ -148,18 +148,36 @@ These methods all return a `Ty<'tcx>` – note that the lifetime you get back is arena that this `tcx` has access to. Types are always canonicalized and interned (so we never allocate exactly the same type twice). -> N.B. -> Because types are interned, it is possible to compare them for equality efficiently using `==` -> – however, this is almost never what you want to do unless you happen to be hashing and looking -> for duplicates. This is because often in Rust there are multiple ways to represent the same type, -> particularly once inference is involved. If you are going to be testing for type equality, you -> probably need to start looking into the inference code to do it right. - You can also find various common types in the `tcx` itself by accessing its fields: `tcx.types.bool`, `tcx.types.char`, etc. (See [`CommonTypes`] for more.) [`CommonTypes`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.CommonTypes.html + +## Comparing types + +Because types are interned, it is possible to compare them for equality efficiently using `==` +– however, this is almost never what you want to do unless you happen to be hashing and looking +for duplicates. This is because often in Rust there are multiple ways to represent the same type, +particularly once inference is involved. + +For example, the type `{integer}` (an integer inference variable, the type of an integer +literal like `0`) and `u8` should often be treated as equal when testing whether they +can be assigned to each other (which is a common operation in diagnostics code). +`==` on them will return `false` though, since they are different types. + +The simplest way to compare two types correctly requires an inference context (`infcx`). +If you have one, you can use `infcx.can_eq(ty1, ty2)` to check whether the types can be made equal, +so whether they can be assigned to each other. +When working with an inference context, you have to be careful to ensure that potential inference +variables inside the types actually belong to that inference context. If you are in a function +that has access to an inference context already, this should be the case. + +Another consideration is normalization. Two types may actually be the same, but one is behind an associated type. +To compare them correctly, you have to normalize the types first. + + + ## `ty::TyKind` Variants Note: `TyKind` is **NOT** the functional programming concept of *Kind*. From 3871f33f98695ba2603250c165e159425d46160d Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:38:10 +0100 Subject: [PATCH 2/3] Expand section basedd on review comments --- src/ty.md | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ty.md b/src/ty.md index 5599bf098..16ce94d1b 100644 --- a/src/ty.md +++ b/src/ty.md @@ -161,22 +161,47 @@ Because types are interned, it is possible to compare them for equality efficien for duplicates. This is because often in Rust there are multiple ways to represent the same type, particularly once inference is involved. -For example, the type `{integer}` (an integer inference variable, the type of an integer -literal like `0`) and `u8` should often be treated as equal when testing whether they -can be assigned to each other (which is a common operation in diagnostics code). -`==` on them will return `false` though, since they are different types. +For example, the type `{integer}` (`ty::Infer(ty::IntVar(..))` an integer inference variable, +the type of an integer literal like `0`) and `u8` (`ty::UInt(..)`) should often be treated as +equal when testing whether they can be assigned to each other (which is a common operation in +diagnostics code). `==` on them will return `false` though, since they are different types. The simplest way to compare two types correctly requires an inference context (`infcx`). -If you have one, you can use `infcx.can_eq(ty1, ty2)` to check whether the types can be made equal, -so whether they can be assigned to each other. +If you have one, you can use `infcx.can_eq(ty1, ty2)` to check whether the types can be made equal. +This is typically what you want to check during diagnostics, which is concerned with questions such +as whether two types can be assigned to each other, not whether they're represented identically in +the compiler's type-checking layer. + When working with an inference context, you have to be careful to ensure that potential inference variables inside the types actually belong to that inference context. If you are in a function -that has access to an inference context already, this should be the case. - -Another consideration is normalization. Two types may actually be the same, but one is behind an associated type. -To compare them correctly, you have to normalize the types first. - - +that has access to an inference context already, this should be the case. Specifically, this is the +case during HIR type checking or MIR borrow checking. + +Another consideration is normalization. Two types may actually be the same, but one is behind an +associated type. To compare them correctly, you have to normalize the types first. This is +primarily a concern during HIR type checking and with all types from a `TyCtxt` query +(for example from `tcx.type_of()`). + +When a `FnCtxt` or an `ObligationCtxt` is available during type checking, `.normalize(ty)` +can be used on them to normalize the type. After type checking, diagnostics code can use +`tcx.normalize_erasing_regions(ty)`. + +There are also cases where using `==` on `Ty` is fine. This is for example the case in late lints +or after monomorphization, since type checking has been completed, meaning all inference variables +are resolved and all regions have been erased. In these cases, if you know that inference variables +or normalization won't be a concern, `#[allow]` or `#[expect]`ing the lint is recommended. + +When diagnostics code does not have access to an inference context, it should be threaded through +the function calls if one is available in some place (like during type checking). + +If no inference context is available at all, then one can be created as described in +[type-inference]. But this is only useful when the involved types (for example, if +they came from a query like `tcx.type_of()`) are actually substituted with fresh +inference variables using [`fresh_substs_for_item`]. This can be used to answer questions +like "can `Vec` for any `T` be unified with `Vec`?". + +[type-inference]: ./type-inference.md#creating-an-inference-context +[`fresh_substs_for_item`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_infer/infer/struct.InferCtxt.html#method.fresh_substs_for_item ## `ty::TyKind` Variants From 4c01b27898bb3ebb06547d06ff26f67902cbd06d Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 2 Feb 2023 09:18:13 +0100 Subject: [PATCH 3/3] Add param_env and wording --- src/ty.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ty.md b/src/ty.md index 16ce94d1b..f82d299ae 100644 --- a/src/ty.md +++ b/src/ty.md @@ -167,7 +167,8 @@ equal when testing whether they can be assigned to each other (which is a common diagnostics code). `==` on them will return `false` though, since they are different types. The simplest way to compare two types correctly requires an inference context (`infcx`). -If you have one, you can use `infcx.can_eq(ty1, ty2)` to check whether the types can be made equal. +If you have one, you can use `infcx.can_eq(param_env, ty1, ty2)` +to check whether the types can be made equal. This is typically what you want to check during diagnostics, which is concerned with questions such as whether two types can be assigned to each other, not whether they're represented identically in the compiler's type-checking layer. @@ -183,7 +184,7 @@ primarily a concern during HIR type checking and with all types from a `TyCtxt` (for example from `tcx.type_of()`). When a `FnCtxt` or an `ObligationCtxt` is available during type checking, `.normalize(ty)` -can be used on them to normalize the type. After type checking, diagnostics code can use +should be used on them to normalize the type. After type checking, diagnostics code can use `tcx.normalize_erasing_regions(ty)`. There are also cases where using `==` on `Ty` is fine. This is for example the case in late lints