mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:40:11 +01:00
Orphan rule for scopes (#7140)
Update the orphan rule to require a name to be defined within, or by, the same scope as the impl declaration. Since libraries can not be nested, this also enforces the old rule, but rejects more impl declarations. In particular, it rejects an impl declaration in a generic context which would have no way to provide a value for the generic bindings it inherited from its enclosing scope, making the impl unusable. Discussed in open discussion [2026-05-04](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.3ifnhz83n73d#heading=h.p45zfugbmdih).
This commit is contained in:
+119
-23
@@ -4585,26 +4585,115 @@ library depends on.
|
||||
#### Orphan rule
|
||||
|
||||
To achieve [coherence](terminology.md#coherence), we need to ensure that any
|
||||
given impl can only be defined in a library that must be imported for it to
|
||||
apply. Specifically, given a specific type and specific interface, `impl`
|
||||
declarations that can match can only be in libraries that must have been
|
||||
imported to name that type or interface. This is achieved with the _orphan
|
||||
rule_.
|
||||
given `impl` can only be defined where it is always visible when it could be
|
||||
used. Specifically, given a specific type and specific interface, `impl`
|
||||
declarations that can match must be imported along with some name in that type
|
||||
or interface.
|
||||
|
||||
**Orphan rule:** Some name from the type structure of an `impl` declaration must
|
||||
be defined in the same library as the `impl`, that is some name must be _local_.
|
||||
be an _anchor name_, which is a name that names an entity whose first
|
||||
[owning declaration](/docs/design/declaring_entities.md) is in the same file as
|
||||
each owning declaration of the `impl`, and either:
|
||||
|
||||
- the scope of the owning declaration directly contains the `impl`
|
||||
declaration, or
|
||||
- the owning declaration is within the scope containing the `impl`
|
||||
declaration, including nested scopes.
|
||||
|
||||
Let's say you have some interface `I(T, U(V))` being implemented for some type
|
||||
`A(B(C(D), E))`. To satisfy the orphan rule for coherence, that `impl` must be
|
||||
defined in some library that must be imported in any code that looks up whether
|
||||
that interface is implemented for that type. This requires that `impl` is
|
||||
defined in the same library that defines the interface or one of the names
|
||||
needed by the type. That is, the `impl` must be defined with one of `I`, `T`,
|
||||
`U`, `V`, `A`, `B`, `C`, `D`, or `E`. We further require anything looking up
|
||||
this `impl` to import the _definitions_ of all of those names. Seeing a forward
|
||||
declaration of these names is insufficient, since you can presumably see forward
|
||||
declarations without seeing an `impl` with the definition. This accomplishes a
|
||||
few goals:
|
||||
`A(B(C(D), E))`. To satisfy the orphan rule for coherence, that `impl`'s first
|
||||
owning declaration must be in a file where one of the names `I`, `T`, `U`, `V`,
|
||||
`A`, `B`, `C`, `D`, or `E` is introduced by its first owning declaration. This
|
||||
ensures that if the name is imported, the `impl` will be imported along with it.
|
||||
We further require that the `impl` be anchored by a name in its type structure
|
||||
to its current scope in some way. This ensures that when declared within a
|
||||
generic context, the `impl` is only accessible through a specific of the
|
||||
enclosing generic.
|
||||
|
||||
```carbon
|
||||
interface Z {}
|
||||
interface Y(T:! type) {}
|
||||
class A {}
|
||||
|
||||
fn F(T:! type) {
|
||||
class B { class C {} }
|
||||
|
||||
// Accepted; anchored by the name `B`.
|
||||
impl B as Z {}
|
||||
|
||||
// Accepted; anchored by the name `C`.
|
||||
impl B.C as Z {}
|
||||
|
||||
// Accepted; anchored by the name `B`.
|
||||
impl A as Y(B) {}
|
||||
|
||||
// Accepted; anchored by the name `C`.
|
||||
impl A as Y(B.C) {}
|
||||
|
||||
interface X {}
|
||||
|
||||
// Accepted; anchored by the name `X`.
|
||||
impl A as X {}
|
||||
|
||||
class D {
|
||||
// Accepted; anchored by the name `D`.
|
||||
impl D as Z {}
|
||||
// Accepted; anchored by implied `Self` which resolves to the name `D`.
|
||||
impl as Z {}
|
||||
// Accepted; anchored by `Self` which resolves to the name `D`.
|
||||
impl Self as Z {}
|
||||
}
|
||||
|
||||
class E {
|
||||
// ❌ Rejected, no anchor name.
|
||||
impl B as Z {};
|
||||
|
||||
class G {
|
||||
// ❌ Rejected, no anchor name. The `impl` is in the scope of `G`. But
|
||||
// the scope of `G` is not the defining owning declaration of `E` nor
|
||||
// does it contain the owning declaration of `E` .
|
||||
impl E as Z {}
|
||||
}
|
||||
}
|
||||
|
||||
if (true) {
|
||||
// ❌ Rejected, no anchor name.
|
||||
impl B as Z {}
|
||||
}
|
||||
|
||||
fn H() {
|
||||
// ❌ Rejected, no anchor name.
|
||||
impl B as Z {}
|
||||
}
|
||||
|
||||
// ❌ Rejected, no anchor name. Note that it has no way to specify a value
|
||||
// for the `T` binding.
|
||||
impl A as Z {}
|
||||
|
||||
// ❌ Rejected, no anchor name. A binding can not be an anchor name, only an
|
||||
// entity declaration can.
|
||||
impl A as Y(T) {}
|
||||
}
|
||||
```
|
||||
|
||||
```carbon
|
||||
// api file.
|
||||
library "lib";
|
||||
|
||||
class C;
|
||||
interface Z {}
|
||||
|
||||
// impl file.
|
||||
impl library "lib";
|
||||
|
||||
class C {}
|
||||
|
||||
// ❌ Rejected, no anchor name. The names `C` and `Z` are introduced in the api
|
||||
// file.
|
||||
impl C as Z {}
|
||||
```
|
||||
|
||||
This rule accomplishes a few goals:
|
||||
|
||||
- The compiler can check that there is only one definition of any `impl` that
|
||||
is actually used, avoiding
|
||||
@@ -4622,19 +4711,26 @@ Note that [the rules for specialization](#lookup-resolution-and-specialization)
|
||||
do allow there to be more than one `impl` to be defined for a type, by
|
||||
unambiguously picking one as most specific.
|
||||
|
||||
> **References:** Implementation coherence is
|
||||
> [defined in terminology](terminology.md#coherence), and is
|
||||
> [a goal for Carbon generics](goals.md#coherence). More detail can be found in
|
||||
> [this appendix with the rationale and alternatives considered](appendix-coherence.md).
|
||||
|
||||
Only the implementing interface and types (self type and type parameters) in the
|
||||
type structure are relevant here; an interface mentioned in a constraint is not
|
||||
sufficient since it
|
||||
[need not be imported](/proposals/p000920-generic-blanket-impls-details-5.md#orphan-rule-could-consider-interface-requirements-in-blanket-impls).
|
||||
|
||||
Since Carbon in addition requires there be no cyclic library dependencies, we
|
||||
conclude that there is at most one library that can contain `impl` definitions
|
||||
with a particular type structure.
|
||||
conclude that there is at most one source file that can contain owning `impl`
|
||||
declarations with a particular type structure.
|
||||
|
||||
> **References:** Implementation coherence is
|
||||
> [defined in terminology](terminology.md#coherence), and is
|
||||
> [a goal for Carbon generics](goals.md#coherence). More detail can be found in
|
||||
> [this appendix with the rationale and alternatives considered](appendix-coherence.md).
|
||||
|
||||
> **Alternatives considered:** Alternative choices for the orphan rule were
|
||||
> considered:
|
||||
>
|
||||
> - [A syntactic check, instead of applying the rule after evaluation](/proposals/p007140-orphan-rule-for-scopes.md#a-syntactic-check-instead-of-applying-the-rule-after-evaluation)
|
||||
> - [Disallowing the anchor name to be in a nested scope](/proposals/p007140-orphan-rule-for-scopes.md#disallowing-the-anchor-name-to-be-in-a-nested-scope)
|
||||
> - [Anchoring to a definition](/proposals/p007140-orphan-rule-for-scopes.md#anchoring-to-a-definition)
|
||||
|
||||
#### Overlap rule
|
||||
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
# Orphan rule for scopes
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
[Pull request](https://github.com/carbon-language/carbon-lang/pull/7140)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Problem](#problem)
|
||||
- [Background](#background)
|
||||
- [Proposal](#proposal)
|
||||
- [Details](#details)
|
||||
- [What the orphan rule disallows](#what-the-orphan-rule-disallows)
|
||||
- [Re-entering a nested scope in an `impl` declaration](#re-entering-a-nested-scope-in-an-impl-declaration)
|
||||
- [Interaction with evaluation](#interaction-with-evaluation)
|
||||
- [Rationale](#rationale)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [A syntactic check, instead of applying the rule after evaluation](#a-syntactic-check-instead-of-applying-the-rule-after-evaluation)
|
||||
- [Disallowing the anchor name to be in a nested scope](#disallowing-the-anchor-name-to-be-in-a-nested-scope)
|
||||
- [Anchoring to a definition](#anchoring-to-a-definition)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Abstract
|
||||
|
||||
Extend the orphan rule to require that at least one name in the type structure
|
||||
of an `impl` declaration is introduced in, or by, the same scope as the `impl`
|
||||
declaration, or in a scope nested within the scope of the `impl` declaration.
|
||||
|
||||
## Problem
|
||||
|
||||
The current orphan rule states:
|
||||
|
||||
> Some name from the type structure of an `impl` declaration must be defined in
|
||||
> the same library as the `impl`, that is some name must be _local_.
|
||||
>
|
||||
> ...
|
||||
>
|
||||
> We further require anything looking up this `impl` to import the _definitions_
|
||||
> of all of those names. Seeing a forward declaration of these names is
|
||||
> insufficient, since you can presumably see forward declarations without seeing
|
||||
> an `impl` with the definition.
|
||||
|
||||
The goal of the orphan rule is:
|
||||
|
||||
> Every attempt to use an `impl` will see the exact same `impl` definition,
|
||||
> making the interpretation and semantics of code consistent no matter its
|
||||
> context, in accordance with the
|
||||
> [low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md).
|
||||
|
||||
However with its current wording, it is possible to violate this ambition by
|
||||
placing a type definition (an owning declaration) in a library api file, then
|
||||
placing an `impl` decl referring to it in the same library's impl file. This
|
||||
satisfies the rule that the `impl` is in the same library as the definition of
|
||||
the type, and that users performing `impl` lookup can import the definition of
|
||||
the type. But the library impl file sees the `impl` decl in the impl file, while
|
||||
other libraries do not, creating an inconsistent point of view for which `impl`
|
||||
will be used.
|
||||
|
||||
```carbon
|
||||
// API file
|
||||
library "problem";
|
||||
|
||||
class C {}
|
||||
impl forall [T:! type] T as Z where .A = () {}
|
||||
fn F(T:! Z) -> T.(Z.A);
|
||||
```
|
||||
|
||||
```carbon
|
||||
// Impl file
|
||||
impl library "problem";
|
||||
|
||||
impl C as Z where .A = {} {}
|
||||
fn F(T:! Z) -> C.(Z.A) { ... }
|
||||
```
|
||||
|
||||
In this example the return type of `F(C)` will be `{}` in the `problem` library
|
||||
impl file but will be `()` in other libraries.
|
||||
|
||||
Second, this rule allows an `impl` declaration in a generic scope that can be
|
||||
referred to outside of that generic scope. This exposes the generic bindings
|
||||
through the `impl` without providing a way for the user to specify their values
|
||||
by making a specific of the enclosing generic.
|
||||
|
||||
```carbon
|
||||
interface Z { Z1:! type }
|
||||
class C;
|
||||
fn F(T:! type) {
|
||||
impl C as Z where .Z1 = T;
|
||||
}
|
||||
fn G() -> C.(Z.Z1);
|
||||
```
|
||||
|
||||
In this example the value `C.(Z.Z1)` is a generic binding in `F`, but no
|
||||
specific for the generic `F` is constructed, so the value of `T` cannot be
|
||||
known. This makes the `impl` declaration available, but unusable.
|
||||
|
||||
## Background
|
||||
|
||||
- [Out-of-line `impl`](https://github.com/carbon-language/carbon-lang/blob/2445ad9703c6f481ec371224803922e2fa4e6167/docs/design/generics/details.md#out-of-line-impl)
|
||||
- [Orphan rule](https://github.com/carbon-language/carbon-lang/blob/2445ad9703c6f481ec371224803922e2fa4e6167/docs/design/generics/details.md#orphan-rule)
|
||||
- [Declaring entities](https://github.com/carbon-language/carbon-lang/blob/4d1a61de29e4a12f2c74298edf499029ad7bc12c/docs/design/declaring_entities.md)
|
||||
for the concept of an owning declaration.
|
||||
- [`impl` redeclarations](https://github.com/carbon-language/carbon-lang/blob/2445ad9703c6f481ec371224803922e2fa4e6167/proposals/p3763.md#redeclarations)
|
||||
for declaring an `impl` into a nested scope.
|
||||
- [Scope differences](https://github.com/carbon-language/carbon-lang/blob/2445ad9703c6f481ec371224803922e2fa4e6167/proposals/p3763.md#scope-differences)
|
||||
for comparing nested scopes in re-declarations.
|
||||
|
||||
## Proposal
|
||||
|
||||
We propose to change the orphan rule to:
|
||||
|
||||
> Some name from the type structure of an `impl` declaration must be an _anchor
|
||||
> name_, which is a name that names an entity whose first
|
||||
> [owning declaration](/docs/design/declaring_entities.md) is in the same file
|
||||
> as each owning declaration of the `impl`, and either:
|
||||
>
|
||||
> - the scope of the owning declaration directly contains the `impl`
|
||||
> declaration, or
|
||||
> - the owning declaration is within the scope containing the `impl`
|
||||
> declaration, including nested scopes.
|
||||
|
||||
## Details
|
||||
|
||||
This proposal largely subsumes the existing orphan rule. Libraries can not share
|
||||
a file, so if an `impl` declaration named something with a definition in the
|
||||
same file, it would also name something with a definition in the same library.
|
||||
|
||||
However the rule is changed to require the anchor name to be an owning
|
||||
declaration, instead of a definition. That means we allow one thing the previous
|
||||
rule did not:
|
||||
|
||||
- A declaration of a `class C` in a library api file.
|
||||
- An `impl` declaration naming `C` as its anchor name in the library api file.
|
||||
- The definition of `C` in the library impl file.
|
||||
|
||||
This does not violate the intent of the orphan rule, as all users of `class C`
|
||||
will have a consistent view of the `impl` declarations that apply to it. This
|
||||
follows from the fact that all users of `class C` will see its owning
|
||||
declaration.
|
||||
|
||||
In exchange, the new rule disallows placing an `impl` declaration in a library
|
||||
impl file that only refers to names whose first owning declaration is in the
|
||||
library api file, since this creates coherence issues.
|
||||
|
||||
The new rule further restricts other `impl` declarations so that they can not
|
||||
only refer to names unrelated to the scope containing the `impl` declaration.
|
||||
|
||||
All examples below assume the "z" library is available as follows, and imported:
|
||||
|
||||
```carbon
|
||||
library "z";
|
||||
interface Z {}
|
||||
```
|
||||
|
||||
There are three cases allowed by the new rule:
|
||||
|
||||
1. An anchor name is introduced by the scope containing the `impl` declaration.
|
||||
|
||||
```carbon
|
||||
fn F() {
|
||||
class C {
|
||||
impl Self as Z;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here the `Self` is resolved to `C`, and `C` is being introduced by the scope
|
||||
containing the `impl` declaration. If `C` is generic, any use of the impl will
|
||||
require naming a specific `C`.
|
||||
|
||||
2. A name is introduced in the same scope as the `impl` declaration.
|
||||
|
||||
```carbon
|
||||
fn F() {
|
||||
class C;
|
||||
impl C as Z;
|
||||
}
|
||||
```
|
||||
|
||||
Here the `impl` declaration is in the scope of `fn F`, and the first owning
|
||||
declaration of `C` is within the same scope. All users of the `impl` declaration
|
||||
will have to be inside `F` since it uses the name `C` which is introduced inside
|
||||
the scope of `F`. Thus if `F` is generic, all users of the `impl` will share a
|
||||
consistent view of any generic bindings used by the `impl` declaration.
|
||||
|
||||
3. A name is introduced in a scope nested within the scope containing the `impl`
|
||||
declaration.
|
||||
|
||||
```carbon
|
||||
fn F() {
|
||||
class C {
|
||||
class D {}
|
||||
}
|
||||
impl C.D as Z;
|
||||
}
|
||||
```
|
||||
|
||||
Here the `impl` declaration is in the same scope as the first owning declaration
|
||||
of `C`. The first owning declaration of `D` is within the nested scope of `C`.
|
||||
Any user of the `impl` declaration will need to see the first owning declaration
|
||||
of both `C` and `D`, which means it will have to be inside `F`. Thus if `F` is
|
||||
generic, all users of the `impl` will share a consistent view of any generic
|
||||
bindings used by the `impl` declaration.
|
||||
|
||||
### What the orphan rule disallows
|
||||
|
||||
This rule forbids the following:
|
||||
|
||||
1. An `impl` declaration where all names are declared outside the scope
|
||||
containing the `impl`.
|
||||
|
||||
```carbon
|
||||
class A {}
|
||||
|
||||
class B {
|
||||
// ERROR: Neither `A` nor `Z` is defined by or has its owning declaration
|
||||
// within the scope `B`.
|
||||
impl A as Z {};
|
||||
}
|
||||
|
||||
class C {
|
||||
class D {
|
||||
// ERROR: Neither `C` nor `Z` is defined by or has its owning declaration
|
||||
// within the scope `D`.
|
||||
impl C as Z {}
|
||||
}
|
||||
}
|
||||
|
||||
fn F() {
|
||||
class E {}
|
||||
if (true) {
|
||||
// ERROR: Neither `E` nor `Z` is defined by or has its owning declaration
|
||||
// within this `if` block-scope.
|
||||
impl E as Z {}
|
||||
}
|
||||
}
|
||||
|
||||
fn G() {
|
||||
// ERROR: Neither `A` nor `Z` is defined by or has its owning declaration
|
||||
// within the scope `G`.
|
||||
impl A as Z {}
|
||||
}
|
||||
```
|
||||
|
||||
2. An `impl` declaration where all names have their owning declaration in a
|
||||
different file than the `impl`.
|
||||
|
||||
```carbon
|
||||
// Library api file.
|
||||
library "example";
|
||||
class A;
|
||||
class B {}
|
||||
interface Z {}
|
||||
```
|
||||
|
||||
```carbon
|
||||
// Library impl file.
|
||||
impl library "example";
|
||||
|
||||
// The definition is in this file, but the owning declaration comes first
|
||||
// and is in the api file.
|
||||
class A {}
|
||||
|
||||
// ERROR: Neither `A` nor `Z` have their owning declarations in this file.
|
||||
impl A as Z {}
|
||||
|
||||
// ERROR: Neither `B` nor `Z` have their owning declarations in this file.
|
||||
impl B as Z {}
|
||||
```
|
||||
|
||||
### Re-entering a nested scope in an `impl` declaration
|
||||
|
||||
It is possible to re-enter a nested scope by writing a qualified path for the
|
||||
entire `Type as Interface` expression, such as `impl C.(D as Z)`. This functions
|
||||
like writing `impl D as Z` within the nested scope `C`, or in other words, by
|
||||
performing name lookups from the scope of `C`.
|
||||
|
||||
By re-entering the nested scope `C`, it becomes the scope containing the `impl`
|
||||
declaration when applying the orphan rule.
|
||||
|
||||
For example, this is equivalent to writing `impl D as Z` inside the class `C`,
|
||||
which is allowed by the proposed orphan rule.
|
||||
|
||||
```carbon
|
||||
class C {
|
||||
class D {}
|
||||
}
|
||||
impl C.(D as Z);
|
||||
```
|
||||
|
||||
Whereas it is not allowed to write `impl C as Z` inside the scope of `D`, so it
|
||||
is also not allowed to write `impl C.D.(C as Z)`.
|
||||
|
||||
```carbon
|
||||
class C {
|
||||
class D {}
|
||||
}
|
||||
// ERROR: Neither `C` nor `Z` is defined by or has its owning declaration
|
||||
// within the scope `C.D`.
|
||||
impl C.D.(C as Z);
|
||||
```
|
||||
|
||||
### Interaction with evaluation
|
||||
|
||||
The orphan rule is applied to the `impl` declaration after evaluation. This
|
||||
means aliases and compile-time functions are evaluated and resolved before the
|
||||
rule is verified.
|
||||
|
||||
The following is rejected, since neither `C` and `Z` is defined by or has its
|
||||
owning declaration within the scope containing the `impl` declaration.
|
||||
|
||||
```carbon
|
||||
library "a";
|
||||
|
||||
musteval fn F() -> auto {
|
||||
class C {}
|
||||
return C;
|
||||
}
|
||||
```
|
||||
|
||||
```carbon
|
||||
library "b";
|
||||
import library "a";
|
||||
|
||||
musteval fn G() -> auto {
|
||||
return F();
|
||||
}
|
||||
|
||||
// ERROR: Neither `C` nor `Z` is defined by or has its owning declaration
|
||||
// within the current scope.
|
||||
impl G() as Z;
|
||||
```
|
||||
|
||||
But if the class `C` is declared in a function that is within the same scope as
|
||||
the `impl` declaration, then it is accepted.
|
||||
|
||||
```carbon
|
||||
musteval fn G() -> auto {
|
||||
class C {}
|
||||
return C;
|
||||
}
|
||||
|
||||
impl G() as Z;
|
||||
```
|
||||
|
||||
## Rationale
|
||||
|
||||
By resolving a coherence issue, where different libraries had a different view
|
||||
of what `impl` to use for a given type, we support the
|
||||
[low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md).
|
||||
|
||||
By ensuring generic bindings inherited by an `impl` declaration always have a
|
||||
value that can be made more specific, we support the goal of
|
||||
[Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
|
||||
It enables providing a clear error when when an invalid `impl` is written that
|
||||
describes the reason why, instead of an indirect error related to the generic
|
||||
bindings later.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### A syntactic check, instead of applying the rule after evaluation
|
||||
|
||||
This avoids the need to look "through" a function to figure out where a given
|
||||
entity was defined. But creates other questions instead. It would prevent the
|
||||
use of evaluation in an `impl` declaration, which would prevent things like
|
||||
aliases as well, and would generally be fighting against Carbon's eager
|
||||
evaluation model.
|
||||
|
||||
### Disallowing the anchor name to be in a nested scope
|
||||
|
||||
The original formulation of this rule required the anchor name to be from the
|
||||
same scope as the `impl` declaration. This is more restrictive than required to
|
||||
meet the goals of the rule. Accessing the nested scope of a name in the same
|
||||
scope as the `impl` declaration still requires anchoring the `impl` declaration
|
||||
to a name in its containing scope, which is used to qualify the path to the
|
||||
nested name.
|
||||
|
||||
Without allowing nested scopes, the following would be disallowed, without a
|
||||
good reason:
|
||||
|
||||
```carbon
|
||||
fn F() {
|
||||
class C { class D {} }
|
||||
// D is declared in a nested scope.
|
||||
impl C.D as Z {}
|
||||
|
||||
fn G() -> auto {
|
||||
class C {}
|
||||
return C;
|
||||
}
|
||||
// G() resolves to C, which is declared in an nested scope.
|
||||
impl G() as Z {}
|
||||
}
|
||||
```
|
||||
|
||||
### Anchoring to a definition
|
||||
|
||||
The original formation of this rule required the definition of the anchor name
|
||||
to be within the same scope as the `impl` declaration. This matched the wording
|
||||
of the previous orphan rule. However we noted the coherence issue where a name
|
||||
is forward declarad in an library api file, but declared in a library impl file.
|
||||
In this scenario, the only valid place to write the `impl` declaration using
|
||||
that name as its anchor name is in the api file, with the owning declaration.
|
||||
Any other choice allows multiple views of which `impl` to apply to a type or
|
||||
interface involving the anchor name.
|
||||
+216
-6
@@ -23,40 +23,46 @@ constraint ImportedN(T:! type) {}
|
||||
|
||||
class ImportedAnyParam[T:! type](X:! T) {}
|
||||
|
||||
// --- fail_class_definition_missing.carbon
|
||||
// --- fail_todo_class_definition_missing.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
// This is the first owning decl of C, so it can be an anchor name.
|
||||
class C;
|
||||
|
||||
// CHECK:STDERR: fail_class_definition_missing.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// TODO: This should be accepted.
|
||||
// CHECK:STDERR: fail_todo_class_definition_missing.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// CHECK:STDERR: impl C as ImportedY {}
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
impl C as ImportedY {}
|
||||
|
||||
// --- fail_class_definition_missing_for_self_specific.carbon
|
||||
// --- fail_todo_class_definition_missing_for_self_specific.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
// This is the first owning decl of C, so it can be an anchor name.
|
||||
class C;
|
||||
|
||||
// CHECK:STDERR: fail_class_definition_missing_for_self_specific.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// TODO: This should be accepted.
|
||||
// CHECK:STDERR: fail_todo_class_definition_missing_for_self_specific.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// CHECK:STDERR: impl ImportedD(C) as ImportedY {}
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
impl ImportedD(C) as ImportedY {}
|
||||
|
||||
// --- fail_class_definition_missing_for_interface_specific.carbon
|
||||
// --- fail_todo_class_definition_missing_for_interface_specific.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
// This is the first owning decl of C, so it can be an anchor name.
|
||||
class C;
|
||||
|
||||
// CHECK:STDERR: fail_class_definition_missing_for_interface_specific.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// TODO: This should be accepted.
|
||||
// CHECK:STDERR: fail_todo_class_definition_missing_for_interface_specific.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// CHECK:STDERR: impl ImportedC as ImportedZ(C) {}
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
@@ -214,3 +220,207 @@ import library "imports";
|
||||
constraint N(T:! type) {}
|
||||
|
||||
impl ImportedAnyParam(N) as ImportedY {}
|
||||
|
||||
// --- todo_fail_orphan_in_class_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
// Neither {} nor Z are defined within or by the scope of `C`.
|
||||
class C {
|
||||
impl {} as Z {}
|
||||
}
|
||||
|
||||
// --- valid_in_fn_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
fn F() {
|
||||
class C {}
|
||||
impl C as Z {}
|
||||
}
|
||||
|
||||
// --- todo_fail_orphan_in_fn_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
// Neither {} nor Z are defined within or by the scope of `F`.
|
||||
fn F() {
|
||||
impl {} as Z {}
|
||||
}
|
||||
|
||||
// --- valid_in_generic_fn_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
fn F(unused T:! type) {
|
||||
class C {}
|
||||
impl C as Z {}
|
||||
}
|
||||
|
||||
// --- fail_todo_orphan_in_generic_fn_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
// Neither {} nor Z are defined within or by the scope of `F`.
|
||||
fn F(unused T:! type) {
|
||||
// TODO: The error should be about being an orphan, not an unused binding.
|
||||
// CHECK:STDERR: fail_todo_orphan_in_generic_fn_scope.carbon:[[@LINE+4]]:3: error: `impl` with unused generic binding [ImplUnusedBinding]
|
||||
// CHECK:STDERR: impl {} as Z {}
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
impl {} as Z {}
|
||||
}
|
||||
|
||||
// --- valid_in_block_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
fn F() {
|
||||
if (true) {
|
||||
class C {}
|
||||
impl C as Z {}
|
||||
}
|
||||
}
|
||||
|
||||
// --- todo_fail_orphan_in_block_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
interface Z {}
|
||||
|
||||
fn F() {
|
||||
class C {}
|
||||
// Neither C nor Z are defined within or by this if block scope.
|
||||
if (true) {
|
||||
impl C as Z {}
|
||||
}
|
||||
}
|
||||
|
||||
// --- name_defined_by_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
class C {
|
||||
impl as ImportedY {}
|
||||
};
|
||||
|
||||
// --- name_defined_in_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
fn F() {
|
||||
class C {}
|
||||
impl C as ImportedY {}
|
||||
};
|
||||
|
||||
// --- name_defined_after_in_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
fn F() {
|
||||
class C;
|
||||
impl C as ImportedY {}
|
||||
class C {}
|
||||
};
|
||||
|
||||
// --- name_defined_in_nested_namespace_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
namespace N;
|
||||
class N.C {}
|
||||
|
||||
impl N.C as ImportedY {}
|
||||
|
||||
// --- name_defined_after_in_nested_namespace_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
namespace N;
|
||||
class N.C;
|
||||
|
||||
impl N.C as ImportedY {}
|
||||
|
||||
class N.C {}
|
||||
|
||||
// --- name_defined_in_nested_class_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
class C {
|
||||
class D {}
|
||||
}
|
||||
impl C.D as ImportedY {}
|
||||
|
||||
// --- name_defined_after_in_nested_class_scope.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
class C {
|
||||
class D;
|
||||
}
|
||||
impl C.D as ImportedY {}
|
||||
|
||||
class C.D {}
|
||||
|
||||
// --- fail_todo_extern_class.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "use_extern_class";
|
||||
|
||||
// CHECK:STDERR: fail_todo_extern_class.carbon:[[@LINE+4]]:1: error: redeclaration of `class C` is redundant [RedeclRedundant]
|
||||
// CHECK:STDERR: extern class C;
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR: fail_todo_extern_class.carbon:[[@LINE-5]]:1: in import [InImport]
|
||||
extern class C;
|
||||
|
||||
// --- fail_use_extern_class.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import library "imports";
|
||||
|
||||
// A non-owning decl of C.
|
||||
// CHECK:STDERR: fail_use_extern_class.carbon:[[@LINE+8]]:1: note: previously declared here [RedeclPrevDecl]
|
||||
// CHECK:STDERR: extern library "extern_class" class C;
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
// CHECK:STDERR: fail_use_extern_class.carbon:[[@LINE+4]]:1: error: semantics TODO: `extern library` [SemanticsTodo]
|
||||
// CHECK:STDERR: extern library "extern_class" class C;
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
extern library "extern_class" class C;
|
||||
|
||||
// CHECK:STDERR: fail_use_extern_class.carbon:[[@LINE+4]]:1: error: orphan `impl` found; something in the self-type or constraint must be defined in the same file [ImplIsOrphan]
|
||||
// CHECK:STDERR: impl C as ImportedY {}
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
impl C as ImportedY {}
|
||||
|
||||
// --- imported_first_owning_decl.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
class ImportedClassDecl;
|
||||
|
||||
interface ImportedInterface {}
|
||||
|
||||
// --- todo_fail_imported_first_owning_decl.impl.carbon
|
||||
impl library "[[@TEST_NAME]]";
|
||||
|
||||
// This is an owning decl, but not the first owning decl, which is in the api
|
||||
// file. So it can not be an anchor name.
|
||||
class ImportedClassDecl {}
|
||||
|
||||
// TODO: This should fail, there is no anchor name.
|
||||
impl ImportedClassDecl as ImportedInterface {}
|
||||
|
||||
Reference in New Issue
Block a user