From 51e3f7892b4676e7c0467dbcc0c0b6483980184f Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 20 Mar 2024 23:13:37 +0000 Subject: [PATCH 01/24] Filling out template with PR 3802 --- proposals/p3802.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 proposals/p3802.md diff --git a/proposals/p3802.md b/proposals/p3802.md new file mode 100644 index 0000000000000..845d55e5df72b --- /dev/null +++ b/proposals/p3802.md @@ -0,0 +1,70 @@ +# `extend api` + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/3802) + + + +## 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 3af039bb116713c431e1c2a7acfed1cbf2f1ca4c Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 20 Mar 2024 23:22:26 +0000 Subject: [PATCH 02/24] Copy content from #3720 --- proposals/p3802.md | 316 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 4 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 845d55e5df72b..5bda19840dbef 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -17,15 +17,20 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Background](#background) - [Proposal](#proposal) - [Details](#details) + - [Member forwarding](#member-forwarding) + - [`extend api`](#extend-api-1) + - [Binding to the members of another type](#binding-to-the-members-of-another-type) + - [Other uses of `extend api`](#other-uses-of-extend-api) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) + - [Allow `extend api` of an incomplete type](#allow-extend-api-of-an-incomplete-type) + - [Make some name resolutions cases unambiguous with `extend api`](#make-some-name-resolutions-cases-unambiguous-with-extend-api) ## Abstract -TODO: Describe, in a succinct paragraph, the gist of this document. This -paragraph should be reproduced verbatim in the PR summary. +Allow types to `extend api` other types, for forwarding use cases. ## Problem @@ -39,13 +44,291 @@ 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? +In addition, we propose adding a declaration in a type definition to find names +defined in another type, [`extend api T`](#extend-api): + +```carbon +class Extended { + fn F[self: Self](); + fn G[self: Self](); +} + +class Extending { + fn G[self: Self](); + extend api Extended; + impl as ImplicitAs(Extended); +} + +var e: Extending; +``` + +This means that lookup into `Extending` also looks in the type `Extended` for +members. In this way, `e.F` will find `e.(Extended.F)`. Note that does not make +`e.F()` actually work unless `Extended.F` was somehow legal to call on an value +of type `Extending`. In the example, this is accomplished by defining an +implicit conversion from `Extending` to `Extended`. + +`extend api T` is equivalent to defining an `alias` for every member of `T` that +doesn't have a conflicting name. This means `Extending` is equivalent to: + +```carbon +class Extending { + fn G[self: Self](); + alias F = Extended.F; + impl as ImplicitAs(Extended); +} +``` + +This is used for [member forwarding use cases](#member-forwarding). ## Details TODO: Fully explain the details of the proposed solution. +### Member forwarding + +Consider a class that we want to act like it has the members of another type. +For example, a type `Box(T)` that has a pointer to a `T` object allocated on the +heap: + +```carbon +class Box(T:! type) { + var ptr: T*; + // ??? +} +``` + +`Box(T)` should act like it has all the members of `T`: + +```carbon +class String { + fn Search[self: Self](c: u8) -> i32; +} +var b: Box(String) = ...; +var position: i32 = b.Search(32); +``` + +There are two ingredients to make this work: + +- We need some way to make `b.Search` be equivalent to `b.(String.Search)` not + `b.(Box(String).Search)`. +- We need the act of binding a method of `String` to a `Box(String)` value + work by dereferencing the pointer `b.ptr`. + +#### `extend api` + +For the first ingredient, we need a way to customize +[simple member access](/docs/design/expressions/member_access.md) for the class +`Box(T)`. Normally simple member access on a value like `b` +[looks in the type of `b`](/docs/design/expressions/member_access.md#values). +However, types can designate other entities to also look up in using the +`extend` keyword (see [proposal #2760](/proposals/p2760.md#proposal)): + +- `extend impl as I` adds lookup into the implementation of an interface `I`. +- `extend base: B` adds lookup into the base class `B`. +- `extend adapt C` adds lookup into the adapted class `C`. +- [mixins are also planning](/proposals/p2760.md#future-work-mixins) on using + an `extend` syntax + +The natural choice is to add another way of using `extend` with consistent +lookup rules as the other uses of `extend`, with conflicts handled as determined +by leads issues +[#2355](https://github.com/carbon-language/carbon-lang/issues/2355) and +[#2745](https://github.com/carbon-language/carbon-lang/issues/2745). I propose +`extend api T`: + +```carbon +class Box(T:! type) { + var ptr: T*; + extend api T; +} +``` + +This means that lookup into `Box(T)` also looks in the type `T` for members. In +this way, `b.Search` will find `b.(String.Search)`. The extended type is +required to be complete at the point of the `extend api` declaration, so these +lookups into `T` can succeed. + +The general rule for resolving ambiguity for `extend`, which we apply here as +well, is that if lookup into `Box(T)` succeeds, then that result is used and no +lookup into `T` is performed. If a class uses `extend` more than once, and finds +the same name more than once, that is an ambiguity error that needs to be +resolved by qualifying the name on use. + +> **TODO:** Are these the right rules in the context of API evolution? See +> [comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527084183). + +> **Note:** This is an alternative to defining an `alias` for each member of the +> extended type. This avoids having to repeat them, which is both lengthy and +> could get out of sync as the class evolves. The `extend api` approach works +> even if, as in this example, we don't know the names of the members of the +> type being extended. + +Like other uses of `extend`, an `extend api` declaration do not have access +control modifiers and only operate on public names. + +> **Future:** We might want `extend api` to also get (some) interface +> implementations, like `extend base` does. Or this may be provided by a +> different mechanism, for example that allows customization of impl binding by +> implementing an interface. See +> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527083149) +> and +> [2024-03-18 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.23p8d6pqg1qp). + +#### Binding to the members of another type + +For the second ingredient, the binding interfaces already provide everything +needed, we just need to make a parameterized implementation of them: + +```carbon +impl forall [T:! type, U:! ValueBind(T)] + U as ValueBind(Box(T)) where .Result = U.Result { + fn Op[self: Self](x: Box(T)) -> Result { + // Uses `ValueBind(T).Op` based on type of `U`. + return self.Op(*x.ptr); + + // NOT `return x.ptr->(self)` since that attempts + // to do reference binding not value binding. + } +} + +impl forall [T:! type, U:! RefBind(T)] + U as RefBind(Box(T)) where .Result = U.Result { + fn Op[self: Self](p: Box(T)*) -> Result* { + return self.Op(p->ptr); + // Or equivalently: + // return p->ptr->(self); + } +} +``` + +A few observations: + +- These declarations use `Box` to satisfy the orphan rule, and so this + approach only works with `Box(T)` values, not types that can implicitly + convert to `Box(T)`. +- The implementation of the `Op` method is where we follow the pointer member + `ptr` of `Box(T)` to get a `T` value that is compatible with the member + being bound. This resolves the type mismatch that is introduced by allowing + name resolution to find another type's members. +- We have to be a little careful in the implementation of `ValueBind(Box(T))` + to still use value binding even when we get a reference expression from + dereferencing the pointer `ptr`. + +With these two ingredients, `b.Search(32)` is equivalent to +`b.(String.Search)(32)`, which is then equivalent to +`b.ptr->(String.Search)(32)` or `b.ptr->Search(32)` (since `b.ptr` has type +`String*`). + +### Other uses of `extend api` + +> TODO: Give the example of reference library type from +> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513712572). + +> TODO: Show how to model other language constructs with `extend api`, custom +> binding, and implicit (or explicit) conversions, as described in +> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564): +> +> - other uses of `extend`, +> - inheritance, +> - virtual dispatch, and +> - mixins. + +The [`extend api` mechanism](#extend-api), allowing lookup to find names from +another type, has other uses beyond [member forwarding](#member-forwarding). + +A class could extend the API of a class it implicitly converts to. For example, +imagine we have a class representing an integer in a restricted range that can +implicitly convert to an integer value. + + + +```carbon +class InRange(Low:! i32, High:! i32) { + var value: i32; + impl as ImplicitAs(i32) { + fn Convert[self: Self]() -> i32 { return self.value; } + } + extend api i32; +} +``` + +By including `extend api i32`, `InRange` gains support for any non-`addr` +methods on `i32`, like perhaps `Abs`. + +Or a class could have members specifically intended for use by another class, as +[extension methods](https://github.com/carbon-language/carbon-lang/issues/1345) +-- effectively acting as mixin except it can't add member variables. + +The examples so far use `extend api` between two classes, but we also allow it +with interfaces and named constraints. + +For example, a class can `extend api` of an interface it +[conditionally conforms to](/docs/design/generics/details.md#conditional-conformance), +as in: + +```carbon +interface A { + fn F(); +} +class C(T:! type) { + extend api A; +} +impl C(i32) as A { fn F() { ... } } +``` + +Here, the class `C(T)` implements `A` for some values of `T`, such as `i32`. +Rather than manually individually aliasing the members of `A` in `C`, to make +them available as part of the API of `C`, so that `C(i32).F()` is valid, an +`extend api` declaration includes all of them at once. + +Another use case is that an interface can `extend api` of another interface. In +this example, + +```carbon +interface A { + fn F(); +} +interface B { + extend api A; + fn G(); +} +``` + +`B.F` would be an alias for `A.F`, but without any implied +`require Self impls A`, in contrast with a plain `extend A`. So `extend I;` is +equivalent to `require Self impls I; extend api I;`. + +Lastly, an interface could `extend api` a class. This could be done to add +something that acts like `final` functions to the interface, using +[extension methods](https://github.com/carbon-language/carbon-lang/issues/1345), +as in: + +```carbon +class Helpers { + fn F[T:! type, self: T]() { DoStuffWith(self); } + // ... +} +interface Iface { + extend api Helpers; + fn G(); + // ... +} +class C { + extend impl as Iface; +} +fn Test(c: C) { + // Calls Helpers.F from extended class Helpers + c.F(); +} +``` + +Unlike `final` functions in an interface, this approach defines all the helpers +in a separate entity that could be used to extend more than one type. + ## Rationale TODO: How does this proposal effectively advance Carbon's goals? Rather than @@ -68,3 +351,28 @@ For example: ## Alternatives considered TODO: What alternative solutions have you considered? + +### Allow `extend api` of an incomplete type + +We considered allowing `extend api T` with `T` an incomplete type. This has some +disadvantages: + +- `T` must be complete before the first time name lookup into it is performed. + Actually avoiding name lookup into `T` is very difficult, though, since you + need to be very careful to fully qualify (using `package.`) names used in + the class definition, except those names defined within the class itself. +- Other uses of `extend`, such as `extend base: T` require the type to be + complete. +- We did not have a use case for using `extend api` with an incomplete type. +- Requiring complete types forces types to be defined in a total order, + preventing cycles (`A` extends `B` extends `C` extends `A`). + +This was discussed in the +[proposal review](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1507988547) +and +[the #typesystem channel on Discord](https://discord.com/channels/655572317891461132/708431657849585705/1217499991329738852). + +### Make some name resolutions cases unambiguous with `extend api` + +TODO: See +[comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513671030) From 5787dd33c1b349f9f33d5553a3e6382f06e1c5d3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 6 Apr 2024 01:59:55 +0000 Subject: [PATCH 03/24] Checkpoint progress. --- proposals/p3802.md | 343 ++++++++++++++++++++++++++------------------- 1 file changed, 201 insertions(+), 142 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 5bda19840dbef..ae8c0a7206798 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -17,10 +17,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Background](#background) - [Proposal](#proposal) - [Details](#details) - - [Member forwarding](#member-forwarding) - - [`extend api`](#extend-api-1) - - [Binding to the members of another type](#binding-to-the-members-of-another-type) - - [Other uses of `extend api`](#other-uses-of-extend-api) + - [`extend api` with interfaces](#extend-api-with-interfaces) + - [Use case: Member forwarding](#use-case-member-forwarding) + - [Use case: Reference library type](#use-case-reference-library-type) + - [Use case: with implicit conversion](#use-case-with-implicit-conversion) + - [Use case: extension methods](#use-case-extension-methods) + - [Modeling other language constructs](#modeling-other-language-constructs) + - [Future work: customizing impl binding](#future-work-customizing-impl-binding) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [Allow `extend api` of an incomplete type](#allow-extend-api-of-an-incomplete-type) @@ -34,18 +37,18 @@ Allow types to `extend api` other types, for forwarding use cases. ## Problem -TODO: What problem are you trying to solve? How important is that problem? Who +FIXME: 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 +FIXME: Is there any background that readers should consider to fully understand this problem and your approach to solving it? ## Proposal -In addition, we propose adding a declaration in a type definition to find names -defined in another type, [`extend api T`](#extend-api): +We propose adding a declaration in a type definition to find names defined in +another type, [`extend api T`](#extend-api): ```carbon class Extended { @@ -68,8 +71,11 @@ members. In this way, `e.F` will find `e.(Extended.F)`. Note that does not make of type `Extending`. In the example, this is accomplished by defining an implicit conversion from `Extending` to `Extended`. -`extend api T` is equivalent to defining an `alias` for every member of `T` that -doesn't have a conflicting name. This means `Extending` is equivalent to: +The lookup rules for `extend api` are consistent with the other uses of +`extend`, with conflicts handled as determined by leads issues +[#2355](https://github.com/carbon-language/carbon-lang/issues/2355) and +[#2745](https://github.com/carbon-language/carbon-lang/issues/2745). This means +`Extending` is equivalent to: ```carbon class Extending { @@ -79,13 +85,120 @@ class Extending { } ``` -This is used for [member forwarding use cases](#member-forwarding). +The extended type is required to be complete at the point of the `extend api` +declaration, so the lookups that will be performed in the the containing class +definition into the extended type can succeed. ## Details -TODO: Fully explain the details of the proposed solution. +Normally simple member access on a value +[looks in the type of that value](/docs/design/expressions/member_access.md#values). +However, types can designate other entities to also look up in using the +`extend` keyword (see [proposal #2760](/proposals/p2760.md#proposal)): + +- `extend impl as I` adds lookup into the implementation of an interface `I`. +- `extend base: B` adds lookup into the base class `B`. +- `extend adapt C` adds lookup into the adapted class `C`. +- [mixins are also planning](/proposals/p2760.md#future-work-mixins) on using + an `extend` syntax + +For some use cases, we want to be able to add name to lookup into that type +without making other changes to the type, for which we introduce `extend api`, +with consistent lookup rules as the other uses of `extend`, with conflicts +handled as determined by leads issues +[#2355](https://github.com/carbon-language/carbon-lang/issues/2355) and +[#2745](https://github.com/carbon-language/carbon-lang/issues/2745). + +The extended type is required to be complete at the point of the `extend api` +declaration, so these lookups into `T` can succeed. + +The general rule for resolving ambiguity for `extend`, which we apply here as +well, is that if lookup into `Box(T)` succeeds, then that result is used and no +lookup into `T` is performed. If a class uses `extend` more than once, and finds +the same name more than once, that is an ambiguity error that needs to be +resolved by qualifying the name on use. + +> **FIXME:** Are these the right rules in the context of API evolution? See +> [comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527084183). + +> **Note:** This is an alternative to defining an `alias` for each member of the +> extended type. This avoids having to repeat them, which is both lengthy and +> could get out of sync as the class evolves. The `extend api` approach works +> even if, as in this example, we don't know the names of the members of the +> type being extended. + +Like other uses of `extend`, an `extend api` declaration do not have access +control modifiers and only operate on public names. + +### `extend api` with interfaces + +The examples so far use `extend api` between two classes, but we also allow it +with interfaces and named constraints. + +For example, a class can `extend api` of an interface it +[conditionally conforms to](/docs/design/generics/details.md#conditional-conformance), +as in: + +```carbon +interface A { + fn F(); +} +class C(T:! type) { + extend api A; +} +impl C(i32) as A { fn F() { ... } } +``` + +Here, the class `C(T)` implements `A` for some values of `T`, such as `i32`. +Rather than manually individually aliasing the members of `A` in `C`, to make +them available as part of the API of `C`, so that `C(i32).F()` is valid, an +`extend api` declaration includes all of them at once. + +Another use case is that an interface can `extend api` of another interface. In +this example, + +```carbon +interface A { + fn F(); +} +interface B { + extend api A; + fn G(); +} +``` + +`B.F` would be an alias for `A.F`, but without any implied +`require Self impls A`, in contrast with a plain `extend A`. So `extend I;` is +equivalent to `require Self impls I; extend api I;`. -### Member forwarding +Lastly, an interface could `extend api` a class. This could be done to add +something that acts like `final` functions to the interface, using +[extension methods](https://github.com/carbon-language/carbon-lang/issues/1345), +as in: + +```carbon +class Helpers { + fn F[T:! type, self: T]() { DoStuffWith(self); } + // ... +} +interface Iface { + extend api Helpers; + fn G(); + // ... +} +class C { + extend impl as Iface; +} +fn Test(c: C) { + // Calls Helpers.F from extended class Helpers + c.F(); +} +``` + +Unlike `final` functions in an interface, this approach defines all the helpers +in a separate entity that could be used to extend more than one type. + +### Use case: Member forwarding Consider a class that we want to act like it has the members of another type. For example, a type `Box(T)` that has a pointer to a `T` object allocated on the @@ -115,27 +228,9 @@ There are two ingredients to make this work: - We need the act of binding a method of `String` to a `Box(String)` value work by dereferencing the pointer `b.ptr`. -#### `extend api` - -For the first ingredient, we need a way to customize +For the first ingredient, we use `extend api` to customize [simple member access](/docs/design/expressions/member_access.md) for the class -`Box(T)`. Normally simple member access on a value like `b` -[looks in the type of `b`](/docs/design/expressions/member_access.md#values). -However, types can designate other entities to also look up in using the -`extend` keyword (see [proposal #2760](/proposals/p2760.md#proposal)): - -- `extend impl as I` adds lookup into the implementation of an interface `I`. -- `extend base: B` adds lookup into the base class `B`. -- `extend adapt C` adds lookup into the adapted class `C`. -- [mixins are also planning](/proposals/p2760.md#future-work-mixins) on using - an `extend` syntax - -The natural choice is to add another way of using `extend` with consistent -lookup rules as the other uses of `extend`, with conflicts handled as determined -by leads issues -[#2355](https://github.com/carbon-language/carbon-lang/issues/2355) and -[#2745](https://github.com/carbon-language/carbon-lang/issues/2745). I propose -`extend api T`: +`Box(T)`: ```carbon class Box(T:! type) { @@ -145,40 +240,12 @@ class Box(T:! type) { ``` This means that lookup into `Box(T)` also looks in the type `T` for members. In -this way, `b.Search` will find `b.(String.Search)`. The extended type is -required to be complete at the point of the `extend api` declaration, so these -lookups into `T` can succeed. - -The general rule for resolving ambiguity for `extend`, which we apply here as -well, is that if lookup into `Box(T)` succeeds, then that result is used and no -lookup into `T` is performed. If a class uses `extend` more than once, and finds -the same name more than once, that is an ambiguity error that needs to be -resolved by qualifying the name on use. - -> **TODO:** Are these the right rules in the context of API evolution? See -> [comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527084183). - -> **Note:** This is an alternative to defining an `alias` for each member of the -> extended type. This avoids having to repeat them, which is both lengthy and -> could get out of sync as the class evolves. The `extend api` approach works -> even if, as in this example, we don't know the names of the members of the -> type being extended. - -Like other uses of `extend`, an `extend api` declaration do not have access -control modifiers and only operate on public names. - -> **Future:** We might want `extend api` to also get (some) interface -> implementations, like `extend base` does. Or this may be provided by a -> different mechanism, for example that allows customization of impl binding by -> implementing an interface. See -> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527083149) -> and -> [2024-03-18 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.23p8d6pqg1qp). - -#### Binding to the members of another type +this way, `b.Search` will find `b.(String.Search)`. -For the second ingredient, the binding interfaces already provide everything -needed, we just need to make a parameterized implementation of them: +For the second ingredient, the binding interfaces from +[proposal #3720: "Binding operators"](https://github.com/carbon-language/carbon-lang/pull/3720) +already provide everything needed, we just need to make a parameterized +implementation of them: ```carbon impl forall [T:! type, U:! ValueBind(T)] @@ -220,22 +287,57 @@ With these two ingredients, `b.Search(32)` is equivalent to `b.ptr->(String.Search)(32)` or `b.ptr->Search(32)` (since `b.ptr` has type `String*`). -### Other uses of `extend api` +### Use case: Reference library type -> TODO: Give the example of reference library type from +> FIXME: Give the example of reference library type from > [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513712572). -> TODO: Show how to model other language constructs with `extend api`, custom -> binding, and implicit (or explicit) conversions, as described in -> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564): -> -> - other uses of `extend`, -> - inheritance, -> - virtual dispatch, and -> - mixins. +The `Box` type that was used as +[an example of member forwarding](#use-case-member-forwarding) preserves the +expression category -- you can only call mutating operations on a `Box` +reference expression, not on a `Box` value expression. For a library reference +type, we instead want to permit value binding for anything where `T` permits +reference binding: -The [`extend api` mechanism](#extend-api), allowing lookup to find names from -another type, has other uses beyond [member forwarding](#member-forwarding). +```carbon +class Ref(T:! type) { + var ptr: T*; + extend api T; +} +impl forall [T:! type, U:! RefBind(T)] + U as ValueBind(Ref(T)) where .Result = Ref(U.Result) { + fn Op[self: Self](p: Ref(T)) -> Ref(Result) { + return {.ptr = self.Op(p->ptr)}; + } +} +// (Plus impls for the other three combinations of ValueBind and RefBind.) +impl forall [S:! type, T:! Call(S)] Ref(T) as Call(S) where .Result = T.Result { + // ... +} +``` + +For example, it could be used as: + +```carbon +class C { + fn F[addr self: Self*](); + var n: i32; +} +fn F(r: Ref(C)) { + // OK, even though `r` is a value expression. + // `r.F` is a `Ref()`, + // which is callable. + r.F(); + + // OK, forms a reference to the `n` member. + let n_ref: Ref(i32) = r.n; +} +``` + +Note, however, `Ref(T)` doesn't preserve the interface implementations of `T`, +see [future work](#future-work-customizing-impl-binding). + +### Use case: with implicit conversion A class could extend the API of a class it implicitly converts to. For example, imagine we have a class representing an integer in a restricted range that can @@ -259,79 +361,38 @@ class InRange(Low:! i32, High:! i32) { By including `extend api i32`, `InRange` gains support for any non-`addr` methods on `i32`, like perhaps `Abs`. +### Use case: extension methods + Or a class could have members specifically intended for use by another class, as [extension methods](https://github.com/carbon-language/carbon-lang/issues/1345) -- effectively acting as mixin except it can't add member variables. -The examples so far use `extend api` between two classes, but we also allow it -with interfaces and named constraints. - -For example, a class can `extend api` of an interface it -[conditionally conforms to](/docs/design/generics/details.md#conditional-conformance), -as in: - -```carbon -interface A { - fn F(); -} -class C(T:! type) { - extend api A; -} -impl C(i32) as A { fn F() { ... } } -``` - -Here, the class `C(T)` implements `A` for some values of `T`, such as `i32`. -Rather than manually individually aliasing the members of `A` in `C`, to make -them available as part of the API of `C`, so that `C(i32).F()` is valid, an -`extend api` declaration includes all of them at once. - -Another use case is that an interface can `extend api` of another interface. In -this example, - -```carbon -interface A { - fn F(); -} -interface B { - extend api A; - fn G(); -} -``` +> **FIXME:** Give an example -`B.F` would be an alias for `A.F`, but without any implied -`require Self impls A`, in contrast with a plain `extend A`. So `extend I;` is -equivalent to `require Self impls I; extend api I;`. +### Modeling other language constructs -Lastly, an interface could `extend api` a class. This could be done to add -something that acts like `final` functions to the interface, using -[extension methods](https://github.com/carbon-language/carbon-lang/issues/1345), -as in: +> FIXME: Show how to model other language constructs with `extend api`, custom +> binding, and implicit (or explicit) conversions, as described in +> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564): +> +> - other uses of `extend`, +> - inheritance, +> - virtual dispatch, and +> - mixins. -```carbon -class Helpers { - fn F[T:! type, self: T]() { DoStuffWith(self); } - // ... -} -interface Iface { - extend api Helpers; - fn G(); - // ... -} -class C { - extend impl as Iface; -} -fn Test(c: C) { - // Calls Helpers.F from extended class Helpers - c.F(); -} -``` +### Future work: customizing impl binding -Unlike `final` functions in an interface, this approach defines all the helpers -in a separate entity that could be used to extend more than one type. +> **Future:** We might want `extend api` to also get (some) interface +> implementations, like `extend base` does. Or this may be provided by a +> different mechanism, for example that allows customization of impl binding by +> implementing an interface. See +> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527083149) +> and +> [2024-03-18 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.23p8d6pqg1qp). ## Rationale -TODO: How does this proposal effectively advance Carbon's goals? Rather than +FIXME: 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), @@ -350,8 +411,6 @@ For example: ## Alternatives considered -TODO: What alternative solutions have you considered? - ### Allow `extend api` of an incomplete type We considered allowing `extend api T` with `T` an incomplete type. This has some From c99b2e8de7e0198a06b5d71daab4b5a3117a2c2a Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 18 Apr 2024 15:58:35 +0000 Subject: [PATCH 04/24] Checkpoint progress. --- proposals/p3802.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index ae8c0a7206798..6c23e880cbd0a 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -33,13 +33,18 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Abstract -Allow types to `extend api` other types, for forwarding use cases. +Allow types to `extend api` other types, adding the names from the other types +to its namespace, for forwarding and delegation use cases. ## Problem FIXME: What problem are you trying to solve? How important is that problem? Who is impacted by it? +**Non-goal:** Addressing how one type can inherit the interface implementations +of another type. This occurs with `extend base` and `extend adapt`. That is +[future work](#future-work-customizing-impl-binding). + ## Background FIXME: Is there any background that readers should consider to fully understand @@ -382,13 +387,24 @@ Or a class could have members specifically intended for use by another class, as ### Future work: customizing impl binding -> **Future:** We might want `extend api` to also get (some) interface -> implementations, like `extend base` does. Or this may be provided by a -> different mechanism, for example that allows customization of impl binding by -> implementing an interface. See -> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527083149) -> and -> [2024-03-18 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.23p8d6pqg1qp). +We might want a mechanism to also get (some) interface implementations from an +extended type. This already occurs for `extend adapt`, from +[proposal #731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731), +and refined by +[open discussion on 2023-04-06](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ&tab=t.0#heading=h.x4syhdu36xdc) +and [PR #3231](https://github.com/carbon-language/carbon-lang/pull/3231). That +open discussion expressed that interface implementations would also be inherited +with `extend base`, though there are more cases where you can't use a base class +implementation for the derived class than with adapters. + +For example, the mechanism might allow customization of impl binding by +implementing an interface. See +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527083149) +and +[2024-03-18 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.23p8d6pqg1qp). +It is unclear how that would work, since the interfaces that would be +implemented by `extend base` and `extend adapt` are different, and +[the rules for conversions allowed by adapters are complex](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#adapter-compatibility). ## Rationale From ad86e81e96080fc0ae5309b4c3648719e428b962 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 19 Apr 2024 21:03:27 +0000 Subject: [PATCH 05/24] Checkpoint progress. --- proposals/p3802.md | 217 +++++++++++++++++++++++++++++---------------- 1 file changed, 141 insertions(+), 76 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 6c23e880cbd0a..3e9672d5e02ed 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -17,12 +17,19 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Background](#background) - [Proposal](#proposal) - [Details](#details) - - [`extend api` with interfaces](#extend-api-with-interfaces) - [Use case: Member forwarding](#use-case-member-forwarding) - [Use case: Reference library type](#use-case-reference-library-type) - [Use case: with implicit conversion](#use-case-with-implicit-conversion) - [Use case: extension methods](#use-case-extension-methods) + - [`extend api` with interfaces](#extend-api-with-interfaces) - [Modeling other language constructs](#modeling-other-language-constructs) + - [Modeling `extend impl`](#modeling-extend-impl) + - [Modeling `extend base`](#modeling-extend-base) + - [Modeling virtual dispatch](#modeling-virtual-dispatch) + - [Modeling `extend adapt`](#modeling-extend-adapt) + - [Modeling mixins](#modeling-mixins) + - [Modeling `extend` in interfaces and named constraints](#modeling-extend-in-interfaces-and-named-constraints) + - [Modeling `extend` in interfaces and named constraints](#modeling-extend-in-interfaces-and-named-constraints-1) - [Future work: customizing impl binding](#future-work-customizing-impl-binding) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) @@ -76,6 +83,9 @@ members. In this way, `e.F` will find `e.(Extended.F)`. Note that does not make of type `Extending`. In the example, this is accomplished by defining an implicit conversion from `Extending` to `Extended`. +Type definitions here include `class`, `interface`, and `constraint` +definitions. + The lookup rules for `extend api` are consistent with the other uses of `extend`, with conflicts handled as determined by leads issues [#2355](https://github.com/carbon-language/carbon-lang/issues/2355) and @@ -98,7 +108,7 @@ definition into the extended type can succeed. Normally simple member access on a value [looks in the type of that value](/docs/design/expressions/member_access.md#values). -However, types can designate other entities to also look up in using the +However, classes can designate other entities to also look up in using the `extend` keyword (see [proposal #2760](/proposals/p2760.md#proposal)): - `extend impl as I` adds lookup into the implementation of an interface `I`. @@ -107,6 +117,10 @@ However, types can designate other entities to also look up in using the - [mixins are also planning](/proposals/p2760.md#future-work-mixins) on using an `extend` syntax +Interfaces and named constraints also +[use the `extend` keyword](/docs/design/generics/details.md#interface-extension) +to lookup into another, which is also required. + For some use cases, we want to be able to add name to lookup into that type without making other changes to the type, for which we introduce `extend api`, with consistent lookup rules as the other uses of `extend`, with conflicts @@ -135,74 +149,6 @@ resolved by qualifying the name on use. Like other uses of `extend`, an `extend api` declaration do not have access control modifiers and only operate on public names. -### `extend api` with interfaces - -The examples so far use `extend api` between two classes, but we also allow it -with interfaces and named constraints. - -For example, a class can `extend api` of an interface it -[conditionally conforms to](/docs/design/generics/details.md#conditional-conformance), -as in: - -```carbon -interface A { - fn F(); -} -class C(T:! type) { - extend api A; -} -impl C(i32) as A { fn F() { ... } } -``` - -Here, the class `C(T)` implements `A` for some values of `T`, such as `i32`. -Rather than manually individually aliasing the members of `A` in `C`, to make -them available as part of the API of `C`, so that `C(i32).F()` is valid, an -`extend api` declaration includes all of them at once. - -Another use case is that an interface can `extend api` of another interface. In -this example, - -```carbon -interface A { - fn F(); -} -interface B { - extend api A; - fn G(); -} -``` - -`B.F` would be an alias for `A.F`, but without any implied -`require Self impls A`, in contrast with a plain `extend A`. So `extend I;` is -equivalent to `require Self impls I; extend api I;`. - -Lastly, an interface could `extend api` a class. This could be done to add -something that acts like `final` functions to the interface, using -[extension methods](https://github.com/carbon-language/carbon-lang/issues/1345), -as in: - -```carbon -class Helpers { - fn F[T:! type, self: T]() { DoStuffWith(self); } - // ... -} -interface Iface { - extend api Helpers; - fn G(); - // ... -} -class C { - extend impl as Iface; -} -fn Test(c: C) { - // Calls Helpers.F from extended class Helpers - c.F(); -} -``` - -Unlike `final` functions in an interface, this approach defines all the helpers -in a separate entity that could be used to extend more than one type. - ### Use case: Member forwarding Consider a class that we want to act like it has the members of another type. @@ -374,16 +320,135 @@ Or a class could have members specifically intended for use by another class, as > **FIXME:** Give an example +### `extend api` with interfaces + +The examples so far use `extend api` between two classes, but we also allow it +with interfaces and named constraints. + +For example, a class can `extend api` of an interface it +[conditionally conforms to](/docs/design/generics/details.md#conditional-conformance), +as in: + +```carbon +interface A { + fn F(); +} +class C(T:! type) { + extend api A; +} +impl C(i32) as A { fn F() { ... } } +``` + +Here, the class `C(T)` implements `A` for some values of `T`, such as `i32`. +Rather than manually individually aliasing the members of `A` in `C`, to make +them available as part of the API of `C`, so that `C(i32).F()` is valid, an +`extend api` declaration includes all of them at once. + +Another use case is that an interface can `extend api` of another interface. In +this example, + +```carbon +interface A { + fn F(); +} +interface B { + extend api A; + fn G(); +} +``` + +`B.F` would be an alias for `A.F`, but without any implied +`require Self impls A`, in contrast with a plain `extend A`. So `extend I;` is +equivalent to `require Self impls I; extend api I;`. + +Lastly, an interface could `extend api` a class. This could be done to add +something that acts like `final` functions to the interface, using +[extension methods](https://github.com/carbon-language/carbon-lang/issues/1345), +as in: + +```carbon +class Helpers { + fn F[T:! type, self: T]() { DoStuffWith(self); } + // ... +} +interface Iface { + extend api Helpers; + fn G(); + // ... +} +class C { + extend impl as Iface; +} +fn Test(c: C) { + // Calls Helpers.F from extended class Helpers + c.F(); +} +``` + +Unlike `final` functions in an interface, this approach defines all the helpers +in a separate entity that could be used to extend more than one type. + ### Modeling other language constructs > FIXME: Show how to model other language constructs with `extend api`, custom > binding, and implicit (or explicit) conversions, as described in -> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564): -> -> - other uses of `extend`, -> - inheritance, -> - virtual dispatch, and -> - mixins. +> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564). + +#### Modeling `extend impl` + +```carbon +interface I { ... } +class C { + extend impl as I; +} +``` + +is equivalent to: + +```carbon +interface I { ... } +class C { + impl as I; + extend api I; +} +``` + +#### Modeling `extend base` + +FIXME + +#### Modeling virtual dispatch + +FIXME + +#### Modeling `extend adapt` + +FIXME + +#### Modeling mixins + +FIXME + +#### Modeling `extend` in interfaces and named constraints + +```carbon +interface A { ... } +interface B { + extend A; +} +``` + +is equivalent to + +```carbon +interface A { ... } +interface B { + require Self impls A; + extend api A; +} +``` + +#### Modeling `extend` in interfaces and named constraints ### Future work: customizing impl binding From 23af39eadc99db305a907308c4a3c6d3393c97a3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 26 Apr 2024 21:53:08 +0000 Subject: [PATCH 06/24] Checkpoint progress. --- proposals/p3802.md | 176 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 160 insertions(+), 16 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 3e9672d5e02ed..9b36fd6b4f69e 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -27,9 +27,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Modeling `extend base`](#modeling-extend-base) - [Modeling virtual dispatch](#modeling-virtual-dispatch) - [Modeling `extend adapt`](#modeling-extend-adapt) - - [Modeling mixins](#modeling-mixins) - [Modeling `extend` in interfaces and named constraints](#modeling-extend-in-interfaces-and-named-constraints) - - [Modeling `extend` in interfaces and named constraints](#modeling-extend-in-interfaces-and-named-constraints-1) + - [Future work: modeling mixins](#future-work-modeling-mixins) - [Future work: customizing impl binding](#future-work-customizing-impl-binding) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) @@ -40,22 +39,56 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Abstract -Allow types to `extend api` other types, adding the names from the other types -to its namespace, for forwarding and delegation use cases. +Allow types to `extend api` other types, adding the names from the other type to +its namespace, for forwarding and delegation use cases. ## Problem -FIXME: What problem are you trying to solve? How important is that problem? Who -is impacted by it? - -**Non-goal:** Addressing how one type can inherit the interface implementations -of another type. This occurs with `extend base` and `extend adapt`. That is +Carbon has a number of constructs using the `extend` keyword that allow a type +to extend its API by the API of another type in various ways. For example, a +class can use `extend base` to inherit from a base class, or an interface can +`extend` another interface. One component of this is including the names of the +extended type in the extending type. + +This capability is useful on its own, particularly when combined with the +ability customize how binding works for a type, as introduced in +[proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720), for +example [member forwarding](#use-case-member-forwarding). + +It also allows most of the current `extend` constructs to be expressed as +[a rewrite into more primitive language feature](#modeling-other-language-constructs). +This is a useful strategy in general: + +- It gives a concrete way of explaining the language semantics. +- It means that the rules governing different language constructs are + consistent, which helps user learning and understanding, and allows the + implementation to be smaller due to reuse of logic. +- It gives users additional expressive power through the unbundling of + capabilities the user might want to use individually, and the ability to + combine the primitives in new ways. + +**Non-goal:** This proposal does not address how one type can inherit the +_interface implementations_ of another type. This occurs with `extend adapt` and +is planned with `extend base`. Controlling that capability is [future work](#future-work-customizing-impl-binding). ## Background -FIXME: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +This proposal builds upon a number of previous proposals: + +- [Proposal #553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) + introduced interface extension and a way for classes to include the API of + implemented interfaces. +- [Proposal #731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731) + defined extending adapters. +- [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777) + defined how a class could extend a base class. +- [Proposal 2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) + changed the syntax to consistently use the `extend` keyword for any language + construct that involves delegating name lookup to another type. +- [Proposal #3720: Binding operators](https://github.com/carbon-language/carbon-lang/pull/3720) + introduced customization of how member binding works, as in `x.y` or + `x.(y)`. This proposal was originally part of #3720, but was split out. ## Proposal @@ -423,14 +456,123 @@ FIXME #### Modeling `extend adapt` -FIXME +First, let us consider `adapt` without `extend`. For example, -#### Modeling mixins +```carbon +class A { ... } +class B { + adapt A; +} +``` -FIXME +is equivalent to: + +```carbon +class A { ... } +class B { + var __a: A; +} +impl B as As(A) { + fn Convert[self: B]() -> A { + return self.__a; + } +} +impl A as As(B) { + fn Convert[self: A]() -> B { + return {.__a = self} as B; + } +} +impl forall [T:! ValueBind(A)] + T as ValueBind(B) where .Result = T.Result { + fn Op[self: Self](x: B) -> Result { + return self.Op(x as A); + } +} +impl forall [T:! RefBind(A)] + T as RefBind(B) where .Result = T.Result { + fn Op[self: Self](p: B*) -> Result* { + return self.Op(&p->__a); + } +} +``` + +In addition, there are `as` conversions that convert reference expressions to +reference expressions, which is currently not expressible by implementing +interfaces. + +Note that the blanket implementations of the binding operator interfaces allow +methods of `A` to be used on values and objects of type `B`, as in: + +```carbon +class A { + fn F[self: Self](x: Self) -> Self; + fn G[self: Self](y: Self) -> Self; +} + +class B { + adapt A; + alias AG = A.G; +} + +var a: A = {}; +var b_ref: B = {}; +let b_val: B = {}; + +// Allowed: binds `b_val` to `A.F`. Since `b_val` is a value expression, +// it uses `A.F as ValueBind(B)`, so this is equivalent to +// `(b_val as A).(A.F)(a)`. +b_val.(A.F)(a); + +// Allowed: binds `b_ref` to `A.F`. Since `b_ref` is a reference +// expression, it uses `A.F as RefBind(B)`, so this is equivalent to: +// `(&((&b_ref)->__a))->(A.F)(a)`. +b_ref.(A.F)(a); + +// These also work, and show how aliases can be used to add methods of +// `A` to `B`. +b_val.AG(a); +b_ref.AG(a); +``` + +Notice how custom binding implementation adapts the type of `self`, but not any +other `Self` type used in the signature. So `b_ref.(A.F)` is a function that +takes and returns `A` values, not `B` values. + +Now consider adding the `extend` modifier before `adapt`, as in: + +```carbon +class A { + fn F[self: Self](x: Self) -> Self; + fn G[self: Self](y: Self) -> Self; +} + +class B { + extend adapt A; +} +``` + +The `extend` does two things: + +- It adds `extend api A;` to class `B`. Due to the custom binding + implementation provided by `adapt A`, this allows calling the methods + defined on `A` on values and objects of type `B`. + +- For an interface `I`, when no implementation `B as I` is found, it will + attempt to adapt the implementation `A as I`, as described in + [the existing "extending adapter" section of the design](/docs/design/generics/details.md#extending-adapter). + Note though that the the `Self` types in signatures in members of `I` + **are** changed to `B`, as required to be an implementation of `B as I`, + though this is + [not always possible](/docs/design/generics/details.md#adapter-compatibility). + Allowing the user to do this directly is + [future work](#future-work-customizing-impl-binding). #### Modeling `extend` in interfaces and named constraints +The `extend` declaration in interfaces and named constraints is equivalent to a +requirement that the extended type is implemented plus an `extend api` to add +the names of the extended type to the extending type. For example, + ```carbon interface A { ... } interface B { @@ -438,7 +580,7 @@ interface B { } ``` -is equivalent to +is equivalent to: ```carbon interface A { ... } @@ -448,7 +590,9 @@ interface B { } ``` -#### Modeling `extend` in interfaces and named constraints +#### Future work: modeling mixins + +FIXME ### Future work: customizing impl binding From 6e8bd8731ef298ecac429cee27f246aaf2ea8d84 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 27 Apr 2024 00:46:06 +0000 Subject: [PATCH 07/24] Checkpoint progress. --- proposals/p3802.md | 89 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 9b36fd6b4f69e..09e51bfbdd530 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -267,21 +267,18 @@ A few observations: dereferencing the pointer `ptr`. With these two ingredients, `b.Search(32)` is equivalent to -`b.(String.Search)(32)`, which is then equivalent to -`b.ptr->(String.Search)(32)` or `b.ptr->Search(32)` (since `b.ptr` has type -`String*`). +`b.(String.Search)(32)` due to `extend api`, which then uses the custom binding +implementation to get something equivalent to `b.ptr->(String.Search)(32)`, +which is the same as `b.ptr->Search(32)` since `b.ptr` has type `String*`. ### Use case: Reference library type -> FIXME: Give the example of reference library type from -> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513712572). - The `Box` type that was used as [an example of member forwarding](#use-case-member-forwarding) preserves the expression category -- you can only call mutating operations on a `Box` reference expression, not on a `Box` value expression. For a library reference -type, we instead want to permit value binding for anything where `T` permits -reference binding: +type that would simulate the C++-built-in reference type, we instead want to +permit value binding for anything where `T` permits reference binding: ```carbon class Ref(T:! type) { @@ -321,6 +318,9 @@ fn F(r: Ref(C)) { Note, however, `Ref(T)` doesn't preserve the interface implementations of `T`, see [future work](#future-work-customizing-impl-binding). +This application of `extend api` and binding was first described in +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513712572). + ### Use case: with implicit conversion A class could extend the API of a class it implicitly converts to. For example, @@ -329,7 +329,7 @@ implicitly convert to an integer value. ```carbon @@ -343,15 +343,54 @@ class InRange(Low:! i32, High:! i32) { ``` By including `extend api i32`, `InRange` gains support for any non-`addr` -methods on `i32`, like perhaps `Abs`. +methods on `i32`, like perhaps `Abs`. The method `i32.Abs` has a `self` type of +`i32`, which an `InRange` value can implicitly convert to. ### Use case: extension methods -Or a class could have members specifically intended for use by another class, as +A class could have members specifically intended for use by another class, as [extension methods](https://github.com/carbon-language/carbon-lang/issues/1345) -- effectively acting as mixin except it can't add member variables. -> **FIXME:** Give an example +```carbon +class ThreeExtension { + fn Three() -> i32 { + return 3; + } +} + +class EmptyExtension(template T:! type) { + fn Empty[self: T]() -> bool { + return self.Size() == 0; + } +} + +class I32Range { + var low: i32; + var high: i32; + + // Adds `Three` class function + extend api ThreeExtension; + // Roughly equivalent to: + // `alias Three = ThreeExtension.Three;` + + fn Size[self: Self]() -> i32 { + return self.high - self.low; + } + + // Adds an `Empty` method. + extend api EmptyExtension(I32Range); +} + +// Because of `extend api`, `I32Range.Three` is equivalent to +// `ThreeExtension.Three`. +Assert(I32Range.Three() == 3); + +var r: I32Range = {.low = 2, .high = 5}; +// Because of `extend api`, `r.Empty` is equivalent to +// `r.(EmptyExtension(I32Range).Empty)` +Assert(r.Empty() == false); +``` ### `extend api` with interfaces @@ -397,7 +436,7 @@ equivalent to `require Self impls I; extend api I;`. Lastly, an interface could `extend api` a class. This could be done to add something that acts like `final` functions to the interface, using [extension methods](https://github.com/carbon-language/carbon-lang/issues/1345), -as in: +as [can be done with classes](#use-case-extension-methods): ```carbon class Helpers { @@ -418,14 +457,28 @@ fn Test(c: C) { } ``` -Unlike `final` functions in an interface, this approach defines all the helpers -in a separate entity that could be used to extend more than one type. +This is approximately equivalent to using an interface with `final` functions: + +```carbon +interface Helpers { + final fn F[self: Self]() { DoStuffWith(self); } + // ... +} +interface Iface { + extend Helpers; + fn G(); + // ... +} +``` + +This relies on the fact that implementing `Iface` will also implement extended +interfaces like `Helpers`. ### Modeling other language constructs -> FIXME: Show how to model other language constructs with `extend api`, custom -> binding, and implicit (or explicit) conversions, as described in -> [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564). +[This comment on proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564) +observed that `extend api` could be used to model other language constructs +using `extend`. #### Modeling `extend impl` From 05729f410addc66b5ceb3431f0ec826cee8094ba Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 1 May 2024 22:18:00 +0000 Subject: [PATCH 08/24] Checkpoint progress. --- proposals/p3802.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 09e51bfbdd530..2c2b502c9c673 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -170,8 +170,9 @@ lookup into `T` is performed. If a class uses `extend` more than once, and finds the same name more than once, that is an ambiguity error that needs to be resolved by qualifying the name on use. -> **FIXME:** Are these the right rules in the context of API evolution? See -> [comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527084183). +> **Future work:** Are these the right rules in the context of API evolution? +> See +> [comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/97004ac6748a4f39e664d7409401df1811adc25c#r1527084183). > **Note:** This is an alternative to defining an `alias` for each member of the > extended type. This avoids having to repeat them, which is both lengthy and @@ -278,7 +279,8 @@ The `Box` type that was used as expression category -- you can only call mutating operations on a `Box` reference expression, not on a `Box` value expression. For a library reference type that would simulate the C++-built-in reference type, we instead want to -permit value binding for anything where `T` permits reference binding: +permit value binding for anything where `T` permits reference binding. This can +also be done using `extend api` and a different set of binding implementations: ```carbon class Ref(T:! type) { @@ -319,7 +321,7 @@ Note, however, `Ref(T)` doesn't preserve the interface implementations of `T`, see [future work](#future-work-customizing-impl-binding). This application of `extend api` and binding was first described in -[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513712572). +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/37e967ff53e89e345eecb49ec79c6dfbe18a3c54#r1513712572). ### Use case: with implicit conversion @@ -333,7 +335,7 @@ implicitly convert to an integer value. --> ```carbon -class InRange(Low:! i32, High:! i32) { +class I32InRange(Low:! i32, High:! i32) { var value: i32; impl as ImplicitAs(i32) { fn Convert[self: Self]() -> i32 { return self.value; } @@ -342,9 +344,9 @@ class InRange(Low:! i32, High:! i32) { } ``` -By including `extend api i32`, `InRange` gains support for any non-`addr` +By including `extend api i32`, `I32InRange` gains support for any non-`addr` methods on `i32`, like perhaps `Abs`. The method `i32.Abs` has a `self` type of -`i32`, which an `InRange` value can implicitly convert to. +`i32`, which an `I32InRange` value can implicitly convert to. ### Use case: extension methods @@ -365,7 +367,7 @@ class EmptyExtension(template T:! type) { } } -class I32Range { +class RangeOfI32 { var low: i32; var high: i32; @@ -379,16 +381,16 @@ class I32Range { } // Adds an `Empty` method. - extend api EmptyExtension(I32Range); + extend api EmptyExtension(RangeOfI32); } -// Because of `extend api`, `I32Range.Three` is equivalent to +// Because of `extend api`, `RangeOfI32.Three` is equivalent to // `ThreeExtension.Three`. -Assert(I32Range.Three() == 3); +Assert(RangeOfI32.Three() == 3); -var r: I32Range = {.low = 2, .high = 5}; +var r: RangeOfI32 = {.low = 2, .high = 5}; // Because of `extend api`, `r.Empty` is equivalent to -// `r.(EmptyExtension(I32Range).Empty)` +// `r.(EmptyExtension(RangeOfI32).Empty)` Assert(r.Empty() == false); ``` @@ -430,8 +432,8 @@ interface B { ``` `B.F` would be an alias for `A.F`, but without any implied -`require Self impls A`, in contrast with a plain `extend A`. So `extend I;` is -equivalent to `require Self impls I; extend api I;`. +`require Self impls A`, +[in contrast with a plain `extend A`](#modeling-extend-in-interfaces-and-named-constraints). Lastly, an interface could `extend api` a class. This could be done to add something that acts like `final` functions to the interface, using @@ -476,7 +478,7 @@ interfaces like `Helpers`. ### Modeling other language constructs -[This comment on proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527089564) +[This comment on proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/97004ac6748a4f39e664d7409401df1811adc25c#r1527089564) observed that `extend api` could be used to model other language constructs using `extend`. @@ -661,7 +663,7 @@ implementation for the derived class than with adapters. For example, the mechanism might allow customization of impl binding by implementing an interface. See -[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1527083149) +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/97004ac6748a4f39e664d7409401df1811adc25c#r1527083149) and [2024-03-18 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.23p8d6pqg1qp). It is unclear how that would work, since the interfaces that would be From a3495e51e37a09e2fad144e8e019bc5bd7dbf6e7 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 2 May 2024 05:05:38 +0000 Subject: [PATCH 09/24] Checkpoint progress. --- proposals/p3802.md | 147 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 3 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 2c2b502c9c673..64b2c8fcb01e1 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -25,7 +25,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Modeling other language constructs](#modeling-other-language-constructs) - [Modeling `extend impl`](#modeling-extend-impl) - [Modeling `extend base`](#modeling-extend-base) - - [Modeling virtual dispatch](#modeling-virtual-dispatch) + - [Modeling virtual dispatch](#modeling-virtual-dispatch) - [Modeling `extend adapt`](#modeling-extend-adapt) - [Modeling `extend` in interfaces and named constraints](#modeling-extend-in-interfaces-and-named-constraints) - [Future work: modeling mixins](#future-work-modeling-mixins) @@ -503,9 +503,150 @@ class C { #### Modeling `extend base` -FIXME +[Class inheritance](/docs/design/classes.md#inheritance) can be modelled using +`extend api`, outside of planned +[interface implementation inheritance](#future-work-customizing-impl-binding). +This is done by the extending class: + +- using `extend api` of the base class; +- having a data member holding the base class; +- defining + [implicit conversion to the base class](#use-case-with-implicit-conversion); +- [modeling virtual dispatch](#modeling-virtual-dispatch). + +In this example, + +```carbon +base class B { + var i: i32; + + fn Get[self: Self]() -> i32 { + return self.i; + } + fn Set[addr self: Self*](x: i32) { + self->i = x; + } +} + +class D { + extend base B; + var increment_by: i32; + + fn Make(init: i32) -> D { + return {.base = {.i = init}, .increment_by = 1}; + } + + fn Increment[addr self: Self*]() { + self->Set(self->i + self->increment_by); + } +} + +var d: D = D.Make(2); +``` + +The class `D` could be replaced by: + +```carbon +class D { + extend api B; + var __base: B; + var increment_by: i32; + + fn Make(init: i32) -> D { + return {.__base = {.i = init}, .increment_by = 1}; + } + + impl as ImplicitAs(B) { + fn Convert[self: Self]() -> B { return self.__base; } + } + impl D* as ImplicitAs(B*) { + fn Convert[self: D*]() -> B* { return &self->__base; } + } + + fn Increment[addr self: Self*]() { + // Unchanged + self->Set(self->i + self->increment_by); + + // This finds the names `Set` and `i` from `B` using + // `extend api`. Those names are usable due to the + // implicit conversions defined above. + } +} +``` -#### Modeling virtual dispatch + + +**Future work:** Currently the Carbon design doesn't include base-to-derived +conversions. When they are added, it would be good to also have a way to model +them for classes that use `extend api` instead of `extend base`. + +##### Modeling virtual dispatch + +To support virtual dispatch, virtual functions need to be collected into their +own class and a vtable pointer stored. For example, + +```carbon +base class VB { + var i: i32; + virtual fn F[self: Self]() -> i32 { return 0; } + fn Make(init: i32) -> partial VB { + return {.i = init}; + } +} + +class VD { + extend base VB; + impl fn F[self: Self]() -> i32 { return 1; } + virtual fn G[self: Self]() -> i32 { return 2; } + fn Make() -> VD { + return {.base = VB.Make()}; + } +} +``` + +would be modelled as: + +```carbon +class VB; +base class VBVTable { + var F: fnty [self: VB]() -> i32; +} + +class __partial_VB { + var __vtable: VBVTable*; + var i: i32; + extend api VBVTable; + fn Make(init: i32) -> partial VB { + return {__vtable = 0 as VBVTable*, .i = init}; + } +} + +class VB { + var __vtable: VBVTable*; + var i: i32; + extend api __partial_VB; + impl __partial_VB as ImplicitAs(VB); +} + +// Members of VBVTable may be bound to VB +// FIXME: Somehow use function pointers here. +impl forall [U:! ValueBind(VBVTable)] + U as ValueBind(VB) where .Result = U.Result { + fn Op[self: Self](x: VB) -> Result { + return self.Op(x); + } +} + +impl forall [U:! RefBind(VBVTable)] + U as RefBind(VB) where .Result = U.Result { + fn Op[self: Self](p: VB*) -> Result* { + return self.Op(p); + } +} +``` FIXME From 4f03a62bd681c1e43e1d7885111d3e940cffb94c Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 2 May 2024 21:48:52 +0000 Subject: [PATCH 10/24] Checkpoint progress. --- proposals/p3802.md | 80 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 64b2c8fcb01e1..9d7405befacf4 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -607,48 +607,92 @@ class VD { } ``` -would be modelled as: +Could be modeled without virtual functions: ```carbon class VB; -base class VBVTable { + +base class __VBVTableType { + // Placeholder syntax for a method pointer var F: fnty [self: VB]() -> i32; } class __partial_VB { - var __vtable: VBVTable*; + var __vtable: __VBVTableType*; var i: i32; - extend api VBVTable; - fn Make(init: i32) -> partial VB { - return {__vtable = 0 as VBVTable*, .i = init}; + extend api __VBVTableType; + fn Make(init: i32) -> __partial_VB { + return {__vtable = 0 as __VBVTableType*, .i = init}; } } class VB { - var __vtable: VBVTable*; - var i: i32; - extend api __partial_VB; - impl __partial_VB as ImplicitAs(VB); + extend base __partial_VB; +} + +var __VBVTable: __VBVTableType = + {.F = fn[self: VB]() -> i32 { return 0; }}; + +impl __partial_VB as ImplicitAs(VB) { + fn Convert[self: __partial_VB]() -> VB { + return {.__vtable = &__VBVTable, .i = self.i} + } } -// Members of VBVTable may be bound to VB -// FIXME: Somehow use function pointers here. -impl forall [U:! ValueBind(VBVTable)] - U as ValueBind(VB) where .Result = U.Result { +// Members of __VBVTableType may be bound to VB +impl forall [T:! ValueBind(__VBVTableType)] + T as ValueBind(VB) where .Result = T.Result { fn Op[self: Self](x: VB) -> Result { + // Question: Does anything more have to happen + // here to dereference the method pointer? return self.Op(x); } } -impl forall [U:! RefBind(VBVTable)] - U as RefBind(VB) where .Result = U.Result { +impl forall [T:! RefBind(__VBVTableType)] + T as RefBind(VB) where .Result = T.Result { fn Op[self: Self](p: VB*) -> Result* { - return self.Op(p); + return self->Op(p); } } + +class VD; + +// This can be final since `VD` is final. +class __VDTableType { + extend base __VBVTableType; + var G: fnty [self: VD]() -> i32; +} + +// No need to support `partial VD` since `VD` is final. +class VD { + // NOTE: This isn't exactly right since `VD.F` takes + // `self` with type `VD` not `VB`. + extend base VB; + fn Make() -> VD; +} + +var __VBVTable: __VBVTableType = + {.F = fn[self: VB]() -> i32 { return 1; }, + .G = fn[self: VD]() -> i32 { return 2; }}; + +fn VD.Make() -> VD { + var __base: __partial_VB = VB.Make(); + __base.vtable = &__VDVTable; + return {.base = __base}; +} + + +// Members of __VDVTableType may be bound to VD. +// (Definition very similar to the above for VB.) +impl forall [T:! ValueBind(__VDVTableType)] + T as ValueBind(VD) where .Result = T.Result; +impl forall [T:! RefBind(__VDVTableType)] + T as RefBind(VD) where .Result = T.Result; ``` -FIXME +And then separately, the `extend base` could be removed, +[as before](#modeling-extend-base). #### Modeling `extend adapt` From 7822cb92be76d209d6b565c0fc2d1a318a955c51 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 2 May 2024 22:01:06 +0000 Subject: [PATCH 11/24] Checkpoint progress. --- proposals/p3802.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 9d7405befacf4..31ec624aba832 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -617,7 +617,7 @@ base class __VBVTableType { var F: fnty [self: VB]() -> i32; } -class __partial_VB { +base class __partial_VB { var __vtable: __VBVTableType*; var i: i32; extend api __VBVTableType; @@ -626,7 +626,7 @@ class __partial_VB { } } -class VB { +base class VB { extend base __partial_VB; } @@ -659,21 +659,22 @@ impl forall [T:! RefBind(__VBVTableType)] class VD; // This can be final since `VD` is final. -class __VDTableType { +class __VDVTableType { extend base __VBVTableType; var G: fnty [self: VD]() -> i32; } // No need to support `partial VD` since `VD` is final. class VD { - // NOTE: This isn't exactly right since `VD.F` takes - // `self` with type `VD` not `VB`. extend base VB; + extend api __VDVTableType; fn Make() -> VD; } var __VBVTable: __VBVTableType = - {.F = fn[self: VB]() -> i32 { return 1; }, + {.F = fn[self: VB]() -> i32 { + // Can perform normally unsafe cast `self` to `VD` here. + return 1; }, .G = fn[self: VD]() -> i32 { return 2; }}; fn VD.Make() -> VD { @@ -682,7 +683,6 @@ fn VD.Make() -> VD { return {.base = __base}; } - // Members of __VDVTableType may be bound to VD. // (Definition very similar to the above for VB.) impl forall [T:! ValueBind(__VDVTableType)] From 9fcab2a9067c03115da098faf2d567e9fd49c87d Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 2 May 2024 22:10:59 +0000 Subject: [PATCH 12/24] Checkpoint progress. --- proposals/p3802.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 31ec624aba832..b9b16fbaedb6c 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -586,7 +586,8 @@ them for classes that use `extend api` instead of `extend base`. ##### Modeling virtual dispatch To support virtual dispatch, virtual functions need to be collected into their -own class and a vtable pointer stored. For example, +own [vtable](https://en.wikipedia.org/wiki/Virtual_method_table) class and a +vtable pointer stored. For example, ```carbon base class VB { @@ -684,7 +685,9 @@ fn VD.Make() -> VD { } // Members of __VDVTableType may be bound to VD. -// (Definition very similar to the above for VB.) +// (Definition very similar to the above for VB, +// except we need to cast the `__vtable` member +// from `__VBVTableType` to `__VDVTableType`.) impl forall [T:! ValueBind(__VDVTableType)] T as ValueBind(VD) where .Result = T.Result; impl forall [T:! RefBind(__VDVTableType)] @@ -694,6 +697,11 @@ impl forall [T:! RefBind(__VDVTableType)] And then separately, the `extend base` could be removed, [as before](#modeling-extend-base). +> **Note:** This ignores the specifics of the ABI. In particular, Carbon may +> match C++'s ABI to ease interop. For this reason, the specifics of how +> `extend base` is desugared is not specified, and is not something users can +> rely on. + #### Modeling `extend adapt` First, let us consider `adapt` without `extend`. For example, From 6c0e0e21c4856882b4120d9e0fb53b88d9a28105 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 3 May 2024 00:31:05 +0000 Subject: [PATCH 13/24] Checkpoint progress. --- proposals/p3802.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index b9b16fbaedb6c..335e4f5c392df 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -698,9 +698,18 @@ And then separately, the `extend base` could be removed, [as before](#modeling-extend-base). > **Note:** This ignores the specifics of the ABI. In particular, Carbon may -> match C++'s ABI to ease interop. For this reason, the specifics of how -> `extend base` is desugared is not specified, and is not something users can -> rely on. +> match C++'s ABI, which includes dynamic `std::type_info` and additional data +> to support multiple inheritance, to ease interop. For this reason, the +> specifics of how `extend base` is desugared is not specified, and is not +> something users can rely on. + +The above is quite complex, but shows that in principle, once various features +of Carbon are filled in, that inheritance and dynamic dispatch could be +implemented from scratch to allow the implementation to be customized. Even +without that, this proposal does make the name lookup consistent across all uses +of `extend`. That is, `extend base` works like `extend api` plus a hypothetical +`base` feature without `extend` that worked like `extend base` but didn't +introduce any names into the class' scope. #### Modeling `extend adapt` @@ -906,5 +915,5 @@ and ### Make some name resolutions cases unambiguous with `extend api` -TODO: See -[comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513671030) +FIXME: See +[comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/97004ac6748a4f39e664d7409401df1811adc25c#r1513671030) From 4b2880a06e50315a0c5d81abbd2b531a36325909 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 3 May 2024 04:23:47 +0000 Subject: [PATCH 14/24] Checkpoint progress. --- proposals/p3802.md | 138 ++++++++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 335e4f5c392df..bd1796537a1e6 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -600,30 +600,37 @@ base class VB { class VD { extend base VB; - impl fn F[self: Self]() -> i32 { return 1; } - virtual fn G[self: Self]() -> i32 { return 2; } + impl fn F[self: Self]() -> i32 { return self.G(); } + virtual fn G[self: Self]() -> i32 { return 1; } fn Make() -> VD { return {.base = VB.Make()}; } } ``` -Could be modeled without virtual functions: +Could be modeled without virtual functions, assuming we have a couple of other +features: + +- A way to represent a function pointer (written `fnty` below) +- A way to perform an unsafe cast (written `UnsafeCast` below) ```carbon class VB; +// Type of `VB`'s vtable base class __VBVTableType { - // Placeholder syntax for a method pointer - var F: fnty [self: VB]() -> i32; + // Placeholder syntax for a function pointer + var F: fnty (__self: VB) -> i32; } +// Represents `partial VB` base class __partial_VB { - var __vtable: __VBVTableType*; + var __vptr: __VBVTableType*; var i: i32; extend api __VBVTableType; fn Make(init: i32) -> __partial_VB { - return {__vtable = 0 as __VBVTableType*, .i = init}; + return {.__vptr = UnsafeCast(0, __VBVTableType*), + .i = init}; } } @@ -631,67 +638,112 @@ base class VB { extend base __partial_VB; } +// Represents the specific vtable of `VB` var __VBVTable: __VBVTableType = - {.F = fn[self: VB]() -> i32 { return 0; }}; + {.F = fn(__self: VB) -> i32 { return 0; }}; +// Converting a `partial VB` value to `VB` sets +// the vptr to the specific `VB` vtable impl __partial_VB as ImplicitAs(VB) { fn Convert[self: __partial_VB]() -> VB { - return {.__vtable = &__VBVTable, .i = self.i} + return {.__vptr = &__VBVTable, .i = self.i}; } } -// Members of __VBVTableType may be bound to VB -impl forall [T:! ValueBind(__VBVTableType)] - T as ValueBind(VB) where .Result = T.Result { - fn Op[self: Self](x: VB) -> Result { - // Question: Does anything more have to happen - // here to dereference the method pointer? - return self.Op(x); +// Represents the binding of a `VB` value or object +// with `__VBVTableType.F`. +class __VB_F { + adapt VB; + impl as Call(()) where .Result = i32 { + fn Op[self: Self](_: ()) -> i32 { + return (self as VB).__vptr->F(self as VB); + } } } -impl forall [T:! RefBind(__VBVTableType)] - T as RefBind(VB) where .Result = T.Result { - fn Op[self: Self](p: VB*) -> Result* { - return self->Op(p); +// `__VBVTableType.F` may be bound to any type that +// can implicitly convert to VB. +impl forall [T:! ImplicitAs(VB)] + __VBVTableType.F as ValueBind(T) + where .Result = __VB_F; + fn Op[self: Self](x: T) -> Result { + return (x as VB) as __VB_F; + } +} +impl forall [T:! type where T* impls ImplicitAs(VB*)] + __VBVTableType.F as RefBind(T) + where .Result = __VB_F; + fn Op[self: Self](p: T*) -> Result* { + return (p as VB*) as __VB_F*; } } class VD; +// Represents the type of `VD`'s vtable. // This can be final since `VD` is final. class __VDVTableType { extend base __VBVTableType; - var G: fnty [self: VD]() -> i32; + var G: fnty (__self: VD) -> i32; } -// No need to support `partial VD` since `VD` is final. +// No need to support a separate `partial VD` +// since `VD` is final. class VD { extend base VB; extend api __VDVTableType; fn Make() -> VD; } -var __VBVTable: __VBVTableType = - {.F = fn[self: VB]() -> i32 { - // Can perform normally unsafe cast `self` to `VD` here. - return 1; }, - .G = fn[self: VD]() -> i32 { return 2; }}; +// Represents the specific vtable of `VD`. +var __VDVTable: __VDVTableType = + {.F = fn(__self: VB) -> i32 { + // Cast `__self` to `VD` is safe due to + // this implementation only used on `VD` + // instances due to virtual dispatch. + return UnsafeCast(__self, VD).G(); + }, + .G = fn(__self: VD) -> i32 { return 1; } + }; +// Constructing a `VD` value or object sets +// the vptr to point at `VD`'s vtable. fn VD.Make() -> VD { var __base: __partial_VB = VB.Make(); - __base.vtable = &__VDVTable; + __base.__vptr = &__VDVTable; return {.base = __base}; } -// Members of __VDVTableType may be bound to VD. -// (Definition very similar to the above for VB, -// except we need to cast the `__vtable` member -// from `__VBVTableType` to `__VDVTableType`.) -impl forall [T:! ValueBind(__VDVTableType)] - T as ValueBind(VD) where .Result = T.Result; -impl forall [T:! RefBind(__VDVTableType)] - T as RefBind(VD) where .Result = T.Result; +// Represents the binding of a `VD` value or object +// with `__VDVTableType.G`. Like `__VB_F` above, +// except we need to cast the `__vptr` member. +class __VD_G { + adapt VD; + impl as Call(()) where .Result = i32 { + fn Op[self: Self](_: ()) -> i32 { + let vptr: auto = + UnsafeCast((self as VD).__vptr, __VDVTableType*); + return vptr->G(self as VD); + } + } +} + +// `__VDVTableType.G` may be bound to any type that can +// implicitly convert to `VD`, just like `F` above. +impl forall [T:! ImplicitAs(VD)] + __VDVTableType.G as ValueBind(T) + where .Result = __VD_G; + fn Op[self: Self](x: T) -> Result { + return (x as VD) as __VD_G; + } +} +impl forall [T:! type where T* impls ImplicitAs(VD*)] + __VDVTableType.G as RefBind(T) + where .Result = __VD_G; + fn Op[self: Self](p: T*) -> Result* { + return (p as VD*) as __VD_G*; + } +} ``` And then separately, the `extend base` could be removed, @@ -703,14 +755,20 @@ And then separately, the `extend base` could be removed, > specifics of how `extend base` is desugared is not specified, and is not > something users can rely on. -The above is quite complex, but shows that in principle, once various features +The above is quite involved, but shows that in principle, once various features of Carbon are filled in, that inheritance and dynamic dispatch could be -implemented from scratch to allow the implementation to be customized. Even -without that, this proposal does make the name lookup consistent across all uses -of `extend`. That is, `extend base` works like `extend api` plus a hypothetical +implemented from scratch to allow the details to be customized. Even without +that, this proposal does make the name lookup consistent across all uses of +`extend`. That is, `extend base` works like `extend api` plus a hypothetical `base` feature without `extend` that worked like `extend base` but didn't introduce any names into the class' scope. +> **FIXME:** This desugaring suggests that name lookup finds virtual functions +> through an `extend`, which may not match our desired handling of name +> conflicts. This may not be a problem in practice though, since name conflicts +> with virtual methods are forbidden, see +> [leads issue #2355: Name conflicts between base and derived classes](https://github.com/carbon-language/carbon-lang/issues/2355). + #### Modeling `extend adapt` First, let us consider `adapt` without `extend`. For example, From 11ad1c0910a817313fcef916a581e29b7718878e Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 3 May 2024 18:26:20 +0000 Subject: [PATCH 15/24] Checkpoint progress. --- proposals/p3802.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index bd1796537a1e6..aa891c49adb96 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -670,7 +670,7 @@ impl forall [T:! ImplicitAs(VB)] return (x as VB) as __VB_F; } } -impl forall [T:! type where T* impls ImplicitAs(VB*)] +impl forall [T:! type where .Self* impls ImplicitAs(VB*)] __VBVTableType.F as RefBind(T) where .Result = __VB_F; fn Op[self: Self](p: T*) -> Result* { @@ -737,7 +737,7 @@ impl forall [T:! ImplicitAs(VD)] return (x as VD) as __VD_G; } } -impl forall [T:! type where T* impls ImplicitAs(VD*)] +impl forall [T:! type where .Self* impls ImplicitAs(VD*)] __VDVTableType.G as RefBind(T) where .Result = __VD_G; fn Op[self: Self](p: T*) -> Result* { @@ -763,11 +763,15 @@ that, this proposal does make the name lookup consistent across all uses of `base` feature without `extend` that worked like `extend base` but didn't introduce any names into the class' scope. -> **FIXME:** This desugaring suggests that name lookup finds virtual functions -> through an `extend`, which may not match our desired handling of name -> conflicts. This may not be a problem in practice though, since name conflicts -> with virtual methods are forbidden, see -> [leads issue #2355: Name conflicts between base and derived classes](https://github.com/carbon-language/carbon-lang/issues/2355). +> **Note:** This desugaring suggests that name lookup finds virtual functions +> through an `extend`. +> [Leads issue #2355: Name conflicts between base and derived classes](https://github.com/carbon-language/carbon-lang/issues/2355) +> specifies that name conflicts with virtual methods are forbidden, rather than +> the `extend` behavior being discussed in +> [leads issue #2745: Name conflicts beyond inheritance](https://github.com/carbon-language/carbon-lang/issues/2745). +> We include this additional rule about name conflicts from #2355, observing +> that it is stricter than the `extend` rules being considered, but otherwise +> compatible. #### Modeling `extend adapt` From bc1432dfc5bdea8ea4c31c6b25bb2f715fb567e6 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 3 May 2024 20:58:07 +0000 Subject: [PATCH 16/24] Mixins --- proposals/p3802.md | 115 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index aa891c49adb96..93325f6d70035 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -911,7 +911,120 @@ interface B { #### Future work: modeling mixins -FIXME +Mixins are planned feature of Carbon. +[Leads issue #995: "Generics external impl versus extends"](https://github.com/carbon-language/carbon-lang/issues/995) +and +[proposal #2760: "Consistent `class` and `interface` syntax](p2760.md#future-work-mixins) +give a syntax for including a mixin in a class using the `extend` keyword. The +declaration `extend m: M;` includes a mixin of type `M` in the class, using the +name `m` to reference it. The +["Mixin vision" document](https://docs.google.com/document/d/1ewVySLnOniph4UJR30TrCsrPhNkWOCR8kgj_F_KJiA8/edit?tab=t.0) +describes the thinking about mixins more comprehensively as of Q1 2023, and has +more background material. + +Example: + +```carbon +interface I { + fn F[self: Self]() -> i32; +} + +mixin Mix for I { + inject fn G[self: Self]() -> i32{ + return self.F() + self.plus - self.minus; + } + + fn Make(p: i32, m: i32) -> Mix { + return {.plus = p, .minus = m}; + } + + inject var plus: i32; + private var minus: i32; +} + +class C { + impl as I { + fn F[self: Self]() -> i32 { return 1; } + } + extend m: Mix = Mix.Make(); +} +``` + +could be roughly represented by: + +```carbon +// Unchanged +interface I { + fn F[self: Self]() -> i32; +} + +// Represents a type that has a mixin of type `Mix`. +interface HasMix; + +// Represents the mixin `Mix` when mixed into type `__Self`. +class Mix(__Self:! I & HasMix(.Self)); + +// A type that has a mixin of type `Mix` can turn a +// `Self` value/object into a `Mix(Self)` value/object. +interface HasMix { + require Self impls I; + fn GetMixinValue[self: Self]() -> Mix(Self); + fn GetMixinRef[addr self: Self*]() -> Mix(Self)*; +} + +class Mix(__Self:! I & HasMix) { + fn G[self: __Self]() -> i32 { + return self.F() + + self.GetMixinValue().plus + - self.GetMixinValue().minus; + } + + fn Make(p: i32, m: i32) -> Self { + return {.plus = p, .minus = m}; + } + + var plus: i32; + private var minus: i32; +} + +class __Mix_Injected(__Self:! I & HasMix) { + alias G = Mix(__Self).G; + alias plus = Mix(__Self).plus; +} + +impl forall [__Self:! I & HasMix] + Mix(__Self).plus as ValueBind(__Self) where .Result = i32 { + fn Op[self: Self](x: __Self) -> Result { + return x.GetMixinValue().plus; + } +} + +impl forall [__Self:! I & HasMix] + Mix(__Self).plus as RefBind(__Self) where .Result = i32 { + fn Op[self: Self](p: __Self*) -> Result* { + return &p->GetMixinRef()->plus; + } +} + +class C { + impl as I { + fn F[self: Self]() -> i32 { return 1; } + } + var m: Mix(Self) = Mix(Self).Make(); + impl as HasMix { + fn GetMixinValue[self: Self]() -> Mix(Self) { + return self.m; + } + fn GetMixinRef[addr self: Self*]() -> Mix(Self)* { + return &self->m; + } + } + extend api __Mix_Injected(Self); +} +``` + +Of course this is just speculation until such time as we have an accepted mixins +proposal. ### Future work: customizing impl binding From 12971d628d46189c03085a62a5f628b358334a49 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 4 May 2024 01:29:31 +0000 Subject: [PATCH 17/24] Alternative --- proposals/p3802.md | 68 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 93325f6d70035..3d21d7af4a9c2 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -1049,22 +1049,18 @@ implemented by `extend base` and `extend adapt` are different, and ## Rationale -FIXME: 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) +This proposal advances these [Carbon goals](/docs/project/goals.md): + +- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem), + by making the language more orthogonal and providing a path to rewriting + constructs into more primitive features, as described in the + ["Problem" section](#problem). +- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution), + by giving additional ways to express a single API, allowing some additional + freedom to evolve an implementation without changing clients. +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write), + due to the consistency across different uses of `extend`, as described in + the ["Problem" section](#problem). ## Alternatives considered @@ -1090,5 +1086,41 @@ and ### Make some name resolutions cases unambiguous with `extend api` -FIXME: See -[comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/97004ac6748a4f39e664d7409401df1811adc25c#r1513671030) +In +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/97004ac6748a4f39e664d7409401df1811adc25c#r1513671030), +regarding this code + +```carbon +class A { + fn F(); +} +class B { + extend api A; + // Hides A.F + fn F(); +} +class C { + extend api A; + extend api B; +} +``` + +There was a question about whether we want `C.F` to be ambiguous, or to +unambiguously name `B.F`. The idea was that class `B` intentionally hid `A.F`, +and in C++, the corresponding rule for member name lookup in an inheritance +hierarchy makes this ambiguous if `A` is a non-virtual base, but unambiguously +picks `C.F` if `A` is a virtual base. If this use of `extend api` was more like +a virtual base situation, it could motivate making `C.F` refer unambiguously to +`B.F`. + +For now, we instead decided to go with the simpler rule: + +> We look in each `extend`, and if a name is found multiple times it is an +> ambiguity error that needs qualification to disambiguate. + +In addition to being simpler, there was also the fact we can always add a more +complicated rule in the future, based on need. For now the extra complexity +doesn't seem justified given how rare conflicting multiple `extend`s is expected +to be. This specific example was not very motivating since the ambiguity is +fixed by dropping `extend api A;` from `C`, though that may not apply in other +examples. From f9b63b9bbf45a3ab1000d9be8c98fb93711e12c8 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 4 May 2024 18:10:06 +0000 Subject: [PATCH 18/24] Refine mixins --- proposals/p3802.md | 59 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 3d21d7af4a9c2..b32fc520d4991 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -990,15 +990,18 @@ class Mix(__Self:! I & HasMix) { class __Mix_Injected(__Self:! I & HasMix) { alias G = Mix(__Self).G; alias plus = Mix(__Self).plus; + // These might not be able to be aliases, instead: + // let template G:! auto = Mix(__Self).G; + // let template plus:! auto = Mix(__Self).plus; } +// Allows classes that mixin `Mix` to bind to `plus`. impl forall [__Self:! I & HasMix] Mix(__Self).plus as ValueBind(__Self) where .Result = i32 { fn Op[self: Self](x: __Self) -> Result { return x.GetMixinValue().plus; } } - impl forall [__Self:! I & HasMix] Mix(__Self).plus as RefBind(__Self) where .Result = i32 { fn Op[self: Self](p: __Self*) -> Result* { @@ -1006,6 +1009,10 @@ impl forall [__Self:! I & HasMix] } } +// Classes that mixin `Mix` are translated into classes with a +// `Mix(Self)` field, implement the `HasMix` interface, and +// include the names from `__Mix_Injected(Self)` using +// `extend api`. class C { impl as I { fn F[self: Self]() -> i32 { return 1; } @@ -1023,6 +1030,56 @@ class C { } ``` +> **Note:** If a class uses the same mixin twice, it will need to use distinct +> adapters for each. The adapters will have implementations of `HasMix` that +> return the corresponding mixin member. For example: +> +> ```carbon +> class C { +> impl as I; +> extend m1: Mix; +> extend m2: Mix; +> } +> ``` +> +> would be translated to: +> +> ```carbon +> class __C_m1; +> class __C_m2; +> class C { +> impl as I; +> var m1: Mix(__C_m1); +> var m2: Mix(__C_m2); +> } +> class __C_m1 { +> adapt C; +> impl as HasMix { +> fn GetMixinValue[self: Self]() -> Mix(Self) { +> return (self as C).m1; +> } +> fn GetMixinRef[addr self: Self*]() -> Mix(Self)* { +> return &(self as C*)->m1; +> } +> } +> } +> class __C_m2 { +> adapt C; +> impl as HasMix { +> fn GetMixinValue[self: Self]() -> Mix(Self) { +> return (self as C).m2; +> } +> fn GetMixinRef[addr self: Self*]() -> Mix(Self)* { +> return &(self as C*)->m2; +> } +> } +> } +> ``` +> +> Though observe we can't inject the same names more than once. This is +> reflected in the fact that the adapter classes `__C_m1` and `__C_m2` are what +> are implementing `HasMix`, not `C`. + Of course this is just speculation until such time as we have an accepted mixins proposal. From 7402c791921332ea4d2b652e4412c5b561f5f97d Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Wed, 15 May 2024 16:08:31 -0700 Subject: [PATCH 19/24] Apply suggestions from code review Co-authored-by: Richard Smith --- proposals/p3802.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index b32fc520d4991..924584d8f32f4 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -152,9 +152,10 @@ However, classes can designate other entities to also look up in using the Interfaces and named constraints also [use the `extend` keyword](/docs/design/generics/details.md#interface-extension) -to lookup into another, which is also required. +to indicate that lookups should be performed in another constraint, +and to add a requirement that `Self` implements the other constraint. -For some use cases, we want to be able to add name to lookup into that type +For some use cases, we want to be able to add to name lookup into a type without making other changes to the type, for which we introduce `extend api`, with consistent lookup rules as the other uses of `extend`, with conflicts handled as determined by leads issues @@ -180,8 +181,8 @@ resolved by qualifying the name on use. > even if, as in this example, we don't know the names of the members of the > type being extended. -Like other uses of `extend`, an `extend api` declaration do not have access -control modifiers and only operate on public names. +Like other uses of `extend`, an `extend api` declaration does not have access +control modifiers and only operates on public names. ### Use case: Member forwarding @@ -210,7 +211,7 @@ There are two ingredients to make this work: - We need some way to make `b.Search` be equivalent to `b.(String.Search)` not `b.(Box(String).Search)`. -- We need the act of binding a method of `String` to a `Box(String)` value +- We need the act of binding a method of `String` to a `Box(String)` value to work by dereferencing the pointer `b.ptr`. For the first ingredient, we use `extend api` to customize @@ -289,8 +290,8 @@ class Ref(T:! type) { } impl forall [T:! type, U:! RefBind(T)] U as ValueBind(Ref(T)) where .Result = Ref(U.Result) { - fn Op[self: Self](p: Ref(T)) -> Ref(Result) { - return {.ptr = self.Op(p->ptr)}; + fn Op[self: Self](p: Ref(T)) -> Result { + return {.ptr = self.Op(p.ptr)}; } } // (Plus impls for the other three combinations of ValueBind and RefBind.) From 668697a137f0622652ccb343529048e012360862 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 15 May 2024 23:09:28 +0000 Subject: [PATCH 20/24] Fix formatting --- proposals/p3802.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 924584d8f32f4..b24d9ec180f2c 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -152,13 +152,13 @@ However, classes can designate other entities to also look up in using the Interfaces and named constraints also [use the `extend` keyword](/docs/design/generics/details.md#interface-extension) -to indicate that lookups should be performed in another constraint, -and to add a requirement that `Self` implements the other constraint. +to indicate that lookups should be performed in another constraint, and to add a +requirement that `Self` implements the other constraint. -For some use cases, we want to be able to add to name lookup into a type -without making other changes to the type, for which we introduce `extend api`, -with consistent lookup rules as the other uses of `extend`, with conflicts -handled as determined by leads issues +For some use cases, we want to be able to add to name lookup into a type without +making other changes to the type, for which we introduce `extend api`, with +consistent lookup rules as the other uses of `extend`, with conflicts handled as +determined by leads issues [#2355](https://github.com/carbon-language/carbon-lang/issues/2355) and [#2745](https://github.com/carbon-language/carbon-lang/issues/2745). From b67b440fc9c5855d469ebcbc8bd574ae858b7406 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 17 May 2024 20:40:25 +0000 Subject: [PATCH 21/24] ValueBind -> BindToValue --- proposals/p3802.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index b24d9ec180f2c..4951c69f5fa6e 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -234,10 +234,10 @@ already provide everything needed, we just need to make a parameterized implementation of them: ```carbon -impl forall [T:! type, U:! ValueBind(T)] - U as ValueBind(Box(T)) where .Result = U.Result { +impl forall [T:! type, U:! BindToValue(T)] + U as BindToValue(Box(T)) where .Result = U.Result { fn Op[self: Self](x: Box(T)) -> Result { - // Uses `ValueBind(T).Op` based on type of `U`. + // Uses `BindToValue(T).Op` based on type of `U`. return self.Op(*x.ptr); // NOT `return x.ptr->(self)` since that attempts @@ -245,8 +245,8 @@ impl forall [T:! type, U:! ValueBind(T)] } } -impl forall [T:! type, U:! RefBind(T)] - U as RefBind(Box(T)) where .Result = U.Result { +impl forall [T:! type, U:! BindToRef(T)] + U as BindToRef(Box(T)) where .Result = U.Result { fn Op[self: Self](p: Box(T)*) -> Result* { return self.Op(p->ptr); // Or equivalently: @@ -264,9 +264,9 @@ A few observations: `ptr` of `Box(T)` to get a `T` value that is compatible with the member being bound. This resolves the type mismatch that is introduced by allowing name resolution to find another type's members. -- We have to be a little careful in the implementation of `ValueBind(Box(T))` - to still use value binding even when we get a reference expression from - dereferencing the pointer `ptr`. +- We have to be a little careful in the implementation of + `BindToValue(Box(T))` to still use value binding even when we get a + reference expression from dereferencing the pointer `ptr`. With these two ingredients, `b.Search(32)` is equivalent to `b.(String.Search)(32)` due to `extend api`, which then uses the custom binding @@ -288,13 +288,13 @@ class Ref(T:! type) { var ptr: T*; extend api T; } -impl forall [T:! type, U:! RefBind(T)] - U as ValueBind(Ref(T)) where .Result = Ref(U.Result) { +impl forall [T:! type, U:! BindToRef(T)] + U as BindToValue(Ref(T)) where .Result = Ref(U.Result) { fn Op[self: Self](p: Ref(T)) -> Result { return {.ptr = self.Op(p.ptr)}; } } -// (Plus impls for the other three combinations of ValueBind and RefBind.) +// (Plus impls for the other three combinations of BindToValue and BindToRef.) impl forall [S:! type, T:! Call(S)] Ref(T) as Call(S) where .Result = T.Result { // ... } @@ -665,14 +665,14 @@ class __VB_F { // `__VBVTableType.F` may be bound to any type that // can implicitly convert to VB. impl forall [T:! ImplicitAs(VB)] - __VBVTableType.F as ValueBind(T) + __VBVTableType.F as BindToValue(T) where .Result = __VB_F; fn Op[self: Self](x: T) -> Result { return (x as VB) as __VB_F; } } impl forall [T:! type where .Self* impls ImplicitAs(VB*)] - __VBVTableType.F as RefBind(T) + __VBVTableType.F as BindToRef(T) where .Result = __VB_F; fn Op[self: Self](p: T*) -> Result* { return (p as VB*) as __VB_F*; @@ -732,14 +732,14 @@ class __VD_G { // `__VDVTableType.G` may be bound to any type that can // implicitly convert to `VD`, just like `F` above. impl forall [T:! ImplicitAs(VD)] - __VDVTableType.G as ValueBind(T) + __VDVTableType.G as BindToValue(T) where .Result = __VD_G; fn Op[self: Self](x: T) -> Result { return (x as VD) as __VD_G; } } impl forall [T:! type where .Self* impls ImplicitAs(VD*)] - __VDVTableType.G as RefBind(T) + __VDVTableType.G as BindToRef(T) where .Result = __VD_G; fn Op[self: Self](p: T*) -> Result* { return (p as VD*) as __VD_G*; @@ -802,14 +802,14 @@ impl A as As(B) { return {.__a = self} as B; } } -impl forall [T:! ValueBind(A)] - T as ValueBind(B) where .Result = T.Result { +impl forall [T:! BindToValue(A)] + T as BindToValue(B) where .Result = T.Result { fn Op[self: Self](x: B) -> Result { return self.Op(x as A); } } -impl forall [T:! RefBind(A)] - T as RefBind(B) where .Result = T.Result { +impl forall [T:! BindToRef(A)] + T as BindToRef(B) where .Result = T.Result { fn Op[self: Self](p: B*) -> Result* { return self.Op(&p->__a); } @@ -839,12 +839,12 @@ var b_ref: B = {}; let b_val: B = {}; // Allowed: binds `b_val` to `A.F`. Since `b_val` is a value expression, -// it uses `A.F as ValueBind(B)`, so this is equivalent to +// it uses `A.F as BindToValue(B)`, so this is equivalent to // `(b_val as A).(A.F)(a)`. b_val.(A.F)(a); // Allowed: binds `b_ref` to `A.F`. Since `b_ref` is a reference -// expression, it uses `A.F as RefBind(B)`, so this is equivalent to: +// expression, it uses `A.F as BindToRef(B)`, so this is equivalent to: // `(&((&b_ref)->__a))->(A.F)(a)`. b_ref.(A.F)(a); @@ -998,13 +998,13 @@ class __Mix_Injected(__Self:! I & HasMix) { // Allows classes that mixin `Mix` to bind to `plus`. impl forall [__Self:! I & HasMix] - Mix(__Self).plus as ValueBind(__Self) where .Result = i32 { + Mix(__Self).plus as BindToValue(__Self) where .Result = i32 { fn Op[self: Self](x: __Self) -> Result { return x.GetMixinValue().plus; } } impl forall [__Self:! I & HasMix] - Mix(__Self).plus as RefBind(__Self) where .Result = i32 { + Mix(__Self).plus as BindToRef(__Self) where .Result = i32 { fn Op[self: Self](p: __Self*) -> Result* { return &p->GetMixinRef()->plus; } From 9ae1a87dd410906f5dbb180f124b3251a392b9e3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 27 May 2024 16:13:02 +0000 Subject: [PATCH 22/24] Clean up references to #3720 now that it is merged --- proposals/p3802.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index 4951c69f5fa6e..cc99193d9faf4 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -86,7 +86,7 @@ This proposal builds upon a number of previous proposals: - [Proposal 2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) changed the syntax to consistently use the `extend` keyword for any language construct that involves delegating name lookup to another type. -- [Proposal #3720: Binding operators](https://github.com/carbon-language/carbon-lang/pull/3720) +- [Proposal #3720: Member binding operators](https://github.com/carbon-language/carbon-lang/pull/3720) introduced customization of how member binding works, as in `x.y` or `x.(y)`. This proposal was originally part of #3720, but was split out. @@ -229,7 +229,7 @@ This means that lookup into `Box(T)` also looks in the type `T` for members. In this way, `b.Search` will find `b.(String.Search)`. For the second ingredient, the binding interfaces from -[proposal #3720: "Binding operators"](https://github.com/carbon-language/carbon-lang/pull/3720) +[proposal #3720: "Member binding operators"](https://github.com/carbon-language/carbon-lang/pull/3720) already provide everything needed, we just need to make a parameterized implementation of them: @@ -328,12 +328,8 @@ This application of `extend api` and binding was first described in A class could extend the API of a class it implicitly converts to. For example, imagine we have a class representing an integer in a restricted range that can -implicitly convert to an integer value. - - +implicitly convert to an integer value, see +[inheritance and other implicit conversions from proposal #3720](p3720.md#inheritance-and-other-implicit-conversions). ```carbon class I32InRange(Low:! i32, High:! i32) { @@ -575,10 +571,8 @@ class D { } ``` - +[implicit conversions are sufficient for member binding to work, no additional member binding implementations are needed](p3720.md#inheritance-and-other-implicit-conversions). **Future work:** Currently the Carbon design doesn't include base-to-derived conversions. When they are added, it would be good to also have a way to model @@ -1138,7 +1132,7 @@ disadvantages: preventing cycles (`A` extends `B` extends `C` extends `A`). This was discussed in the -[proposal review](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1507988547) +[proposal review](https://github.com/carbon-language/carbon-lang/pull/3720/files/19774c5013706e5795a7952f0c6f08a73872a036#r1507988547) and [the #typesystem channel on Discord](https://discord.com/channels/655572317891461132/708431657849585705/1217499991329738852). From 7e068bce973e71c1d778ab45129bec0307d51e01 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 27 May 2024 19:45:28 +0000 Subject: [PATCH 23/24] Fix ref type & make future work --- proposals/p3802.md | 174 +++++++++++++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 52 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index cc99193d9faf4..f8c7810dbcd6f 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -18,7 +18,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Proposal](#proposal) - [Details](#details) - [Use case: Member forwarding](#use-case-member-forwarding) - - [Use case: Reference library type](#use-case-reference-library-type) - [Use case: with implicit conversion](#use-case-with-implicit-conversion) - [Use case: extension methods](#use-case-extension-methods) - [`extend api` with interfaces](#extend-api-with-interfaces) @@ -30,6 +29,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Modeling `extend` in interfaces and named constraints](#modeling-extend-in-interfaces-and-named-constraints) - [Future work: modeling mixins](#future-work-modeling-mixins) - [Future work: customizing impl binding](#future-work-customizing-impl-binding) + - [Future work: Reference library type](#future-work-reference-library-type) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [Allow `extend api` of an incomplete type](#allow-extend-api-of-an-incomplete-type) @@ -273,57 +273,6 @@ With these two ingredients, `b.Search(32)` is equivalent to implementation to get something equivalent to `b.ptr->(String.Search)(32)`, which is the same as `b.ptr->Search(32)` since `b.ptr` has type `String*`. -### Use case: Reference library type - -The `Box` type that was used as -[an example of member forwarding](#use-case-member-forwarding) preserves the -expression category -- you can only call mutating operations on a `Box` -reference expression, not on a `Box` value expression. For a library reference -type that would simulate the C++-built-in reference type, we instead want to -permit value binding for anything where `T` permits reference binding. This can -also be done using `extend api` and a different set of binding implementations: - -```carbon -class Ref(T:! type) { - var ptr: T*; - extend api T; -} -impl forall [T:! type, U:! BindToRef(T)] - U as BindToValue(Ref(T)) where .Result = Ref(U.Result) { - fn Op[self: Self](p: Ref(T)) -> Result { - return {.ptr = self.Op(p.ptr)}; - } -} -// (Plus impls for the other three combinations of BindToValue and BindToRef.) -impl forall [S:! type, T:! Call(S)] Ref(T) as Call(S) where .Result = T.Result { - // ... -} -``` - -For example, it could be used as: - -```carbon -class C { - fn F[addr self: Self*](); - var n: i32; -} -fn F(r: Ref(C)) { - // OK, even though `r` is a value expression. - // `r.F` is a `Ref()`, - // which is callable. - r.F(); - - // OK, forms a reference to the `n` member. - let n_ref: Ref(i32) = r.n; -} -``` - -Note, however, `Ref(T)` doesn't preserve the interface implementations of `T`, -see [future work](#future-work-customizing-impl-binding). - -This application of `extend api` and binding was first described in -[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/37e967ff53e89e345eecb49ec79c6dfbe18a3c54#r1513712572). - ### Use case: with implicit conversion A class could extend the API of a class it implicitly converts to. For example, @@ -1099,6 +1048,127 @@ It is unclear how that would work, since the interfaces that would be implemented by `extend base` and `extend adapt` are different, and [the rules for conversions allowed by adapters are complex](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#adapter-compatibility). +### Future work: Reference library type + +The `Box` type that was used as +[an example of member forwarding](#use-case-member-forwarding) preserves the +expression category -- you can only call mutating operations on a `Box` +reference expression, not on a `Box` value expression. For a library reference +type that would simulate the C++-built-in reference type, we instead want to +permit value binding for anything where `T` permits reference binding. This +could be done using `extend api` with some additional pieces: + +- We need `BindToRef` to fall back to `BindToValue`, as suggested in + [proposal #3720's future work](p3720.md#future-properties). This would allow + binding to a reference expression to produce a value, which is important in + this case since we don't have an object we can return the address of. +- We need support for a `RefCall` interface that is like `Call` but uses + `addr self`. This would handle mutation of the callable, and would be used + to implement `addr self` methods. There would be a blanket `final` + implementation of `RefCall` for any object implementing `Call`, so what + function you get would not change based on knowing more about what its type + implements (implying that there is no way to override a method solely on + category of the receiver). + +It would also use a different set of binding implementations: + +```carbon +class Ref(T:! type) { + var ptr: T*; + extend api T; +} + +match_first { + +// . produces a value expression of type `Ref(R)`. +impl forall [T:! type, U:! BindToRef(T)] + U as BindToValue(Ref(T)) where .Result = Ref(U.Result) { + fn Op[self: Self](p: Ref(T)) -> Result { + return {.ptr = self.Op(p->ptr)}; + } +} + +// . produces a value expression of type `R`. +// A value binding is performed to turn the Ref(T) into a T. +impl forall [T:! type, U:! BindToValue(T)] + U as BindToValue(Ref(T)) where .Result = U.Result { + fn Op[self: Self](p: Ref(T)) -> Result { + return self.Op(*p.ptr); + } +} + +} + +// Support `Call`s of a , if `T` supports `Call` or `RefCall`. +match_first { + +impl forall [...each U:! type, T:! RefCall(... each U)] + Ref(T) as Call(... each U) where .Result = T.Result { + fn Op[self: Self](... each args: ... each U) -> Result { + return (*self.ptr)(... each args); + } +} + +impl forall [...each U:! type, T:! Call(... each U)] + Ref(T) as Call(... each U) where .Result = T.Result { + fn Op[self: Self](... each args: ... each U) -> Result { + return (*self.ptr)(... each args); + } +} + +} +``` + +For example, it could be used as: + +```carbon +class Counter { + fn Increment[addr self: Self*](); + var count: i32 = 0; +} + +fn F(r: Ref(Counter)) { + // OK, even though `r` is a value expression. + r.Increment(); + // - `r.Increment` is `r.(Ref(Counter).Increment)`. + // - Due to the `extend api T` in `Ref(T)`, this finds + // `r.(Counter.Increment)` + // - `Counter.Increment` implements `BindToRef(Counter)` + // so it also implement `BindToValue(Ref(Counter))`. + // - The result of binding to the value `r` has type + // `Ref(Result)` where `Result` is the bound member + // function type + // `(Counter.Increment as BindToRef(Counter)).Result`. + // - `Result` implement `RefCall()`, because + // `Counter.Increment` has signature + // `[addr self: Self*]()`, so `Ref(Result)` implements + // `Call()`. + + // OK, forms a reference to the `count` member. + let count_ref: Ref(i32) = r.count; + // count_ref.ptr == &(r.ptr->count) + + count_ref += 3 as i32; + // - Rewritten to + // `count_ref.(AddAssignWith(i32).Op)(3 as i32)`. + // - `Ref(i32)` can be bound to `AddAssignWith(i32).Op` + // and the result is callable, just like with + // `Ref(Counter)` and `Counter.Increment` in the + // `r.Increment()` example previously. +} +``` + +Note, however, `Ref(T)` doesn't preserve the interface implementations of `T`, +so `Ref(i32)` doesn't implement `AddAssignWith(i32)` despite the call to its +method working. See [future work](#future-work-customizing-impl-binding). + +This application of `extend api` and binding was first described in +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/37e967ff53e89e345eecb49ec79c6dfbe18a3c54#r1513712572). +It had some problems, which were addressed in +[this comment on #3802](https://github.com/carbon-language/carbon-lang/pull/3802/files/f9b63b9bbf45a3ab1000d9be8c98fb93711e12c8#r1602305497) +and +[open discussion on 2024-05-16](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.3qlpp5e56u46). + ## Rationale This proposal advances these [Carbon goals](/docs/project/goals.md): From 082bb2414925874a1dd741c3bde91e06308764f8 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 27 May 2024 19:49:35 +0000 Subject: [PATCH 24/24] Fix forward reference to `Box(T)` --- proposals/p3802.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proposals/p3802.md b/proposals/p3802.md index f8c7810dbcd6f..0a3ecef450114 100644 --- a/proposals/p3802.md +++ b/proposals/p3802.md @@ -166,10 +166,13 @@ The extended type is required to be complete at the point of the `extend api` declaration, so these lookups into `T` can succeed. The general rule for resolving ambiguity for `extend`, which we apply here as -well, is that if lookup into `Box(T)` succeeds, then that result is used and no -lookup into `T` is performed. If a class uses `extend` more than once, and finds -the same name more than once, that is an ambiguity error that needs to be -resolved by qualifying the name on use. +well, is that if lookup into the extended type (`Box(T)` in +[the member forwarding example](#use-case-member-forwarding)) succeeds, then +that result is used and no lookup into the extending type (`T` in +[the member forwarding example](#use-case-member-forwarding)) is performed. If a +class uses `extend` more than once, and finds the same name more than once as a +result, that is an ambiguity error that needs to be resolved by qualifying the +name on use. > **Future work:** Are these the right rules in the context of API evolution? > See