From 75b567102125a2ed30bb0225da40619e04da5c11 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Fri, 8 Mar 2024 14:11:22 -0800 Subject: [PATCH 01/15] Filling out template with PR 3762 --- proposals/p3762.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 proposals/p3762.md diff --git a/proposals/p3762.md b/proposals/p3762.md new file mode 100644 index 0000000000000..e9c0a0672549a --- /dev/null +++ b/proposals/p3762.md @@ -0,0 +1,70 @@ +# Merging forward declarations + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/3762) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Details](#details) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + + + +## Abstract + +TODO: Describe, in a succinct paragraph, the gist of this document. This +paragraph should be reproduced verbatim in the PR summary. + +## Problem + +TODO: What problem are you trying to solve? How important is that problem? Who +is impacted by it? + +## Background + +TODO: Is there any background that readers should consider to fully understand +this problem and your approach to solving it? + +## Proposal + +TODO: Briefly and at a high level, how do you propose to solve the problem? Why +will that in fact solve it? + +## Details + +TODO: Fully explain the details of the proposed solution. + +## Rationale + +TODO: How does this proposal effectively advance Carbon's goals? Rather than +re-stating the full motivation, this should connect that motivation back to +Carbon's stated goals and principles. This may evolve during review. Use links +to appropriate sections of [`/docs/project/goals.md`](/docs/project/goals.md), +and/or to documents in [`/docs/project/principles`](/docs/project/principles). +For example: + +- [Community and culture](/docs/project/goals.md#community-and-culture) +- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) +- [Performance-critical software](/docs/project/goals.md#performance-critical-software) +- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) +- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) +- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) +- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) +- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + +## Alternatives considered + +TODO: What alternative solutions have you considered? From ff2d47d59f766df9b597add2993ba3bc1abdc8a3 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Fri, 8 Mar 2024 15:12:34 -0800 Subject: [PATCH 02/15] Convert from Google doc --- proposals/p3762.md | 622 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 592 insertions(+), 30 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index e9c0a0672549a..edae1c74f6ac2 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -12,50 +12,357 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents -- [Abstract](#abstract) -- [Problem](#problem) -- [Background](#background) -- [Proposal](#proposal) -- [Details](#details) -- [Rationale](#rationale) -- [Alternatives considered](#alternatives-considered) +- [Prior discussion](#prior-discussion) +- [Forward declarations in current design](#forward-declarations-in-current-design) +- [ODR (One definition rule)](#odr-one-definition-rule) +- [Requiring matching declarations to merge](#requiring-matching-declarations-to-merge) +- [`extern` keyword](#extern-keyword) +- [When declarations are allowed](#when-declarations-are-allowed) + - [No forward declarations after declarations](#no-forward-declarations-after-declarations) + - [Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own) + - [Libraries cannot both define an entity and declare it `extern`](#libraries-cannot-both-define-an-entity-and-declare-it-extern) + - [Type scopes may contain both a forward declaration and definition](#type-scopes-may-contain-both-a-forward-declaration-and-definition) +- [Merging `extern` declarations](#merging-extern-declarations) +- [Other modifier keyword merging approaches](#other-modifier-keyword-merging-approaches) +- [No `extern` keyword](#no-extern-keyword) +- [Looser restrictions on declarations](#looser-restrictions-on-declarations) +- [`extern` naming](#extern-naming) +- [Default `extern` to private](#default-extern-to-private) +- [Opaque types](#opaque-types) -## Abstract +# Abstract -TODO: Describe, in a succinct paragraph, the gist of this document. This -paragraph should be reproduced verbatim in the PR summary. +- Add the `extern` keyword for forward declarations in libraries that don't + have the definition. +- Treat repeated forward declarations as redundant. + - Allow them when they prevent a dependence on an imported name. +- Clarify rules for when modifier keywords should be on forward declarations + and definitions. -## Problem +# Problem -TODO: What problem are you trying to solve? How important is that problem? Who -is impacted by it? +A forward declaration can be merged with a definition when they match. However, +there is ambiguity about behavior: -## Background +- Whether a forward declaration can be repeated, including after a definition. +- Whether a keyword is required to make a forward declaration separate from + the implementing library. +- Whether modifier keywords need to match between forward declarations and + definitions. -TODO: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +# Background -## Proposal +## Prior discussion -TODO: Briefly and at a high level, how do you propose to solve the problem? Why -will that in fact solve it? +- [Proposal #1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084) + covered specifics for `impl` and `interface`. +- [Issue #1132: How do we match forward declarations with their definitions?](https://github.com/carbon-language/carbon-lang/issues/1132) +- [Issue #3384: are abstract / base specifiers permitted or required on class forward declarations?](https://github.com/carbon-language/carbon-lang/issues/3384) + asks one question about which keywords exist on declarations versus + definitions. There has also been discussion about what happens for member + functions and other situations. At present there is no decision on #3384. +- TODO: Notes on + https://discord.com/channels/655572317891461132/709488742942900284/1201693766449102878 -## Details +## Forward declarations in current design -TODO: Fully explain the details of the proposed solution. +Example rules for forward declarations in the current design: -## Rationale +- [High-level](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/README.md#declarations-definitions-and-scopes) +- [Classes](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#forward-declaration) +- [Functions](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/functions.md#function-declarations) +- Generics: + - [`impl`](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-impl-declaration) + - [`interface`](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#declaring-interfaces-and-named-constraints) +- [Matching and agreeing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#matching-and-agreeing) + +## ODR (One definition rule) + +[C++'s ODR](https://en.cppreference.com/w/cpp/language/definition) requires each +entity to have only one definition. C++ has trouble detecting issues at compile +time, and linking has trouble catching linker issues too. ODR violation +detection is an active problem +(https://maskray.me/blog/2022-11-13-odr-violation-detection). + +This similarly applies to Carbon. In Carbon, only one library can define an +entity; other libraries can make forward declarations of it, but those don't +constitute a definition. Part of the goal in declaration syntax (`extern` in +particular) is to be able to better diagnose ODR violations based on +declarations. + +## Requiring matching declarations to merge + +When two declarations have the same name, either within the same file or through +imports, they need to "match", or will be diagnosed as a conflict. This is laid +out at under +[Matching and agreeing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#matching-and-agreeing) +in generics. This proposal does not significantly affect these type and name +restrictions. -TODO: How does this proposal effectively advance Carbon's goals? Rather than -re-stating the full motivation, this should connect that motivation back to -Carbon's stated goals and principles. This may evolve during review. Use links -to appropriate sections of [`/docs/project/goals.md`](/docs/project/goals.md), -and/or to documents in [`/docs/project/principles`](/docs/project/principles). For example: -- [Community and culture](/docs/project/goals.md#community-and-culture) +- `fn F(); fn F() {}` could merge because the function signatures match, + forward declaring a function is valid, and only one definition is provided. +- `fn F(); fn F(x: i32);` won't merge because they differ in parameters, + resulting in a diagnostic. + +# Proposal + +1. `extern` is used to mark forward declarations of entities defined in a + different library. + - `extern` must only be used for declarations in libraries that don't + contain the definition. It is invalid for the defining library to + declare as `extern`. + - In modifiers, `extern` comes immediately after access control keywords, + and before other keywords. +2. An entity may only be forward declared once in a given file. A forward + declaration is only allowed before a definition. +3. Declaring an entity as `extern` anywhere in a file means it must be used as + `extern` _throughout_ that file. + - The entity cannot be used _at all_ prior to the `extern` declaration, + even if there is an imported declaration. + - This allows refactoring to add and remove declarations without impacting + files that have an `extern`. +4. If a library declares an entity as non-`extern` in either the `api` or + `impl`, it is invalid to declare the same entity as `extern` elsewhere + within the library. +5. Modifier keywords will be handled on a case-by-case basis for merging + declarations. + +# Details + +## `extern` keyword + +An `extern` modifier keyword is added. It modifies a forward declaration to +indicate that the entity is not defined by the declaring library. A library +using `extern` to declare an entity cannot define that entity. Only the library +which defines an entity can omit `extern`, and omitting `extern` means it must +provide a definition. + +An `extern` declaration forms an entity which has no definition or storage. For +example: + +- `extern fn` forms a function that can be called. +- `extern class` forms an incomplete type. +- `extern interface` forms an undefined interface. +- `extern var` or `extern let` will bind the name without allocating storage. + Initializers are disallowed. + +The `extern` keyword is invalid on declarations that have _only_ a declaration +syntax and lack storage, such as `alias` or `namespace`. It is only valid on +namespace-scoped names; it is invalid on type-scoped names +(`class Foo { extern fn Member(); }`). + +In declaration modifiers, `extern` comes immediately after access control +keywords, and before other keywords. For example, +`private extern class B;`. At present, when `extern` is on a +declaration, only access modifiers are valid (see +[Modifier keywords](#modifier-keywords)). + +## When declarations are allowed + +When considering whether a declaration is allowed, we apply the rules: + +1. A declaration should always add new information. + - No declarations after a definition. +2. Only one library can declare an entity without `extern`. +3. Support moving declarations between already-imported `api` files without + affecting compilation of client libraries. + +### No forward declarations after declarations + +In a file, a forward declaration must never follow a forward declaration or +definition for the same entity. + +For example: + +```carbon +class A { ... } +// Invalid: Disallowed after the definition. +class A; + +class B; +// Invalid: Disallowed due to repetition. +class B: + +class C; +// Valid: Allowed because the definition is added. +class C { ... } +``` + +### Files must either use an imported declaration or declare their own + +In a file, if a declaration or definition of an entity is imported, the file +must choose between either using that version or declaring its own. It cannot do +both. + +For example: + +```carbon +package Foo library "a" api; + +class C { ... } +``` + +```carbon +package Foo library "b" api; +import library "a"; + +extern class C; + +// Valid: Uses the incomplete type of the extern declaration. +fn Foo(c: C*); +``` + +```carbon +package Foo library "c" api; +import library "a"; + +// Valid: Uses the complete type of the imported definition. +fn Foo(c: C); +``` + +```carbon +package Foo library "d" api; +import library "a"; + +fn Foo(c: C); + +// Invalid: `F` used the imported `C`, so an `extern` declaration of `C` is now +// invalid. +extern class C; +``` + +This should have limited effect on `api` and `impl` combinations, but will still +have some effect. For example: + +```carbon +package Bar library "a" api; + +class C { ... } +``` + +```carbon +package Bar library "a" impl; + +// Invalid: Imported the definition from the `api` file. +class C; +``` + +### Libraries cannot both define an entity and declare it `extern` + +In a library, if the `impl` defines an entity, the `api` must not use `extern` +when declaring it. + +For example: + +```carbon +package Wiz library "a" api; + +extern class C; +``` + +```carbon +package Wiz library "a" impl; + +// Invalid: The `api` file declared `C` as `extern`. +class C { ... } +``` + +In a library, the `api` might make an `extern` declaration that the `impl` +imports and uses the definition of. This is consistent because the `impl` file +is not declaring the entity. + +### Type scopes may contain both a forward declaration and definition + +The combination of a forward declaration and a definition is allowed in type +scopes. + +For example: + +```carbon +class C { + class D; + + fn F(); + class D { + fn G() { return C.F(); } + } + fn F() { D.G(); } +} +``` + +This is necessary because type bodies are not automatically moved out-of-line, +unlike function types. + +## Merging `extern` declarations + +For diagnostics, when merging imported declarations that don't differ (from +different imported libraries): + +- Two `extern` declarations will prefer the first. +- An `extern` and non-`extern` declaration will always prefer the non-`extern` + declaration. +- Two non-`extern` declarations is invalid. + - While a given file may both forward declare and have a separate + declaration with definition, they only export one declaration. + +For a new declaration in the importing file, an imported `extern` declaration is +always superseded. It is invalid to use the imported declaration before the new +declaration. It is considered the same entity for type checking, but diagnostics +will lean towards the local definition. + +For a new declaration in the importing file, an imported non-`extern` +declaration will: + +- If the new declaration is `extern`, the imported non-`extern` is superseded + as above. It is invalid to use the imported declaration before the new + declaration. +- If the new declaration is non-`extern`, the imported declaration must be + from the same library. + - The `api` may forward declare when the `impl` defines, or the `impl` may + repeat a forward declaration and also define. + +# Modifier keywords + +When considering various modifiers on a forward declaration versus definition: + +- `extern` is only valid on a forward declaration. Rules are detailed above. +- `extend` in `extend impl` is only on the declaration in the class body + (whether that is a forward declaration or definition), as described at + [Forward `impl` declaration](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-impl-declaration). +- Other class, impl, and interface modifiers (`abstract`, `base`, `final`) + exist only on the definition, not on the forward declaration. +- Function modifiers (`impl`, `virtual`, `default`, `abstract`, `base`, + `final`) must match between forward declaration and definition. + - This only affects type-scoped names because they are invalid on + namespace names. + - `abstract` won't have a definition so is moot here. +- Access modifiers (`private` and `protected`) must match. + - As an exception, an `extern` name may be `private` when the actual name + is public. + - This allows a library to forward declare another library's type + without allowing clients to depend on its forward declaration. + - On merging, the more public declaration will take precedence, hiding + `private extern` declarations. + - This affects both type-scoped names and namespace names. + +# Rationale + +TODO: Link to appropriate goals (probably delaying this for md) + +- Explicitly flagging `extern` will assist readers in understanding when a + library is working with a type in order to avoid dependency loops, with + minimal impact on writing code. +- Preventing redundant forward declarations should simplify potential code + structures and make meaning of entities clear. +- Marking forward declarations should improve diagnostic options, in turn + helping developers. + - Helps catch more ODR issues. +- One way principle for when we choose to make modifier keywords consistent. + - [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) - [Performance-critical software](/docs/project/goals.md#performance-critical-software) - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) @@ -65,6 +372,261 @@ For example: - [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) - [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) -## Alternatives considered +# Alternatives considered + +## Other modifier keyword merging approaches + +There has been intermittent discussion about which modifiers to allow or require +on forward declarations versus definitions. There are advantages and +disadvantages about redundancy and being able to copy-paste declarations. There +might be strict requirements for some modifiers to be present in order to +correctly use a forward declaration. + +This proposal suggests a partial decision here, at least for a reasonable +starting point that we can implement towards. This will likely evolve in future +proposals, particularly as more keywords are added. However, this still offers a +baseline. + +The trade-offs we consider are: + +- Consistency in when a modifier keyword is expected (if applicable) is + valuable. While we may change specifics about when or where a keyword is + required, making it either allowed or disallowed is preferred over making it + optional. +- Access control has a certain necessity for consistency, so that a consumer + of a forward declaration would still be allowed to use the definition if + refactored. + - We could require similar consistency on `extern`, and should have more + nuanced rules if the access control rules go beyond public and + `private`. For example, a package-private type shouldn't be allowed to + be made public through an `extern` declaration; but package-private + isn't actually part of the language right now. +- For a type, we are choosing to have minimal modifiers on the declaration. + - The modifiers we disallow (`abstract`, `base`, `final`, and `default`) + have no effect on uses because the forward declared type is incomplete. + - Requiring them would be somewhat inconsistent with C++, and may + primarily create toil. + - `extern` is a special-case where its presence is intrinsic to the + keyword's semantics. +- For a function, we are choosing to duplicate modifiers between the + declaration and definition. + - There is more emphasis on being able to copy-paste a function + declaration. This in particular may help developers more than classes + because all the parameters must also be copied. Things such as `static` + in C++ being declaration-only is also something we view as a source of + friction rather than a benefit. + - Modifiers such as `virtual` affect the calling convention of functions, + and as a consequence _must_ be on the first declaration. + +The most likely alternative would be to, for type member functions in specific, +disallow most modifiers. This would be because, unlike other situations, a +member must have a forward declaration if there is an out-of-line definition. We +would probably want to drop these collectively in order to maximize copy-paste +ability (ideally, everything before `fn` is dropped). However, it would shift +understandability of the definition in a way that may be harmful. For now this +proposal suggests adopting the more verbose approach and seeing how it goes. + +Similarly, we could allow flexibility to choose which modifier keywords are +provided where. For example, keywords on a function definition must be some +subset of the keywords on the declaration; keywords on a type declaration must +be some subset of the keywords on the definition. This would allow authors to +choose when they expect keywords will be most relevant. However, it could also +serve to detract from readability: two functions in the same file might be +declared similarly, but have different keywords on the definition, implying a +difference in behavior that would not exist. With consideration for the +[Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md), +this proposal takes the more prescriptive approach, rather than offering +flexibility. + +Note a common aspect between types and functions in this model is that modifiers +on the definition are typically a superset of modifiers on the declaration. +While looking at the declaration always gives an incomplete view, looking at the +definition can give a complete view. + +## No `extern` keyword + +If we had no `extern` keyword, then a declaration wouldn't give any hint of +whether a library contains the definition. Given two forward declarations in +different `api` files, either both or neither could have a definition in their +`impl` file. Sometimes this would be detected during linking, particularly if +both are linked together. However, providing an `extern` keyword gives a hint +about the intended behavior, allowing us to evaluate more cases for warnings. It +also gives a hint to the reader, about whether an entity is expected to be +declared later in the library (even if not in the same file). + +For example: + +```carbon +package Foo library "a" api; + +class C; +``` + +```carbon +package Foo library "a" impl; + +class C {} +``` + +```carbon +package Foo library "b" api; +import library "a"; + +class C {}; +``` + +Without the `extern` keyword, this code should be expected to compile. Ideally +it would be caught during linking that there are two definitions of `class C`, +but that relies on some tricks to catch issues. When `extern` is added, then the +processing of library "b" results in a conflict between the `class C;` forward +declared in the api of library "a". + +Note this probably does not fundamentally alter the amount that can be +diagnosed, but will mainly allow some diagnostics to occur during compilation +that otherwise would either be linker diagnostics or missed. + +The `extern` keyword is being added mainly for diagnostics and readability. + +## Looser restrictions on declarations + +We are being restrictive with declarations and when they're allowed. Primarily, +we want to avoid confusion with code such as: + +```carbon +class C { ... } + +// This declaration has no effect. +class C; +``` + +But, we could also allow code such as: + +```carbon +package Foo library "a" api; + +class C { ... } +``` + +```carbon +package Foo library "b" api; +import library "a"; + +fn F(c: C) { ... } + +extern class C; + +fn G(c: C*) { ... } +``` + +Here, `F` requires the imported definition of `C`. But, is `G` seeing an +incomplete type from the `extern`, or is the `extern` redundant and `G` sees the +imported definition of `C`? Would a library importing library "b" see `C` as an +incomplete type, or a complete type? + +In order to eliminate potential understandability issues with the choices we may +make, we are choosing the more restrictive approaches which disallow both of +these. In the first case, the redeclaration after a definition in the same file +is simply disallowed. In the second case, library "b" cannot declare `C` as +`extern` after using the imported definition. Restrictions such as these should +make code clearer by helping developers catch redundant, and possibly incorrect, +code. + +## `extern` naming + +Beyond `extern`, we also considered `external`. `extern` implies external +linkage in C++. We're choosing `extern` mainly for the small consistency with +C++. + +## Default `extern` to private + +The `extern` keyword could have an access control implication equivalent to +`private`. Then `extern` would need explicit work to export the symbol. The +`export` keyword was proposed for this purpose, with the idea that +`export import` syntax might also be provided to re-export all symbols of an +imported library. + +1. `extern` and `export` semantic consistency + + This would mean that `extern` declarations have different access control + than other declarations, which is a different visibility model to + understand, and may also not be intuitive from the "external" meaning. The + use of `export` matches C++'s `export` keyword, which may also imply to + developers that other semantics match C++, such as `export` being necessary + for all declared names. + +2. Interaction with additional access control features + + We'll probably also want more granular access control than just public and + private for API names. Adding package-private access modifier seems useful + (for example, Java provides this as `package`): in C++, this is sometimes + achieved through an "internal" or "details" namespace. If Carbon only + supports library-private symbols, that still addresses some of these + use-cases, but will sometimes require private implementation details to + exist in a single `api` file in order to get language-supported visibility + restrictions. In some cases this will result in an unwieldy amount of code + for a single file. + + For example of how package-private visibility might be used, gtest has + [an internal header directory](https://github.com/google/googletest/blob/main/googletest/include/gtest/internal) + that contains thousands of lines of code. If this needed to migrate to `api` + files in Carbon, it would be ideal if users could not access the names by + importing an internal library. + + For example of how this would interact: + + | Default visibility | Public (proposed) | Private (alternative) | + | ------------------------------ | ------------------------- | -------------------------------- | + | Public | `extern class C;` | `export extern class C;` | + | Library-private | `private extern class C;` | `extern class C;` | + | Package-private (hypothetical) | `package extern class C;` | `export package extern class C;` | + +3. Risks of a public extern + + When making an `extern fn`, it is callable. This creates a risk of a + function being declared as `extern` for intern use, but accidentally + allowing dependencies on the function. This risk could be mitigated by + requiring access control of an `extern` to be either equal to or more + restrictive than the original symbol (which might be hard to validate, but + could be validated when both symbols are seen together). + + When making an `extern class`, it's an incomplete type. It cannot be + instantiated, but pointers and references may be declared. This is only + really useful if there are functions which take a pointer as a parameter, + but an instance of the pointer could only be created by either unsafe casts + or if there's a function that returns the pointer type. + + Unsafe casts carry an inherent risk. In the case of a returned pointer, that + type could be captured by `auto`. Having `extern class` default to private + does not prevent the type's use. + +Considering the trade-offs involved combination of these three points, this +proposal suggests using the regular access control semantic. + +## Opaque types + +Omitting the `extern` modifier means a definition is required in a library. +There may be a use-case for opaque types which are "owned" by a library and have +no definition. If so, there are possible solutions such as a modifier keyword to +indicate an opaque type. For now, an empty definition in the `impl` file of a +library should have a similar effect: `api` users would not be able to provide a +definition. + +For example: + +```carbon +package Foo library "a" api; + +// An opaque type which can be imported by other libraries. +class C; +``` + +```carbon +package Foo library "a" impl; + +// An empty definition. This could be in its own file, or at the end after logic, +// to prevent misuse. +class C {} +``` -TODO: What alternative solutions have you considered? +This proposal requires a definition to exist. That choice may be reconsidered +later, based on use-cases. From 19553ef42cefaa29bdd71e9776b244b3d71b43c5 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Fri, 8 Mar 2024 15:46:21 -0800 Subject: [PATCH 03/15] TODO cleanup --- proposals/p3762.md | 61 +++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index edae1c74f6ac2..0a1255ff1fa40 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -63,8 +63,18 @@ there is ambiguity about behavior: asks one question about which keywords exist on declarations versus definitions. There has also been discussion about what happens for member functions and other situations. At present there is no decision on #3384. -- TODO: Notes on - https://discord.com/channels/655572317891461132/709488742942900284/1201693766449102878 +- The + [2023-11-21 toolchain meeting](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.y377x4v44h2c) + had some early discussion regarding forward declarations and the need for a + syntactic difference, which evolved to `extern` here. +- [On Discord's #syntax on 2024-01-29](https://discord.com/channels/655572317891461132/709488742942900284/1201693766449102878), + there was discussion about the question "What is the intended interaction + between declaration modifiers and qualified declaration names?" +- [On Discord's #syntax on 2024-02-23](https://discord.com/channels/655572317891461132/709488742942900284/1210694116955000932), + there was discussion that started about how `alias` would resolve forward + declaration conflicts. This continued to forward declarations in general, + and has discussion about `extern` use. + - This discussion led to a substantial fraction of this proposal. ## Forward declarations in current design @@ -351,26 +361,43 @@ When considering various modifiers on a forward declaration versus definition: # Rationale -TODO: Link to appropriate goals (probably delaying this for md) - -- Explicitly flagging `extern` will assist readers in understanding when a - library is working with a type in order to avoid dependency loops, with - minimal impact on writing code. -- Preventing redundant forward declarations should simplify potential code - structures and make meaning of entities clear. -- Marking forward declarations should improve diagnostic options, in turn - helping developers. - - Helps catch more ODR issues. -- One way principle for when we choose to make modifier keywords consistent. - -- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) -- [Performance-critical software](/docs/project/goals.md#performance-critical-software) - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) + - This proposal supports moving classes between libraries without + affecting the compilation of clients. + - Allowing a redundant `extern` declaration when a non-`extern` + declaration is imported allows _adding_ the class to an + already-imported library where `extern` declarations were previously + present. + - Requiring the use of a local `extern` declaration prevents + accidental uses that might hinder _removing_ the class from an + imported library. + - Requiring keywords on `extern` declarations only when they affect + calling conventions means that, in most cases, keywords can be added and + removed from declarations in the defining library without breaking + `extern` declarations in other libraries. - [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) + - Explicitly flagging `extern` will assist readers in understanding when a + library is working with a type in order to avoid dependency loops, with + minimal impact on writing code. + - Preventing redundant forward declaration removes a potential avenue for + confusion by making the meaning of entities clearer. + - Requiring keywords be repeated for type-scoped functions is intended to + improve readability. - [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) + - Requiring `extern` declarations be clearly marked should improve our + ability to diagnose ODR violations. This will help developers by + improving detection of a subtle correctness issue. - [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) -- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) + - `extern` declarations are considered essential to supporting separate + compilation of libraries, which in turn supports scaling compilation. - [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + - `extern` is chosen for consistency with C++, and carries a similar -- + albeit slightly different -- meaning. +- [Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md) + - Setting _requirements_ for whether a keyword belongs on a forward + declaration or with the definition, instead of making places _optional_, + supports developers making conclusions based on which keywords they see + -- either by presence or absence. # Alternatives considered From 289337cf4baee3114795cfef43c58e79d4c2c352 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Fri, 8 Mar 2024 15:52:53 -0800 Subject: [PATCH 04/15] Fix TOC --- proposals/p3762.md | 92 +++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index 0a1255ff1fa40..7dd06283aa74d 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -12,27 +12,35 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents -- [Prior discussion](#prior-discussion) -- [Forward declarations in current design](#forward-declarations-in-current-design) -- [ODR (One definition rule)](#odr-one-definition-rule) -- [Requiring matching declarations to merge](#requiring-matching-declarations-to-merge) -- [`extern` keyword](#extern-keyword) -- [When declarations are allowed](#when-declarations-are-allowed) - - [No forward declarations after declarations](#no-forward-declarations-after-declarations) - - [Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own) - - [Libraries cannot both define an entity and declare it `extern`](#libraries-cannot-both-define-an-entity-and-declare-it-extern) - - [Type scopes may contain both a forward declaration and definition](#type-scopes-may-contain-both-a-forward-declaration-and-definition) -- [Merging `extern` declarations](#merging-extern-declarations) -- [Other modifier keyword merging approaches](#other-modifier-keyword-merging-approaches) -- [No `extern` keyword](#no-extern-keyword) -- [Looser restrictions on declarations](#looser-restrictions-on-declarations) -- [`extern` naming](#extern-naming) -- [Default `extern` to private](#default-extern-to-private) -- [Opaque types](#opaque-types) +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) + - [Prior discussion](#prior-discussion) + - [Forward declarations in current design](#forward-declarations-in-current-design) + - [ODR (One definition rule)](#odr-one-definition-rule) + - [Requiring matching declarations to merge](#requiring-matching-declarations-to-merge) +- [Proposal](#proposal) +- [Details](#details) + - [`extern` keyword](#extern-keyword) + - [When declarations are allowed](#when-declarations-are-allowed) + - [No forward declarations after declarations](#no-forward-declarations-after-declarations) + - [Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own) + - [Libraries cannot both define an entity and declare it `extern`](#libraries-cannot-both-define-an-entity-and-declare-it-extern) + - [Type scopes may contain both a forward declaration and definition](#type-scopes-may-contain-both-a-forward-declaration-and-definition) + - [Merging `extern` declarations](#merging-extern-declarations) + - [Modifier keywords](#modifier-keywords) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + - [Other modifier keyword merging approaches](#other-modifier-keyword-merging-approaches) + - [No `extern` keyword](#no-extern-keyword) + - [Looser restrictions on declarations](#looser-restrictions-on-declarations) + - [`extern` naming](#extern-naming) + - [Default `extern` to private](#default-extern-to-private) + - [Opaque types](#opaque-types) -# Abstract +## Abstract - Add the `extern` keyword for forward declarations in libraries that don't have the definition. @@ -41,7 +49,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - Clarify rules for when modifier keywords should be on forward declarations and definitions. -# Problem +## Problem A forward declaration can be merged with a definition when they match. However, there is ambiguity about behavior: @@ -52,9 +60,9 @@ there is ambiguity about behavior: - Whether modifier keywords need to match between forward declarations and definitions. -# Background +## Background -## Prior discussion +### Prior discussion - [Proposal #1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084) covered specifics for `impl` and `interface`. @@ -76,7 +84,7 @@ there is ambiguity about behavior: and has discussion about `extern` use. - This discussion led to a substantial fraction of this proposal. -## Forward declarations in current design +### Forward declarations in current design Example rules for forward declarations in the current design: @@ -88,7 +96,7 @@ Example rules for forward declarations in the current design: - [`interface`](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#declaring-interfaces-and-named-constraints) - [Matching and agreeing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#matching-and-agreeing) -## ODR (One definition rule) +### ODR (One definition rule) [C++'s ODR](https://en.cppreference.com/w/cpp/language/definition) requires each entity to have only one definition. C++ has trouble detecting issues at compile @@ -102,7 +110,7 @@ constitute a definition. Part of the goal in declaration syntax (`extern` in particular) is to be able to better diagnose ODR violations based on declarations. -## Requiring matching declarations to merge +### Requiring matching declarations to merge When two declarations have the same name, either within the same file or through imports, they need to "match", or will be diagnosed as a conflict. This is laid @@ -118,7 +126,7 @@ For example: - `fn F(); fn F(x: i32);` won't merge because they differ in parameters, resulting in a diagnostic. -# Proposal +## Proposal 1. `extern` is used to mark forward declarations of entities defined in a different library. @@ -141,9 +149,9 @@ For example: 5. Modifier keywords will be handled on a case-by-case basis for merging declarations. -# Details +## Details -## `extern` keyword +### `extern` keyword An `extern` modifier keyword is added. It modifies a forward declaration to indicate that the entity is not defined by the declaring library. A library @@ -171,7 +179,7 @@ keywords, and before other keywords. For example, declaration, only access modifiers are valid (see [Modifier keywords](#modifier-keywords)). -## When declarations are allowed +### When declarations are allowed When considering whether a declaration is allowed, we apply the rules: @@ -181,7 +189,7 @@ When considering whether a declaration is allowed, we apply the rules: 3. Support moving declarations between already-imported `api` files without affecting compilation of client libraries. -### No forward declarations after declarations +#### No forward declarations after declarations In a file, a forward declaration must never follow a forward declaration or definition for the same entity. @@ -202,7 +210,7 @@ class C; class C { ... } ``` -### Files must either use an imported declaration or declare their own +#### Files must either use an imported declaration or declare their own In a file, if a declaration or definition of an entity is imported, the file must choose between either using that version or declaring its own. It cannot do @@ -261,7 +269,7 @@ package Bar library "a" impl; class C; ``` -### Libraries cannot both define an entity and declare it `extern` +#### Libraries cannot both define an entity and declare it `extern` In a library, if the `impl` defines an entity, the `api` must not use `extern` when declaring it. @@ -285,7 +293,7 @@ In a library, the `api` might make an `extern` declaration that the `impl` imports and uses the definition of. This is consistent because the `impl` file is not declaring the entity. -### Type scopes may contain both a forward declaration and definition +#### Type scopes may contain both a forward declaration and definition The combination of a forward declaration and a definition is allowed in type scopes. @@ -307,7 +315,7 @@ class C { This is necessary because type bodies are not automatically moved out-of-line, unlike function types. -## Merging `extern` declarations +### Merging `extern` declarations For diagnostics, when merging imported declarations that don't differ (from different imported libraries): @@ -335,7 +343,7 @@ declaration will: - The `api` may forward declare when the `impl` defines, or the `impl` may repeat a forward declaration and also define. -# Modifier keywords +### Modifier keywords When considering various modifiers on a forward declaration versus definition: @@ -359,7 +367,7 @@ When considering various modifiers on a forward declaration versus definition: `private extern` declarations. - This affects both type-scoped names and namespace names. -# Rationale +## Rationale - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) - This proposal supports moving classes between libraries without @@ -399,9 +407,9 @@ When considering various modifiers on a forward declaration versus definition: supports developers making conclusions based on which keywords they see -- either by presence or absence. -# Alternatives considered +## Alternatives considered -## Other modifier keyword merging approaches +### Other modifier keyword merging approaches There has been intermittent discussion about which modifiers to allow or require on forward declarations versus definitions. There are advantages and @@ -470,7 +478,7 @@ on the definition are typically a superset of modifiers on the declaration. While looking at the declaration always gives an incomplete view, looking at the definition can give a complete view. -## No `extern` keyword +### No `extern` keyword If we had no `extern` keyword, then a declaration wouldn't give any hint of whether a library contains the definition. Given two forward declarations in @@ -514,7 +522,7 @@ that otherwise would either be linker diagnostics or missed. The `extern` keyword is being added mainly for diagnostics and readability. -## Looser restrictions on declarations +### Looser restrictions on declarations We are being restrictive with declarations and when they're allowed. Primarily, we want to avoid confusion with code such as: @@ -558,13 +566,13 @@ is simply disallowed. In the second case, library "b" cannot declare `C` as make code clearer by helping developers catch redundant, and possibly incorrect, code. -## `extern` naming +### `extern` naming Beyond `extern`, we also considered `external`. `extern` implies external linkage in C++. We're choosing `extern` mainly for the small consistency with C++. -## Default `extern` to private +### Default `extern` to private The `extern` keyword could have an access control implication equivalent to `private`. Then `extern` would need explicit work to export the symbol. The @@ -629,7 +637,7 @@ imported library. Considering the trade-offs involved combination of these three points, this proposal suggests using the regular access control semantic. -## Opaque types +### Opaque types Omitting the `extern` modifier means a definition is required in a library. There may be a use-case for opaque types which are "owned" by a library and have From fec567fa76a7c3708812729308a50120788ffdbb Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Mon, 11 Mar 2024 10:59:55 -0700 Subject: [PATCH 05/15] Apply suggestions from code review Co-authored-by: Chandler Carruth --- proposals/p3762.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index 7dd06283aa74d..6e54374c846c8 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -43,7 +43,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Abstract - Add the `extern` keyword for forward declarations in libraries that don't - have the definition. + provide the definition. - Treat repeated forward declarations as redundant. - Allow them when they prevent a dependence on an imported name. - Clarify rules for when modifier keywords should be on forward declarations @@ -248,7 +248,7 @@ import library "a"; fn Foo(c: C); -// Invalid: `F` used the imported `C`, so an `extern` declaration of `C` is now +// Invalid: `Foo` used the imported `C`, so an `extern` declaration of `C` is now // invalid. extern class C; ``` @@ -304,16 +304,20 @@ For example: class C { class D; - fn F(); + fn F() -> D; + class D { - fn G() { return C.F(); } + fn G() -> Self { return C.F(); } + + var x: i32; } - fn F() { D.G(); } + + fn F() -> D { return {.x = 42}; } } ``` This is necessary because type bodies are not automatically moved out-of-line, -unlike function types. +unlike function bodies. ### Merging `extern` declarations @@ -330,7 +334,7 @@ different imported libraries): For a new declaration in the importing file, an imported `extern` declaration is always superseded. It is invalid to use the imported declaration before the new declaration. It is considered the same entity for type checking, but diagnostics -will lean towards the local definition. +should use the local definition. For a new declaration in the importing file, an imported non-`extern` declaration will: From 94e8fc3f0b583d9e00e9803cd15772f531ed1bc1 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Mon, 11 Mar 2024 12:03:56 -0700 Subject: [PATCH 06/15] Addressing comments --- proposals/p3762.md | 83 +++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index 6e54374c846c8..049d5cfe53175 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -305,13 +305,13 @@ class C { class D; fn F() -> D; - + class D { fn G() -> Self { return C.F(); } - + var x: i32; } - + fn F() -> D { return {.x = 42}; } } ``` @@ -321,15 +321,11 @@ unlike function bodies. ### Merging `extern` declarations -For diagnostics, when merging imported declarations that don't differ (from -different imported libraries): - -- Two `extern` declarations will prefer the first. -- An `extern` and non-`extern` declaration will always prefer the non-`extern` - declaration. -- Two non-`extern` declarations is invalid. - - While a given file may both forward declare and have a separate - declaration with definition, they only export one declaration. +When merging imported declarations from different libraries, multiple +declarations for the same entity may be found. Only one declaration is retained +for semantic checking and diagnostics. If available, a non-`extern` declaration +(potentially with a definition) is used; otherwise, the `extern` declaration +from the earliest import is used. For a new declaration in the importing file, an imported `extern` declaration is always superseded. It is invalid to use the imported declaration before the new @@ -429,9 +425,19 @@ baseline. The trade-offs we consider are: - Consistency in when a modifier keyword is expected (if applicable) is - valuable. While we may change specifics about when or where a keyword is - required, making it either allowed or disallowed is preferred over making it - optional. + valuable. + - Adding a keyword to an entity with a separate definition may require + adding the keyword to the forward declaration, the separate definition, + or both. It is disallowed where not required to be added. + - For example, `base` is added to `class C { ... }`, and disallowed on + `class C;`. + - Although we could make keywords optional where it would not affect + semantics, we prefer for the presence or absence of a keyword to carry a + clear meaning. + - For example, if `base` were optional to allow `base class C;`, then + an adjacent `class D;` lends itself to being incorrectly interpreted + as meaning "`D` is not a base class" when it actually means "`D` may + or may not be a base class". - Access control has a certain necessity for consistency, so that a consumer of a forward declaration would still be allowed to use the definition if refactored. @@ -443,8 +449,9 @@ The trade-offs we consider are: - For a type, we are choosing to have minimal modifiers on the declaration. - The modifiers we disallow (`abstract`, `base`, `final`, and `default`) have no effect on uses because the forward declared type is incomplete. - - Requiring them would be somewhat inconsistent with C++, and may - primarily create toil. + - Requiring them would end up leaking an implementation detail and + create toil. + - Requiring them would be somewhat inconsistent with C++. - `extern` is a special-case where its presence is intrinsic to the keyword's semantics. - For a function, we are choosing to duplicate modifiers between the @@ -457,30 +464,32 @@ The trade-offs we consider are: - Modifiers such as `virtual` affect the calling convention of functions, and as a consequence _must_ be on the first declaration. -The most likely alternative would be to, for type member functions in specific, -disallow most modifiers. This would be because, unlike other situations, a -member must have a forward declaration if there is an out-of-line definition. We -would probably want to drop these collectively in order to maximize copy-paste -ability (ideally, everything before `fn` is dropped). However, it would shift -understandability of the definition in a way that may be harmful. For now this -proposal suggests adopting the more verbose approach and seeing how it goes. - -Similarly, we could allow flexibility to choose which modifier keywords are -provided where. For example, keywords on a function definition must be some -subset of the keywords on the declaration; keywords on a type declaration must -be some subset of the keywords on the definition. This would allow authors to -choose when they expect keywords will be most relevant. However, it could also -serve to detract from readability: two functions in the same file might be -declared similarly, but have different keywords on the definition, implying a -difference in behavior that would not exist. With consideration for the +The most likely alternative would be to disallow most modifiers on out-of-line +definitions after a forward declaration, for type member functions in specific. +This would be because, unlike other situations, a member must have a forward +declaration if there is an out-of-line definition. We would probably want to +drop these collectively in order to maximize copy-paste ability (ideally, +everything before `fn` is dropped). However, it would shift understandability of +the definition in a way that may be harmful. For now this proposal suggests +adopting the more verbose approach and seeing how it goes. + +Another alternative is that we could allow flexibility to choose which modifier +keywords are provided where. For example, keywords on a function definition must +be some subset of the keywords on the declaration; keywords on a type +declaration must be some subset of the keywords on the definition. This would +allow authors to choose when they expect keywords will be most relevant. +However, it could also serve to detract from readability: two functions in the +same file might be declared similarly, but have different keywords on the +definition, implying a difference in behavior that would not exist. With +consideration for the [Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md), this proposal takes the more prescriptive approach, rather than offering flexibility. -Note a common aspect between types and functions in this model is that modifiers -on the definition are typically a superset of modifiers on the declaration. -While looking at the declaration always gives an incomplete view, looking at the -definition can give a complete view. +Note a common aspect between types and functions in the proposed model is that +modifiers on the definition are typically a superset of modifiers on the +declaration (`extern` as an exception). While looking at the declaration always +gives an incomplete view, looking at the definition can give a complete view. ### No `extern` keyword From 4650046a2a783e7d505d2ea9b6da6eac9eb02656 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Mon, 11 Mar 2024 13:44:17 -0700 Subject: [PATCH 07/15] Missed save --- proposals/p3762.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index 049d5cfe53175..ddef0ff2b5eff 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -648,7 +648,9 @@ imported library. does not prevent the type's use. Considering the trade-offs involved combination of these three points, this -proposal suggests using the regular access control semantic. +proposal suggests using the regular access control semantic. That choice may be +reconsidered later, based on how the semantics work out and any issues that +arise, particularly around access control. ### Opaque types From 4f0d82aa7f261496130ffce93624725952318e47 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Tue, 12 Mar 2024 13:46:38 -0700 Subject: [PATCH 08/15] Addressing comments, redeclarations --- proposals/p3762.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/proposals/p3762.md b/proposals/p3762.md index ddef0ff2b5eff..cde048a8dfed4 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -26,8 +26,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [No forward declarations after declarations](#no-forward-declarations-after-declarations) - [Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own) - [Libraries cannot both define an entity and declare it `extern`](#libraries-cannot-both-define-an-entity-and-declare-it-extern) + - [`impl` files with a forward declaration must contain the definition](#impl-files-with-a-forward-declaration-must-contain-the-definition) - [Type scopes may contain both a forward declaration and definition](#type-scopes-may-contain-both-a-forward-declaration-and-definition) - [Merging `extern` declarations](#merging-extern-declarations) + - [Using `extern` declarations in `extern impl`](#using-extern-declarations-in-extern-impl) - [Modifier keywords](#modifier-keywords) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) @@ -293,6 +295,20 @@ In a library, the `api` might make an `extern` declaration that the `impl` imports and uses the definition of. This is consistent because the `impl` file is not declaring the entity. +#### `impl` files with a forward declaration must contain the definition + +In an `impl` file, if the `impl` file forward declares an entity, it must also +provide the definition. In libraries with multiple `impl` files, this means that +using an entity in one `impl` file when it's defined in a different `impl` file +requires a (possibly `private`) forward declaration in the `api` file. An +`extern` declaration cannot be used for this purpose because the library defines +the entity. + +This allows Carbon to provide a compile-time diagnostic if an entity declared in +the `impl` is not defined locally. Note that an entity declared in the `api` may +still not get a compile-time diagnostic unless the compiler is told it's seeing +_all_ available `impl` files. + #### Type scopes may contain both a forward declaration and definition The combination of a forward declaration and a definition is allowed in type @@ -343,6 +359,15 @@ declaration will: - The `api` may forward declare when the `impl` defines, or the `impl` may repeat a forward declaration and also define. +### Using `extern` declarations in `extern impl` + +An `extern` type may only be used in the type structure of an `impl` if it's an +`extern impl`. They are incompatible with non-`extern` `impl`s. + +Consider two libraries, one defining `A` and declaring `B` as `extern`, and the +other defining `B` and declaring `A` as `extern`. Neither should be able to +define an `impl` involving both `A` and `B`, otherwise both could. + ### Modifier keywords When considering various modifiers on a forward declaration versus definition: From 59d45150bdbc7aa0ae9d5eb9832c703ea2bf9acc Mon Sep 17 00:00:00 2001 From: jonmeow Date: Wed, 13 Mar 2024 12:01:31 -0700 Subject: [PATCH 09/15] Alternative syntax --- proposals/p3762.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/proposals/p3762.md b/proposals/p3762.md index cde048a8dfed4..cbcf84da8b51d 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -39,6 +39,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [`extern` naming](#extern-naming) - [Default `extern` to private](#default-extern-to-private) - [Opaque types](#opaque-types) + - [Require a library provide its own `extern` declarations](#require-a-library-provide-its-own-extern-declarations) @@ -705,3 +706,68 @@ class C {} This proposal requires a definition to exist. That choice may be reconsidered later, based on use-cases. + +### Require a library provide its own `extern` declarations + +As proposed, any library can provide `extern` declarations for other libraries +in the same package. It was proposed that this should be restricted so that a +library would need to make `extern` declarations available, either through a +separate `extern` file (similar to `api` and `impl`) or through additional +markup in the `api` file which could be used to automatically generate an +`extern`-only subset of the `api` (still requiring entities which _should_ be +`extern` to be explicitly marked). + +Advantages: + +- Centralizes ownership of `extern` declarations. + - We are already planning to require a package to provide `extern` + declarations. This goes a step further, requiring the `extern` + declaration be provided by the same library that's defining the entity, + providing a clear, central ownership. +- Simplifies refactoring. + - The current plan of record is to require that `extern` declarations + agree with the library's declaration of the definition. This includes + small details such as parameter names. A consequence of this is that + changing these details in one declaration requires changing them in + _all_ declarations, atomically. There's a desire to limit the scope of + atomic refactorings; for example, similar package-scope atomic + refactoring requirements lead us to _allow_ certain redundant forward + declarations elsewhere in this proposal. + +Disadvantages: + +- Makes it difficult for libraries which want to use `extern` declarations to + use minimal imports for a declaration. + - If a library provides multiple `extern` declarations, the `extern` + file's imports would be a superset of the imports for those + declarations. If a client library only wants one of those `extern` + declarations, it would still get the full set of dependencies; under the + proposed syntax, only the single declaration's dependencies are + required. This allows for fewer dependencies. +- Adds additional work for migrating C++ forward declarations. + - As proposed, migrating a C++ forward declaration requires identifying + where that entity came from, adding `extern` if it's not the same + library (and is a Carbon library), and fixing any missing/differing + parameter names. Under this alternative, it might require creating an + additional build target for the `extern`, and removing the original + forward declaration. Creating the build target is additional work. + - In either case, forward declarations of C++ entities being migrated to + different packages in Carbon may require more significant work. This is + also true for forward declarations involving interoperability. +- Does not support use-cases that may occur in C++. + - It's possible that an extern declaration may depend on a defined entity, + where that entity is being defined in the same file. For example, + `class C { ... }; extern fn F(c: C);`. Under the proposed syntax, this + is supported; under the alternative syntax, another solution would need + to be found. There are potential ways to fix this through refactoring, + including moving one entity to a different library, changing the + `extern` declaration to use only `extern` declarations, or using generic + programming to create an indirection. However, each of these is a + refactoring that may hinder adoption. + +At present, the disadvantages are considered to outweigh the advantages. It's +possible that this may be revisited later if we get more code and libraries +using these tools and recognize some patterns that we can better or more +directly support. However, we should be hesitant to provide a second syntax for +`extern` if it's not adding substantial value, under +[Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md). From e368f834624c676642cc350f569a0c5b84f23254 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Thu, 14 Mar 2024 10:35:17 -0700 Subject: [PATCH 10/15] Migration notes --- proposals/p3762.md | 75 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index cbcf84da8b51d..f33affe139364 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -31,6 +31,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Merging `extern` declarations](#merging-extern-declarations) - [Using `extern` declarations in `extern impl`](#using-extern-declarations-in-extern-impl) - [Modifier keywords](#modifier-keywords) + - [Caveats](#caveats) + - [Migration of C++ forward declarations](#migration-of-c-forward-declarations) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [Other modifier keyword merging approaches](#other-modifier-keyword-merging-approaches) @@ -40,6 +42,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Default `extern` to private](#default-extern-to-private) - [Opaque types](#opaque-types) - [Require a library provide its own `extern` declarations](#require-a-library-provide-its-own-extern-declarations) + - [Allow cross-package `extern` declarations](#allow-cross-package-extern-declarations) @@ -393,6 +396,44 @@ When considering various modifiers on a forward declaration versus definition: `private extern` declarations. - This affects both type-scoped names and namespace names. +### Caveats + +#### Migration of C++ forward declarations + +This is not proposing a particular approach to migration. However, for +consideration of the proposal, it can be helpful to consider how migration will +work. + +It's expected under this approach that migration of a forward declaration will +require identifying the Carbon library that defines the entity. Then: + +- The forward declaration will need to be adjusted based on library and + package boundaries: + - If the forward declaration is disallowed in Carbon, it may need to be + removed. + - If the forward declaration is in the same library as the defining + library, then no `extern` is required. + - If the forward declaration is in a different library but the same + package, then `extern` is added. + - If the forward declaration is in a different package, the forward + declaration must be removed. To replace it, there are a couple options + which would need to be chosen by heuristic: + 1. Add a dependency on the actual definition. This might be infeasible + when the defining library has many complex dependencies. + 2. Add a library to the other package that provides the necessary + `extern` declaration. This might be infeasible when the package is + not owned by the package being migrated. + - If the forward declared code is in C++, we need to retain a forward + declaration in C++. A couple examples of how we might achieve that are: + - Provide Carbon syntax for an in-file forward declaration of C++ code + (for example, `extern cpp `). + - Create a small C++ header providing the forward declaration, and + depend on it. +- Fix meaningful differences in parameter names, for example by updating the + forward declaration's parameter names to match the definition. +- Fix meaningful differences in modifier keywords, for example by adding a + function forward declaration's modifiers to the definition. + ## Rationale - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) @@ -733,6 +774,14 @@ Advantages: atomic refactorings; for example, similar package-scope atomic refactoring requirements lead us to _allow_ certain redundant forward declarations elsewhere in this proposal. +- Reduces complexity for migrating C++ forward declarations. + - As described in + [Migration of C++ forward declarations](#migration-of-c-forward-declarations), + we expect migrating forward declarations to be difficult. Several of the + steps needed already can produce results similar to this alternative. + Under this alternative, we would make the handling of an entity defined + in a different library identical to as if it were in a different + package: a reduction of one case. Disadvantages: @@ -744,16 +793,6 @@ Disadvantages: declarations, it would still get the full set of dependencies; under the proposed syntax, only the single declaration's dependencies are required. This allows for fewer dependencies. -- Adds additional work for migrating C++ forward declarations. - - As proposed, migrating a C++ forward declaration requires identifying - where that entity came from, adding `extern` if it's not the same - library (and is a Carbon library), and fixing any missing/differing - parameter names. Under this alternative, it might require creating an - additional build target for the `extern`, and removing the original - forward declaration. Creating the build target is additional work. - - In either case, forward declarations of C++ entities being migrated to - different packages in Carbon may require more significant work. This is - also true for forward declarations involving interoperability. - Does not support use-cases that may occur in C++. - It's possible that an extern declaration may depend on a defined entity, where that entity is being defined in the same file. For example, @@ -771,3 +810,19 @@ using these tools and recognize some patterns that we can better or more directly support. However, we should be hesitant to provide a second syntax for `extern` if it's not adding substantial value, under [Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md). + +### Allow cross-package `extern` declarations + +We could choose a syntax for `extern` declarations that allows cross-package +`extern` declarations. These are effectively supported in C++, where there are +no package boundaries. Dropping support will create a migration barrier. + +However, there is a strong desire to restrict the use of cross-package +declarations in order to reduce the difficult and complex refactoring costs that +result from cross-package declarations: requiring that a particular name not +change its declaration category (a `class` must remain a `class`, preventing +`alias`) and that parameters (either function or generic) must remain the same, +not even allowing implicit conversions. + +The package boundaries serve an important purpose in balancing costs for +refactoring. From 453f6732b1b9c88a4c34961aab9ce974283a2220 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Wed, 20 Mar 2024 16:29:58 -0700 Subject: [PATCH 11/15] Addressing comments --- proposals/p3762.md | 144 +++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 63 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index f33affe139364..1f88fc7e30422 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -28,8 +28,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Libraries cannot both define an entity and declare it `extern`](#libraries-cannot-both-define-an-entity-and-declare-it-extern) - [`impl` files with a forward declaration must contain the definition](#impl-files-with-a-forward-declaration-must-contain-the-definition) - [Type scopes may contain both a forward declaration and definition](#type-scopes-may-contain-both-a-forward-declaration-and-definition) - - [Merging `extern` declarations](#merging-extern-declarations) - [Using `extern` declarations in `extern impl`](#using-extern-declarations-in-extern-impl) + - [`impl` lookup involving `extern` types](#impl-lookup-involving-extern-types) - [Modifier keywords](#modifier-keywords) - [Caveats](#caveats) - [Migration of C++ forward declarations](#migration-of-c-forward-declarations) @@ -134,25 +134,28 @@ For example: ## Proposal -1. `extern` is used to mark forward declarations of entities defined in a - different library. - - `extern` must only be used for declarations in libraries that don't - contain the definition. It is invalid for the defining library to - declare as `extern`. - - In modifiers, `extern` comes immediately after access control keywords, +1. Add `extern` declarations. + 1. `extern` is used to mark forward declarations of entities defined in a + different library. + - The defining library must declare the entity as public in an `api` + file. + - It is invalid for the defining library to declare as `extern`. + - `extern` must only be used for declarations in libraries that don't + contain the definition. + 2. In modifiers, `extern` comes immediately after access control keywords, and before other keywords. + 3. Declaring an entity as `extern` anywhere in a file means it must be used + as `extern` _throughout_ that file. + - The entity cannot be used _at all_ prior to the `extern` + declaration, even if there is an imported declaration. + - This allows refactoring to add and remove declarations without + impacting files that have an `extern`. + 4. If a library declares an entity as non-`extern` in either the `api` or + `impl`, it is invalid to declare the same entity as `extern` elsewhere + within the library. 2. An entity may only be forward declared once in a given file. A forward declaration is only allowed before a definition. -3. Declaring an entity as `extern` anywhere in a file means it must be used as - `extern` _throughout_ that file. - - The entity cannot be used _at all_ prior to the `extern` declaration, - even if there is an imported declaration. - - This allows refactoring to add and remove declarations without impacting - files that have an `extern`. -4. If a library declares an entity as non-`extern` in either the `api` or - `impl`, it is invalid to declare the same entity as `extern` elsewhere - within the library. -5. Modifier keywords will be handled on a case-by-case basis for merging +3. Modifier keywords will be handled on a case-by-case basis for merging declarations. ## Details @@ -209,7 +212,7 @@ class A; class B; // Invalid: Disallowed due to repetition. -class B: +class B; class C; // Valid: Allowed because the definition is added. @@ -259,22 +262,6 @@ fn Foo(c: C); extern class C; ``` -This should have limited effect on `api` and `impl` combinations, but will still -have some effect. For example: - -```carbon -package Bar library "a" api; - -class C { ... } -``` - -```carbon -package Bar library "a" impl; - -// Invalid: Imported the definition from the `api` file. -class C; -``` - #### Libraries cannot both define an entity and declare it `extern` In a library, if the `impl` defines an entity, the `api` must not use `extern` @@ -313,6 +300,19 @@ the `impl` is not defined locally. Note that an entity declared in the `api` may still not get a compile-time diagnostic unless the compiler is told it's seeing _all_ available `impl` files. +```carbon +package Bar library "a" api; + +class C { ... } +``` + +```carbon +package Bar library "a" impl; + +// Invalid: +class C; +``` + #### Type scopes may contain both a forward declaration and definition The combination of a forward declaration and a definition is allowed in type @@ -339,38 +339,42 @@ class C { This is necessary because type bodies are not automatically moved out-of-line, unlike function bodies. -### Merging `extern` declarations +### Using `extern` declarations in `extern impl` -When merging imported declarations from different libraries, multiple -declarations for the same entity may be found. Only one declaration is retained -for semantic checking and diagnostics. If available, a non-`extern` declaration -(potentially with a definition) is used; otherwise, the `extern` declaration -from the earliest import is used. +It is invalid for a non-`extern` `impl` declaration to use an `extern` type in +its type structure. -For a new declaration in the importing file, an imported `extern` declaration is -always superseded. It is invalid to use the imported declaration before the new -declaration. It is considered the same entity for type checking, but diagnostics -should use the local definition. +Consider two libraries, one defining `A` and declaring `B` as `extern`, and the +other defining `B` and declaring `A` as `extern`. Neither should be able to +define an `impl` involving both `A` and `B`, otherwise both could. -For a new declaration in the importing file, an imported non-`extern` -declaration will: +### `impl` lookup involving `extern` types -- If the new declaration is `extern`, the imported non-`extern` is superseded - as above. It is invalid to use the imported declaration before the new - declaration. -- If the new declaration is non-`extern`, the imported declaration must be - from the same library. - - The `api` may forward declare when the `impl` defines, or the `impl` may - repeat a forward declaration and also define. +If `impl` lookup involving `extern` types finds a non-`final` parameterized +`impl`, the result is that the lookup succeeds, but none of the values of the +associated entities of interface are known. This is because there may be another +more specialized `impl` that applies that is not visible (as can also happen +with constrained generics). -### Using `extern` declarations in `extern impl` +For example: + +``` +library "A" api; +class C { } +class D(T:! type) { } +impl forall [T:! type] D(T) as I where .Result = i32; +impl D(C) as I where .Result = bool; +``` -An `extern` type may only be used in the type structure of an `impl` if it's an -`extern impl`. They are incompatible with non-`extern` `impl`s. +``` +library "B" api; +extern class C; +extern class D(T:! type); +extern impl forall [T:! type] D(T) as I where .Result = i32; +``` -Consider two libraries, one defining `A` and declaring `B` as `extern`, and the -other defining `B` and declaring `A` as `extern`. Neither should be able to -define an `impl` involving both `A` and `B`, otherwise both could. +In the above, `D(C)` impls `I`, but with unknown `.Result`, since it might not +be `i32`. ### Modifier keywords @@ -382,8 +386,8 @@ When considering various modifiers on a forward declaration versus definition: [Forward `impl` declaration](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-impl-declaration). - Other class, impl, and interface modifiers (`abstract`, `base`, `final`) exist only on the definition, not on the forward declaration. -- Function modifiers (`impl`, `virtual`, `default`, `abstract`, `base`, - `final`) must match between forward declaration and definition. +- Function modifiers (`impl`, `virtual`, `default`, `abstract`, `final`) must + match between forward declaration and definition. - This only affects type-scoped names because they are invalid on namespace names. - `abstract` won't have a definition so is moot here. @@ -521,8 +525,19 @@ The trade-offs we consider are: - Requiring them would be somewhat inconsistent with C++. - `extern` is a special-case where its presence is intrinsic to the keyword's semantics. -- For a function, we are choosing to duplicate modifiers between the +- For type-scoped members, we are choosing to duplicate modifiers between the declaration and definition. + + - For example: + + ``` + class A { + private class B; + } + // `private` is required here. + private class A.B { ... } + ``` + - There is more emphasis on being able to copy-paste a function declaration. This in particular may help developers more than classes because all the parameters must also be copied. Things such as `static` @@ -530,6 +545,9 @@ The trade-offs we consider are: friction rather than a benefit. - Modifiers such as `virtual` affect the calling convention of functions, and as a consequence _must_ be on the first declaration. + - A downside of this approach is that it means the class name is inserted + in the middle of the out-of-scope definition, rather than near the + front. The most likely alternative would be to disallow most modifiers on out-of-line definitions after a forward declaration, for type member functions in specific. @@ -698,7 +716,7 @@ imported library. 3. Risks of a public extern When making an `extern fn`, it is callable. This creates a risk of a - function being declared as `extern` for intern use, but accidentally + function being declared as `extern` for internal use, but accidentally allowing dependencies on the function. This risk could be mitigated by requiring access control of an `extern` to be either equal to or more restrictive than the original symbol (which might be hard to validate, but From b22749d23d5374c54e10ff4cd152f6f505fdb3a7 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Wed, 20 Mar 2024 16:44:17 -0700 Subject: [PATCH 12/15] Remove unnecessary example code --- proposals/p3762.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index 1f88fc7e30422..b458b29fd7eea 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -359,15 +359,7 @@ with constrained generics). For example: ``` -library "A" api; -class C { } -class D(T:! type) { } -impl forall [T:! type] D(T) as I where .Result = i32; -impl D(C) as I where .Result = bool; -``` - -``` -library "B" api; +library "a" api; extern class C; extern class D(T:! type); extern impl forall [T:! type] D(T) as I where .Result = i32; From 4911951e560ef63fcf645b51315d3763e09d7a09 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Thu, 21 Mar 2024 08:58:38 -0700 Subject: [PATCH 13/15] Decl example --- proposals/p3762.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index b458b29fd7eea..eb8edb759feb8 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -300,6 +300,8 @@ the `impl` is not defined locally. Note that an entity declared in the `api` may still not get a compile-time diagnostic unless the compiler is told it's seeing _all_ available `impl` files. +For example: + ```carbon package Bar library "a" api; @@ -309,10 +311,39 @@ class C { ... } ```carbon package Bar library "a" impl; -// Invalid: +// Invalid: the `impl` file lacks (and cannot have) a definition. class C; ``` +This doesn't prevent `impl` from providing a forward declaration. It might when +it also provides the definition, which can be useful to unravel dependency +cycles: + +```carbon +package Bar library "a" api; + +class D; +``` + +``` +package Bar library "a" impl; + +class D; + +class E { + fn F[self: Self](d: D*) { ... } +} + +class D { + fn G[self: Self](e: E) { ... } +} +``` + +Here, the `impl` could not use the imported forward declaration in the `api` +because of the rule +[Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own). +Without `class D;` present, the definition of `F` would be invalid. + #### Type scopes may contain both a forward declaration and definition The combination of a forward declaration and a definition is allowed in type From d4a7cda7525ec8b2c7370b1cdba2184e8257317d Mon Sep 17 00:00:00 2001 From: jonmeow Date: Thu, 21 Mar 2024 14:25:20 -0700 Subject: [PATCH 14/15] appendix --- proposals/p3762.md | 80 +++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index eb8edb759feb8..f11c9358591ae 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -31,8 +31,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Using `extern` declarations in `extern impl`](#using-extern-declarations-in-extern-impl) - [`impl` lookup involving `extern` types](#impl-lookup-involving-extern-types) - [Modifier keywords](#modifier-keywords) - - [Caveats](#caveats) - - [Migration of C++ forward declarations](#migration-of-c-forward-declarations) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [Other modifier keyword merging approaches](#other-modifier-keyword-merging-approaches) @@ -43,6 +41,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Opaque types](#opaque-types) - [Require a library provide its own `extern` declarations](#require-a-library-provide-its-own-extern-declarations) - [Allow cross-package `extern` declarations](#allow-cross-package-extern-declarations) +- [Appendix](#appendix) + - [Migration of C++ forward declarations](#migration-of-c-forward-declarations) @@ -423,44 +423,6 @@ When considering various modifiers on a forward declaration versus definition: `private extern` declarations. - This affects both type-scoped names and namespace names. -### Caveats - -#### Migration of C++ forward declarations - -This is not proposing a particular approach to migration. However, for -consideration of the proposal, it can be helpful to consider how migration will -work. - -It's expected under this approach that migration of a forward declaration will -require identifying the Carbon library that defines the entity. Then: - -- The forward declaration will need to be adjusted based on library and - package boundaries: - - If the forward declaration is disallowed in Carbon, it may need to be - removed. - - If the forward declaration is in the same library as the defining - library, then no `extern` is required. - - If the forward declaration is in a different library but the same - package, then `extern` is added. - - If the forward declaration is in a different package, the forward - declaration must be removed. To replace it, there are a couple options - which would need to be chosen by heuristic: - 1. Add a dependency on the actual definition. This might be infeasible - when the defining library has many complex dependencies. - 2. Add a library to the other package that provides the necessary - `extern` declaration. This might be infeasible when the package is - not owned by the package being migrated. - - If the forward declared code is in C++, we need to retain a forward - declaration in C++. A couple examples of how we might achieve that are: - - Provide Carbon syntax for an in-file forward declaration of C++ code - (for example, `extern cpp `). - - Create a small C++ header providing the forward declaration, and - depend on it. -- Fix meaningful differences in parameter names, for example by updating the - forward declaration's parameter names to match the definition. -- Fix meaningful differences in modifier keywords, for example by adding a - function forward declaration's modifiers to the definition. - ## Rationale - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) @@ -867,3 +829,41 @@ not even allowing implicit conversions. The package boundaries serve an important purpose in balancing costs for refactoring. + +## Appendix + +### Migration of C++ forward declarations + +This is not proposing a particular approach to migration. However, for +consideration of the proposal, it can be helpful to consider how migration will +work. + +It's expected under this approach that migration of a forward declaration will +require identifying the Carbon library that defines the entity. Then: + +- The forward declaration will need to be adjusted based on library and + package boundaries: + - If the forward declaration is disallowed in Carbon, it may need to be + removed. + - If the forward declaration is in the same library as the defining + library, then no `extern` is required. + - If the forward declaration is in a different library but the same + package, then `extern` is added. + - If the forward declaration is in a different package, the forward + declaration must be removed. To replace it, there are a couple options + which would need to be chosen by heuristic: + 1. Add a dependency on the actual definition. This might be infeasible + when the defining library has many complex dependencies. + 2. Add a library to the other package that provides the necessary + `extern` declaration. This might be infeasible when the package is + not owned by the package being migrated. + - If the forward declared code is in C++, we need to retain a forward + declaration in C++. A couple examples of how we might achieve that are: + - Provide Carbon syntax for an in-file forward declaration of C++ code + (for example, `extern cpp `). + - Create a small C++ header providing the forward declaration, and + depend on it. +- Fix meaningful differences in parameter names, for example by updating the + forward declaration's parameter names to match the definition. +- Fix meaningful differences in modifier keywords, for example by adding a + function forward declaration's modifiers to the definition. From 4721a18c426eaf737fca334fa2f93224c4a570f1 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Thu, 21 Mar 2024 14:41:45 -0700 Subject: [PATCH 15/15] example --- proposals/p3762.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/proposals/p3762.md b/proposals/p3762.md index f11c9358591ae..b99ecb0300ffc 100644 --- a/proposals/p3762.md +++ b/proposals/p3762.md @@ -305,14 +305,21 @@ For example: ```carbon package Bar library "a" api; -class C { ... } +class C; + +class D { ... } ``` ```carbon package Bar library "a" impl; -// Invalid: the `impl` file lacks (and cannot have) a definition. +// Invalid: Missing a definition in the `impl` file, but if one were added, then +// this would be valid. class C; + +// Invalid: The `api` defines `D`. As a consequence, there is no way to make +// this forward declaration valid. +class D; ``` This doesn't prevent `impl` from providing a forward declaration. It might when