diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index a0b373e8b650..bc903005c6f8 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -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 diff --git a/proposals/p007140-orphan-rule-for-scopes.md b/proposals/p007140-orphan-rule-for-scopes.md new file mode 100644 index 000000000000..5b932024794a --- /dev/null +++ b/proposals/p007140-orphan-rule-for-scopes.md @@ -0,0 +1,413 @@ +# Orphan rule for scopes + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/7140) + + + +## 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) + + + +## 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. diff --git a/toolchain/check/testdata/impl/orphan.carbon b/toolchain/check/testdata/impl/orphan.carbon index 114c310a4994..cac07136b5a6 100644 --- a/toolchain/check/testdata/impl/orphan.carbon +++ b/toolchain/check/testdata/impl/orphan.carbon @@ -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 {}