|
| 1 | +# Type completeness in extend |
| 2 | + |
| 3 | +<!-- |
| 4 | +Part of the Carbon Language project, under the Apache License v2.0 with LLVM |
| 5 | +Exceptions. See /LICENSE for license information. |
| 6 | +SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 7 | +--> |
| 8 | + |
| 9 | +[Pull request](https://github.com/carbon-language/carbon-lang/pull/6395) |
| 10 | + |
| 11 | +<!-- toc --> |
| 12 | + |
| 13 | +## Table of contents |
| 14 | + |
| 15 | +- [Abstract](#abstract) |
| 16 | +- [Problem](#problem) |
| 17 | +- [Background](#background) |
| 18 | +- [Proposal](#proposal) |
| 19 | +- [Details](#details) |
| 20 | +- [Rationale](#rationale) |
| 21 | +- [Alternatives considered](#alternatives-considered) |
| 22 | + |
| 23 | +<!-- tocstop --> |
| 24 | + |
| 25 | +## Abstract |
| 26 | + |
| 27 | +Require any target scopes in an `extend` declaration to be complete, since |
| 28 | +`extend` changes the scopes which are looked in for name lookup to include those |
| 29 | +named in the declaration. |
| 30 | + |
| 31 | +## Problem |
| 32 | + |
| 33 | +Proposal |
| 34 | +[#5168](https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p5168.md) |
| 35 | +laid out rules for when a facet type needs to be identified or complete. When |
| 36 | +`require impls X` is written, then `X` must be identified. However it does not |
| 37 | +specify any rule for `extend require impls X`. And we lack completeness rules |
| 38 | +for other `extend` declarations, including `extend impl as X`, `extend base: X`, |
| 39 | +and `extend adapt X`. |
| 40 | + |
| 41 | +An `extend` declaration always names one or more target entities which will be |
| 42 | +included in the search for names when looking in the enclosing scope of the |
| 43 | +`extend` declaration. In order to do name lookup into an entity, it must be |
| 44 | +complete to avoid poisoning names in the entity. |
| 45 | + |
| 46 | +## Background |
| 47 | + |
| 48 | +- Proposal |
| 49 | + [#5168](https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p5168.md): |
| 50 | + Forward `impl` declaration of an incomplete interface |
| 51 | +- Proposal |
| 52 | + [#2760](https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p2760.md): |
| 53 | + Consistent `class` and `interface` syntax |
| 54 | +- Proposal |
| 55 | + [#0777](https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p0777.md): |
| 56 | + Inheritance |
| 57 | + |
| 58 | +## Proposal |
| 59 | + |
| 60 | +An `extend` declaration declares that the enclosing scope extends the scope |
| 61 | +associated with any target entity named in the declaration. That is to say name |
| 62 | +lookups into the enclosing scope will also look into the scopes which are |
| 63 | +nominated by the `extend` declaration. The `extend` declaration requires that |
| 64 | +the target scopes named in an `extend` declaration are complete, or if the |
| 65 | +target is a generic parameter, requires the type of the parameter to be |
| 66 | +complete. |
| 67 | + |
| 68 | +The scope of a facet type formed by a `where` declaration extends the scope of |
| 69 | +its first operand. And the scope of a facet type formed by a `&` operator |
| 70 | +extends the scopes of both of its operands. In either case, the scope of the |
| 71 | +facet type is complete if every scope it extends is complete. The facet type |
| 72 | +`type` is associated with an empty scope and is complete. |
| 73 | + |
| 74 | +| Facet type | Requirement | |
| 75 | +| ---------------------------- | ------------------------------------- | |
| 76 | +| `I` | Requires `I` is complete. | |
| 77 | +| `I & J` | Requires `I` and `J` are complete. | |
| 78 | +| `type where U impls J` | Requires `type` is complete. | |
| 79 | +| `I & (type where U impls J)` | Requires `I` and `type` are complete. | |
| 80 | + |
| 81 | +## Details |
| 82 | + |
| 83 | +To `extend` an entity `Y` with another `Z` means that name lookups into `Y` will |
| 84 | +also look into `Z`. Immediately after the `extend` operation, members of `Z` |
| 85 | +should also be found when doing name lookup into `Y`, both from outside and from |
| 86 | +inside the definition of `Y`. In order to be able to perform lookups into `Z`, |
| 87 | +we require that `extend` operations only target scopes that are complete. |
| 88 | + |
| 89 | +This requirement functions recursively. Given an interface `B` that extends |
| 90 | +another interface `A`: By naming `A` in an extend declaration, we require `A` is |
| 91 | +complete. This provides that its entire definition is known, and thus its |
| 92 | +`extend` relationship to `B`. The `extend` relationship there also provides that |
| 93 | +`B` is complete. |
| 94 | + |
| 95 | +If the target scope of an `extend` declaration is a generic parameter, its type |
| 96 | +must be complete where the `extend` declaration is written, as name lookups into |
| 97 | +the extended scope will look into the type of the generic parameter. |
| 98 | + |
| 99 | +```carbon |
| 100 | +interface I { |
| 101 | + fn F(); |
| 102 | +} |
| 103 | +
|
| 104 | +class C(T:! I) { |
| 105 | + extend base: T; |
| 106 | + // `F` names `T.F` here, found in `I`. |
| 107 | + fn G() { F(); } |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +As any generic parameter in the enclosing scope is replaced by a more specific |
| 112 | +value, extended scopes that depend on a generic parameter must remain complete. |
| 113 | +This includes forming a specific for the extended scope involving the parameter |
| 114 | +in order to surface any monomorphization errors in the resulting specific. |
| 115 | + |
| 116 | +In the next example, the `extend` declaration in interface `A(N)` names a |
| 117 | +symbolic facet type which can produce monomorphization errors when a negative |
| 118 | +value is provided for `N`. When a more specific value for the target `B(N)` is |
| 119 | +provided, we require the specific value to be complete as well by forming the |
| 120 | +specific. A diagnostic error would be produced while checking `C(-1)` for |
| 121 | +completeness, as it requires `A(-1)` to be complete, which requires |
| 122 | +`B(array(i32, -1))` to be complete, and that contains an invalid type. |
| 123 | + |
| 124 | +```carbon |
| 125 | +interface B(T:! type) {} |
| 126 | +
|
| 127 | +interface A(N:! i32) { |
| 128 | + // Requires `B(N)` to be complete. |
| 129 | + extend require impls B(array(i32, N)) {} |
| 130 | +} |
| 131 | +
|
| 132 | +class C(N! i32) { |
| 133 | + // Requires `A(N)` to be complete, which requires `B(N)` to be complete. |
| 134 | + extend impl as A(N); |
| 135 | +} |
| 136 | +
|
| 137 | +fn F() { |
| 138 | + // Requires `C(-1)` to be complete, which requires `A(-1)` to be complete, which requires `B(array(i32, -1))` to be complete. |
| 139 | + var c: C(-1); |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +These rules prohibit an `extend` declaration from naming its enclosing scope, |
| 144 | +since by being part of the definition of that scope, it is implied that the |
| 145 | +enclosing scope is not complete. This seems reasonable as all names available |
| 146 | +inside the enclosing interface or named constraint are already available or |
| 147 | +would conflict with the ones that are. |
| 148 | + |
| 149 | +## Rationale |
| 150 | + |
| 151 | +This is based on the principle of |
| 152 | +[Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write). |
| 153 | +For code to be easy to write, the rules need to be consistent. Once an `extend` |
| 154 | +declaration has been written, the names inside should become available through |
| 155 | +the enclosing scope immediately. If we allow an interface named in an `extend` |
| 156 | +declaration to be incomplete, then name lookup will fail ambiguously. Those same |
| 157 | +names may become valid later once the interface is defined. |
| 158 | + |
| 159 | +## Alternatives considered |
| 160 | + |
| 161 | +We considered not requiring that the scope named in an `extend` declaration be |
| 162 | +complete where it is written, but only when the enclosing scope is required to |
| 163 | +be complete. This is more flexible, allowing entities to be defined later in the |
| 164 | +program. However this does not allow the use of names from the target scope in |
| 165 | +the `extend` to be used from within the enclosing definition scope. They would |
| 166 | +not become available until the enclosing definition scope was closed and |
| 167 | +complete. |
| 168 | + |
| 169 | +In particular, we want this to work: |
| 170 | + |
| 171 | +```carbon |
| 172 | +interface Z { |
| 173 | + let X:! type; |
| 174 | + fn F() -> X; |
| 175 | +} |
| 176 | +class C { |
| 177 | + extend impl as Z where .X = () { |
| 178 | + // Names `X` directly. |
| 179 | + fn F() -> X; |
| 180 | + } |
| 181 | +
|
| 182 | + // Names `X` and `F` directly. |
| 183 | + fn G() -> X { return F(); } |
| 184 | +} |
| 185 | +
|
| 186 | +// Now `C` is complete, `C.X` and `C.F` are also available. |
| 187 | +fn H() -> C.X { |
| 188 | + return C.F(); |
| 189 | +} |
| 190 | +``` |
0 commit comments