Skip to content

Commit

Permalink
Delete content moved to carbon-language#3802
Browse files Browse the repository at this point in the history
  • Loading branch information
josh11b committed Mar 20, 2024
1 parent 9a922ad commit 8df9720
Showing 1 changed file with 6 additions and 315 deletions.
321 changes: 6 additions & 315 deletions proposals/p3720.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [C++ pointer to member](#c-pointer-to-member)
- [Instance interface members](#instance-interface-members)
- [Non-instance interface members](#non-instance-interface-members)
- [Member forwarding](#member-forwarding)
- [`extend api`](#extend-api)
- [Binding to the members of another type](#binding-to-the-members-of-another-type)
- [Other uses of `extend api`](#other-uses-of-extend-api)
- [C++ operator overloading](#c-operator-overloading)
- [Future work](#future-work)
- [Future: tuple indexing](#future-tuple-indexing)
Expand All @@ -39,17 +35,14 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Swap the bind interface parameters](#swap-the-bind-interface-parameters)
- [Reference binding produces a value that wraps a pointer](#reference-binding-produces-a-value-that-wraps-a-pointer)
- [Separate interface for compile-time binding instead of type binding](#separate-interface-for-compile-time-binding-instead-of-type-binding)
- [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)

<!-- tocstop -->

## Abstract

Define the binding operation used to compute the result of `x.y`, `p->y`,
`x.(C.y)`, and `p->(C.y)` as calling a method from user-implementable
interfaces. Also allow classes to `extend api` other classes, for forwarding use
cases.
interfaces.

## Problem

Expand Down Expand Up @@ -157,43 +150,6 @@ by how they rewrite into the `x.(y)` form using these two rules:
the `T.(`\_\_\_`)` form.
- `x->y` and `x->(y)` are interpreted as `(*x).y` and `(*x).(y)` respectively.

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

To use instance members of a class, we need to go through the additional step of
Expand Down Expand Up @@ -798,253 +754,13 @@ Which calls the above implementation that calls `Fireworks()`.
> result type. Furthermore, we want `TypeBind` implementation no matter which
> facet of the type is used in the code.
### 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, see
[inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions).
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.

### C++ operator overloading
C++ does not support a feature like `extend api` nor does it support customizing
the behavior of `x.y`. It does support customizing the behavior of `operator*`
and `operator->` which is frequently used to support smart pointers and
iterators. There is, however, nothing restricting the implementations of those
two operators to be consistent, so that `(*x).y` and `x->y` are the same.
C++ does not support customizing the behavior of `x.y`. It does support
customizing the behavior of `operator*` and `operator->` which is frequently
used to support smart pointers and iterators. There is, however, nothing
restricting the implementations of those two operators to be consistent, so that
`(*x).y` and `x->y` are the same.
Carbon instead will only have a single interface for customizing dereference,
corresponding to `operator*` not `operator->`. All uses of `x->y` will be
Expand Down Expand Up @@ -1263,28 +979,3 @@ types, not all compile-time values. The
was that types are special because their members are defined by their values,
not by their type
([which is always `type`](https://github.com/carbon-language/carbon-lang/pull/2360)).

### 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)

0 comments on commit 8df9720

Please sign in to comment.