How to treat ambiguous .Self?
leads question
Discussed in [toolchain discussion 2026-04-28](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.h6lecvcwu214#heading=h.twhpk59ki1vo).
## Intro
Given a facet, with the following numbered `.Self` facets in its type:
```carbon
T:! Z where U impls (Y(.Self) where .Self impls X and .Y1 = C(.Self) and .Y2 = .Self)
^1 ^2 ^3 ^4
```
Currently the design says that:
- `:!` introduces `.Self` of type `type`.
- `where` introduces `.Self` with type being the facet type on the LHS of the `where`
Normally this just changes the type of `.Self` and they all refer to whatever facet will replace `T`. However that changes in the facet type nested in `U impls <facet type>`. Within that facet type, there are two potential meanings of the `.Self` introduced by a `where`, either `T` or `U`. We call this an ambiguous `.Self`.
For discussion, we say `T` is the top-level value for `.Self`, and `U` is the inner-most value for `.Self` in this example.
## What `.Self` does in the toolchain
The fundamental purpose of `.Self` is to sit as a placeholder that is replaced later once a self-type is known. We now do this replacement (as of #7097) primarily when identifying a facet type for an associated self-type. This replacement applies to all `.Self` except:
- We encode designators in the toolchain as a `ImplWitnessAccess(LookupImplWitness(.Self, Interface), Index)` instruction. The design (not copied out of proposal yet) says designators are [_not_ an implicit `.Self` anymore](https://docs.carbon-lang.dev/proposals/p2173.html#rewrite-constraints), but that they modify the facet/type of the `where` expression they are a part of. This is the inner-most value for `.Self`. So designators in rewrite (and same-type) constraints are _not_ replaced as part of the global replacement with the top-level self-type, and are replaced instead with the inner-most value.
- We encode `.Self impls` as a separate constraint type in the toolchain (in `FacetTypeInfo`), which does not include the `.Self` instruction in it at all. So these do not participate in `.Self` replacement, and are instead mapped to a facet/type when handling an `impls` constraint inside `WhereExpr` evaluation. We current map these constraints to be against the current `WhereExpr` being evaluated, which works out the same as saying `.Self` is replaced by the inner-most value.
## What goes wrong with ambiguous `.Self`?
The replacement of `.Self` always happens with the top-level value. In this example its `T` with type `Z`. But name lookup finds the nearest `.Self`, which for `^3` is `U` with type `Y(T)`. This creates a problem for replacement if the `.Self` found by name lookup in `^3` is still there in the final facet type when we perform replacement. We will have a value that is replacing `T` and satisfies `Z`, but the `.Self` will want a value for `U` and need it to satisfy `Y(T)`.
We eagerly evaluate parts of the facet type if possible. If the example is changed so that `^3` becomes part of an ImplWitnessAccess, such as `C(.Self).C1`, and a value for `.C1` is known to eval, the `.Self` in `^3` can be consumed by eval, resolving to whatever `.C1` is, which may contain either the inner-most _or_ the top-level `.Self`.
For instance with this definition of `C`, the result of `.C1` is the top-level value for `.Self`:
```carbon
class C[U:! type](T:! Y(U)) {
let C1:! type = U;
}
```
But with this definition `C`, the result of `.C1` is the inner-most value for `.Self`:
```carbon
class C(T:! type) {
let C1:! type = T;
}
```
The actual symbolic facet `.Self` does not contain any information to differentiate between these two values, so it can change its value depending on changes to `C`, which would be bad. So we consider it ambiguous and reject it `^3`.
## .Self impls
As mentioned `.Self impls` is implemented separately in the toolchain, without retaining the `.Self` instruction at all. This is because the `.Self` there sits on its own, so it acts more like a keyword. In particular, it can not resolve in eval to a facet with an ambiguous `.Self`; it is fixed and evaluates to itself.
## Options
### 1. Allow `.Self` impls
Allow `^1` and `^2`. Reject `^3` and `^4`.
- Always allow `.Self impls` since we can disambiguate it. Say that `.Self` refers to the inner-most value so that it's modifying the `where` that it is a part of. This makes `.Self impls` apply the most similar to rewrites (`.A = B`).
- Disallow ambiguous `.Self`, which is any other use of `.Self` in an ambiguous position.
### 1b. Allow `.Self impls`, as top-level
Allow `^1` and `^2`. Reject `^3` and `^4`
- Always allow `.Self impls` since we can disambiguate it. Say that `.Self` always refers to the top-level value, so `^2` modifies `T` instead of `U` in the original example. In particular, `where .Self impls X and .Y1 = C(.Self)` the `.Self` modifies `T` but the `.Y1` modifies `U`. This makes `.Self impls` apply very differently than rewrites (`.A = B`).
- This would require us to maintain the `.Self` instruction in the requirements, instead of erasing it in the FacetTypeInfo.
- Disallow ambiguous `.Self`, which is any other use of `.Self` in an ambiguous position.
In particular, `.Self impls` does not modify the current `where` expression, it modifies whatever becomes the top-level value. This may make `.Self impls` ambiguous in the presence of composing facet types. The `.Self impls` could be modifying a facet/type that is not part of the current facet type at all.
### 2. Maximal disallowance
Allow `^1`. Reject `^2`, `^3` and `^4`
- Disallow all ambiguous `.Self`. Don't try to disambiguate `.Self impls`. This makes `.Self` the most self-consistent, but requires users to take more care when writing `impls` constraints. It makes `.Self impls` not work in some places where rewrites (`.A = B`) do work.
### 3. Maximal non-ambiguous allowance
Allow `^1`, `^2`, and `^4`. Reject `^3`.
- Allow and disambiguate all `.Self` that are used on their own - not as part of some larger type expression. This allows `.Self impls` but also allows `.A = .Self`. But not `.A = C(.Self)`. Disambiguate these `.Self` to be the inner-most value so they modify the same facet/type as rewrites (`.A = B`).
- Also allow `.Self` as the self-type for a member access, such as `.Self.A`, as it can always be replaced with the inner-most value immediately without depending on evaluation to erase the `.Self` reference.
- Disallow any other ambiguous `.Self`.
This makes the error for `C(.Self)` need a bit more explanation, but it allows more use of `.Self`.
### 3b. Maximal non-ambiguous allowance, as top-level
Allow `^1`, `^2`, and `^4`. Reject `^3`.
Same as 3 but disambiguate `.Self` to always be the top-level value. Same tradeoffs as 1b.
Note that `.Y2 = .Y4` in a nested facet type would have the LHS refer to the inner-most value but the RHS would refer to the top-level value.
### 4. Yolo (Inner-most only)
Allow `^1`, `^2`, `^3` and `^4`.
- Let `.Self` be ambiguous. Name lookup gives you a `.Self` that refers to the inner-most value. Rely on typecheck errors to tell the user what went wrong. The user will need to track multiple `.Self` values and understand what is being eagerly evaluated to follow what facets end up being used for any type expression that involves an ambiguous `.Self`.
### 5. Top-level only
Allow `^1`, `^2`, `^3` and `^4`.
- `.Self` always refers to the top-level value. Its type is not modified by `where`. We rely on implicit constraints to typecheck use of `.Self` as a non-`type` facet type.
- Users have to write `U` to refer to the inner-most value instead of writing `.Self`.
- Like 1b, `.Self impls` does not modify the current `where` expression, it modifies whatever becomes the top-level value. This may make `.Self` generally and `.Self impls` ambiguous in the presence of composing facet types. The `.Self impls` could be modifying a facet/type that is not part of the current facet type at all.
- Like 3b, `.Y2 = .Y4` in a nested facet type would have the LHS refer to the inner-most value but the RHS would refer to the top-level value.
4 条评论